diff --git a/Mage.Server/src/main/java/mage/server/TableController.java b/Mage.Server/src/main/java/mage/server/TableController.java index 78d153316ba..63b95dc3c43 100644 --- a/Mage.Server/src/main/java/mage/server/TableController.java +++ b/Mage.Server/src/main/java/mage/server/TableController.java @@ -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 diff --git a/Mage.Server/src/main/java/mage/server/TableManager.java b/Mage.Server/src/main/java/mage/server/TableManager.java index 95b59dd6d00..b541a3bd988 100644 --- a/Mage.Server/src/main/java/mage/server/TableManager.java +++ b/Mage.Server/src/main/java/mage/server/TableManager.java @@ -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 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); diff --git a/Mage.Server/src/main/java/mage/server/UserManager.java b/Mage.Server/src/main/java/mage/server/UserManager.java index 1a64bb93e08..af683f9ec5b 100644 --- a/Mage.Server/src/main/java/mage/server/UserManager.java +++ b/Mage.Server/src/main/java/mage/server/UserManager.java @@ -54,6 +54,7 @@ public class UserManager { private static final Logger logger = Logger.getLogger(UserManager.class); private final ConcurrentHashMap 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 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 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); + } } } diff --git a/Mage.Server/src/main/java/mage/server/game/GameController.java b/Mage.Server/src/main/java/mage/server/game/GameController.java index 2f6d7759d83..109224c72b7 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameController.java +++ b/Mage.Server/src/main/java/mage/server/game/GameController.java @@ -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; } } diff --git a/Mage.Server/src/main/java/mage/server/game/GameWorker.java b/Mage.Server/src/main/java/mage/server/game/GameWorker.java index 83ab9a874da..f015e5e2ec9 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameWorker.java +++ b/Mage.Server/src/main/java/mage/server/game/GameWorker.java @@ -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()); diff --git a/Mage.Server/src/main/java/mage/server/util/ThreadExecutor.java b/Mage.Server/src/main/java/mage/server/util/ThreadExecutor.java index b228e20e132..420775d57b3 100644 --- a/Mage.Server/src/main/java/mage/server/util/ThreadExecutor.java +++ b/Mage.Server/src/main/java/mage/server/util/ThreadExecutor.java @@ -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 { diff --git a/Mage/src/mage/game/match/MatchImpl.java b/Mage/src/mage/game/match/MatchImpl.java index c8715cfc827..8f6574e5c46 100644 --- a/Mage/src/mage/game/match/MatchImpl.java +++ b/Mage/src/mage/game/match/MatchImpl.java @@ -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 players = new ArrayList<>(); protected List 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; } diff --git a/Mage/src/mage/game/match/MatchPlayer.java b/Mage/src/mage/game/match/MatchPlayer.java index 3e91349f7bf..1398c6916ce 100644 --- a/Mage/src/mage/game/match/MatchPlayer.java +++ b/Mage/src/mage/game/match/MatchPlayer.java @@ -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; diff --git a/Mage/src/mage/game/tournament/Round.java b/Mage/src/mage/game/tournament/Round.java index e52221a0b26..3ff2469a061 100644 --- a/Mage/src/mage/game/tournament/Round.java +++ b/Mage/src/mage/game/tournament/Round.java @@ -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(); + } } } }