* Match handling - some changes to solve/workaround the draw games that cause matches not to end. Changed disconnect handling to not block the client (needs tests if works correctly).

This commit is contained in:
LevelX2 2014-09-18 17:14:33 +02:00
parent 8567c1cd38
commit 3c12b23928
9 changed files with 100 additions and 58 deletions

View file

@ -521,12 +521,15 @@ public class TableController {
if (user != null) {
if (!user.isConnected()) {
// if the user is not connected but exits, the user is currently disconnected. So it's neccessary
// to join the user to the game here, so he can join the game, if he reconnects in time.
// to join the user to the game here (instead the client does it) , so he can join the game, if he reconnects in time.
// remove an existing constructing for the player if it exists
user.removeConstructing(match.getPlayer(entry.getValue()).getPlayer().getId());
GameManager.getInstance().joinGame(match.getGame().getId(), user.getId());
logger.debug("Joined currently not connected user " + user.getName() + " matchId: " + match.getId());
} else {
user.gameStarted(match.getGame().getId(), entry.getValue());
}
user.gameStarted(match.getGame().getId(), entry.getValue());
if (creator == null) {
creator = user.getName();
} else {
@ -543,6 +546,9 @@ public class TableController {
matchPlayer.setQuit(true);
}
}
} else {
// Match player has already quit
throw new MageException("Can't start game - user already quit userId " + entry.getKey());
}
}
// Append AI opponents to the log file

View file

@ -44,6 +44,7 @@ import mage.MageException;
import mage.cards.decks.Deck;
import mage.cards.decks.DeckCardLists;
import mage.constants.TableState;
import mage.game.Game;
import mage.game.GameException;
import mage.game.Table;
import mage.game.draft.Draft;
@ -55,6 +56,7 @@ import mage.players.Player;
import mage.server.game.GameController;
import mage.server.game.GameManager;
import mage.server.game.GamesRoomManager;
import mage.server.util.ThreadExecutor;
import org.apache.log4j.Logger;
/**
@ -65,6 +67,8 @@ public class TableManager {
protected static ScheduledExecutorService expireExecutor = Executors.newSingleThreadScheduledExecutor();
// protected static ScheduledExecutorService expireExecutor = ThreadExecutor.getInstance().getExpireExecutor();
private static final TableManager INSTANCE = new TableManager();
private static final Logger logger = Logger.getLogger(TableManager.class);
private static final DateFormat formatter = new SimpleDateFormat("HH:mm:ss");
@ -77,7 +81,7 @@ public class TableManager {
*
* In minutes.
*/
private static final int EXPIRE_CHECK_PERIOD = 5;
private static final int EXPIRE_CHECK_PERIOD = 1;
public static TableManager getInstance() {
return INSTANCE;
@ -340,14 +344,23 @@ public class TableManager {
Table table = tables.get(tableId);
tables.remove(tableId);
Match match = table.getMatch();
Game game = null;
if (match != null) {
game = match.getGame();
if (game != null && !game.hasEnded()) {
game.end();
}
}
// If table is not finished, the table has to be removed completly because it's not a normal state (if finished it will be removed in GamesRoomImpl.Update())
if (!table.getState().equals(TableState.FINISHED)) {
if (game != null) {
GameManager.getInstance().removeGame(game.getId());
}
GamesRoomManager.getInstance().removeTable(tableId);
}
if (table.getMatch() != null && table.getMatch().getGame() != null) {
table.getMatch().getGame().end();
}
}
}
@ -364,6 +377,7 @@ public class TableManager {
logger.debug(chatSession.getChatId() + " " +formatter.format(chatSession.getCreateTime()) +" " + chatSession.getInfo()+ " "+ chatSession.getClients().values().toString());
}
logger.debug("------- Games: " + GameManager.getInstance().getNumberActiveGames() + " --------------------------------------------");
logger.debug(" Active Game Worker: " + ThreadExecutor.getInstance().getActiveThreads(ThreadExecutor.getInstance().getGameExecutor()));
for (Entry<UUID, GameController> entry: GameManager.getInstance().getGameController().entrySet()) {
logger.debug(entry.getKey() + entry.getValue().getPlayerNameList());
}
@ -404,6 +418,7 @@ public class TableManager {
}
for (UUID tableId : toRemove) {
try {
logger.warn("Removing unhealthy tableId " + tableId);
removeTable(tableId);
} catch (Exception e) {
logger.error(e);

View file

@ -54,6 +54,7 @@ public class UserManager {
private static final Logger logger = Logger.getLogger(UserManager.class);
private final ConcurrentHashMap<UUID, User> users = new ConcurrentHashMap<>();
private static final ExecutorService callExecutor = ThreadExecutor.getInstance().getCallExecutor();
private static final UserManager INSTANCE = new UserManager();
@ -131,18 +132,29 @@ public class UserManager {
return false;
}
public void removeUser(UUID userId, DisconnectReason reason) {
public void removeUser(final UUID userId, final DisconnectReason reason) {
if (userId != null) {
User user = users.get(userId);
final User user = users.get(userId);
if (user != null) {
logger.debug("User " + user.getName() + " will be removed (" + reason.toString() + ") userId: " + userId);
user.remove(reason);
users.remove(userId);
logger.debug("User " + user.getName() + " removed");
callExecutor.execute(
new Runnable() {
@Override
public void run() {
try {
logger.debug("User " + user.getName() + " will be removed (" + reason.toString() + ") userId: " + userId);
user.remove(reason);
users.remove(userId);
logger.debug("User " + user.getName() + " removed");
} catch (Exception ex) {
handleException(ex);
}
}
}
);
} else {
logger.warn(new StringBuilder("Trying to remove userId: ").append(userId).append(" but it does not exist."));
}
}
}
}
public boolean extendUserSession(UUID userId, String pingInfo) {
@ -160,34 +172,16 @@ public class UserManager {
* Is the connection lost for more than 3 minutes, the user will be removed (within 3 minutes the user can reconnect)
*/
private void checkExpired() {
// calling this with executer saves the sceduled job to be dying becuase of exception.
// Also exceptions were not reported as now with this handling
try {
callExecutor.execute(
new Runnable() {
@Override
public void run() {
try {
Calendar expired = Calendar.getInstance();
expired.add(Calendar.MINUTE, -3);
List<User> usersToCheck = new ArrayList<>();
usersToCheck.addAll(users.values());
for (User user : usersToCheck) {
if (user.isExpired(expired.getTime())) {
logger.info(new StringBuilder(user.getName()).append(": session expired userId: ").append(user.getId())
.append(" Host: ").append(user.getHost()));
removeUser(user.getId(), DisconnectReason.SessionExpired);
}
}
} catch (Exception ex) {
handleException(ex);
}
}
}
);
} catch (Exception ex) {
handleException(ex);
Calendar expired = Calendar.getInstance();
expired.add(Calendar.MINUTE, -3);
List<User> usersToCheck = new ArrayList<>();
usersToCheck.addAll(users.values());
for (User user : usersToCheck) {
if (user.isExpired(expired.getTime())) {
logger.info(new StringBuilder(user.getName()).append(": session expired userId: ").append(user.getId())
.append(" Host: ").append(user.getHost()));
removeUser(user.getId(), DisconnectReason.SessionExpired);
}
}
}

View file

@ -302,6 +302,7 @@ public class GameController implements GameCallback {
if (!entry.getValue().init()) {
logger.fatal("Unable to initialize client");
//TODO: generate client error message
GameManager.getInstance().removeGame(game.getId());
return;
}
}

View file

@ -55,6 +55,7 @@ public class GameWorker implements Callable {
@Override
public Object call() {
try {
logger.debug("GameWorker started gameId "+ game.getId());
game.start(choosingPlayerId);
game.fireUpdatePlayersEvent();
result.gameResult(game.getWinner());

View file

@ -59,10 +59,10 @@ public class ThreadExecutor {
((ThreadPoolExecutor)callExecutor).setThreadFactory(new XMageThreadFactory("CALL"));
((ThreadPoolExecutor)gameExecutor).setKeepAliveTime(60, TimeUnit.SECONDS);
((ThreadPoolExecutor)gameExecutor).allowCoreThreadTimeOut(true);
((ThreadPoolExecutor)callExecutor).setThreadFactory(new XMageThreadFactory("GAME"));
((ThreadPoolExecutor)gameExecutor).setThreadFactory(new XMageThreadFactory("GAME"));
((ThreadPoolExecutor)timeoutExecutor).setKeepAliveTime(60, TimeUnit.SECONDS);
((ThreadPoolExecutor)timeoutExecutor).allowCoreThreadTimeOut(true);
((ThreadPoolExecutor)callExecutor).setThreadFactory(new XMageThreadFactory("TIME"));
((ThreadPoolExecutor)timeoutExecutor).setThreadFactory(new XMageThreadFactory("TIME"));
}
private static final ThreadExecutor INSTANCE = new ThreadExecutor();
@ -73,10 +73,17 @@ public class ThreadExecutor {
private ThreadExecutor() {}
public int getActiveThreads(ExecutorService executerService) {
if (executerService instanceof ThreadPoolExecutor) {
return ((ThreadPoolExecutor)executerService).getActiveCount();
}
return -1;
}
public ExecutorService getCallExecutor() {
return callExecutor;
}
public ExecutorService getGameExecutor() {
return gameExecutor;
}
@ -84,7 +91,7 @@ public class ThreadExecutor {
public ScheduledExecutorService getTimeoutExecutor() {
return timeoutExecutor;
}
}
class XMageThreadFactory implements ThreadFactory {

View file

@ -38,6 +38,7 @@ import mage.game.events.TableEventSource;
import mage.players.Player;
import java.util.*;
import org.apache.log4j.Logger;
/**
@ -46,6 +47,8 @@ import java.util.*;
*/
public abstract class MatchImpl implements Match {
private static final Logger logger = Logger.getLogger(MatchImpl.class);
protected UUID id = UUID.randomUUID();
protected List<MatchPlayer> players = new ArrayList<>();
protected List<Game> games = new ArrayList<>();
@ -128,6 +131,19 @@ public abstract class MatchImpl implements Match {
@Override
public boolean hasEnded() {
// Some workarounds to end match if for unknown reason the match was not ended regularly
if (getGame() == null && isDoneSideboarding()) {
checkIfMatchEnds();
}
if (getGame() != null && getGame().hasEnded()) {
for (MatchPlayer matchPlayer:players) {
if (matchPlayer.getPlayer().hasQuit() && !matchPlayer.hasQuit()) {
logger.warn("MatchPlayer was not set to quit matchId " + this.getId()+ " - " + matchPlayer.getName());
matchPlayer.setQuit(true);
}
}
checkIfMatchEnds();
}
return endTime != null;
}

View file

@ -45,7 +45,7 @@ public class MatchPlayer {
private final String name;
private boolean quit;
private boolean timerTimeout;
private final boolean timerTimeout;
private boolean doneSideboarding;
private int priorityTimeLeft;

View file

@ -72,18 +72,20 @@ public class Round {
public boolean isRoundOver() {
boolean roundIsOver = true;
for (TournamentPairing pair: pairs) {
if (pair.getMatch() != null && !pair.getMatch().hasEnded()) {
roundIsOver = false;
} else {
if (!pair.isAlreadyPublished()) {
tournament.updateResults();
pair.setAlreadyPublished(true);
if (tournament instanceof TournamentSingleElimination) {
pair.eliminatePlayers();
}
// if it's the last round, finish all players for the tournament if their match is finished
if (getRoundNumber() == tournament.getNumberRounds()) {
pair.finishPlayersThatPlayedLastRound();
if (pair.getMatch() != null) {
if (!pair.getMatch().hasEnded()) {
roundIsOver = false;
} else {
if (!pair.isAlreadyPublished()) {
tournament.updateResults();
pair.setAlreadyPublished(true);
if (tournament instanceof TournamentSingleElimination) {
pair.eliminatePlayers();
}
// if it's the last round, finish all players for the tournament if their match is finished
if (getRoundNumber() == tournament.getNumberRounds()) {
pair.finishPlayersThatPlayedLastRound();
}
}
}
}