diff --git a/Mage.Server/src/main/java/mage/server/MageServerImpl.java b/Mage.Server/src/main/java/mage/server/MageServerImpl.java index c9e1423485f..7edd074a29e 100644 --- a/Mage.Server/src/main/java/mage/server/MageServerImpl.java +++ b/Mage.Server/src/main/java/mage/server/MageServerImpl.java @@ -1004,7 +1004,15 @@ public class MageServerImpl implements MageServer { if (ex.getMessage() != null && !ex.getMessage().equals("No message")) { throw new MageException("Server error: " + ex.getMessage()); } - logger.error("unknown error", ex); // TODO: on logs spamming (e.g. connection problems) move it inside condition block above + + if (ex instanceof ConcurrentModificationException) { + // how-to fix: game objects must be accessible by game thread only, all other threads must work with copies + logger.error("wrong threads sync error", ex); + } else { + // TODO: on logs spamming (e.g. connection problems) move it inside condition block above + logger.error("unknown error", ex); + } + } @Override diff --git a/Mage.Server/src/main/java/mage/server/game/GameSessionWatcher.java b/Mage.Server/src/main/java/mage/server/game/GameSessionWatcher.java index 3bb4fefae3a..e30c6ced338 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameSessionWatcher.java +++ b/Mage.Server/src/main/java/mage/server/game/GameSessionWatcher.java @@ -42,6 +42,8 @@ public class GameSessionWatcher { if (!killed) { Optional user = userManager.getUser(userId); if (user.isPresent()) { + // TODO: can be called outside of the game thread, e.g. user start watching already running game + // possible fix: getGameView must use last cached value in non game thread call (split by sessions) user.get().fireCallback(new ClientCallback(ClientCallbackMethod.GAME_INIT, game.getId(), getGameView())); return true; } diff --git a/Mage.Tests/src/test/java/org/mage/test/load/LoadCallbackClient.java b/Mage.Tests/src/test/java/org/mage/test/load/LoadCallbackClient.java index 4de90ba8779..35046809512 100644 --- a/Mage.Tests/src/test/java/org/mage/test/load/LoadCallbackClient.java +++ b/Mage.Tests/src/test/java/org/mage/test/load/LoadCallbackClient.java @@ -34,7 +34,7 @@ public class LoadCallbackClient implements CallbackClient { private final String logsPrefix; private final Boolean showLogsAsHtml; // original game logs in HTML, but it can be converted to txt for more readable console - private String globalProgress = ""; // progress 33% [=20.cd, +21, +17], AI game #9: --- + private String globalProgress = ""; // example: progress 33% [20.cd, 21.__, 17.__], AI game #09: --- public LoadCallbackClient(boolean joinGameChat, String logsPrefix, Boolean showLogsAsHtml) { this.joinGameChat = joinGameChat; diff --git a/Mage.Tests/src/test/java/org/mage/test/load/LoadTest.java b/Mage.Tests/src/test/java/org/mage/test/load/LoadTest.java index 6f7696f163a..16602b23471 100644 --- a/Mage.Tests/src/test/java/org/mage/test/load/LoadTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/load/LoadTest.java @@ -51,7 +51,7 @@ public class LoadTest { private static final Boolean TEST_SHOW_GAME_LOGS_AS_HTML = false; // html is original format with full data, but can be too bloated private static final String TEST_AI_GAME_MODE = "Freeform Commander Free For All"; private static final String TEST_AI_DECK_TYPE = "Variant Magic - Freeform Commander"; - private static final String TEST_AI_RANDOM_DECK_SETS = "LCI,LCC,WHO"; // set for random generated decks (empty for all sets usage, PELP for lands only - communication test) + private static final String TEST_AI_RANDOM_DECK_SETS = "DFT,DRC"; // set for random generated decks (empty for all sets usage, PELP for lands only - communication test) private static final String TEST_AI_RANDOM_DECK_COLORS_FOR_EMPTY_GAME = "GR"; // colors list for deck generation, empty for all colors private static final String TEST_AI_RANDOM_DECK_COLORS_FOR_AI_GAME = "WUBRG"; private static final String TEST_AI_CUSTOM_DECK_PATH_1 = ""; // custom deck file instead random for player 1 (empty for random) @@ -369,7 +369,7 @@ public class LoadTest { long randomSeed = seedsList.get(i); logger.info("Game " + (i + 1) + " of " + seedsList.size() + ", RANDOM seed: " + randomSeed); Future gameTask = executerService.submit(() -> { - String gameName = "AI game #" + (gameIndex + 1); + String gameName = String.format("AI game #%02d", gameIndex + 1); LoadTestGameResult gameResult = gameResults.createGame(gameIndex + 1, gameName, randomSeed); playTwoAIGame(gameName, gameIndex + 1, tasksProgress, randomSeed, TEST_AI_RANDOM_DECK_COLORS_FOR_AI_GAME, TEST_AI_RANDOM_DECK_SETS, gameResult); }); @@ -606,7 +606,7 @@ public class LoadTest { } private void updateInfo() { - // example: progress 33% [=20.cd, +21, +17], AI game #9: --- + // example: progress 33% [20.cd, 21.__, 17.__], AI game #09: --- int completed = this.finishes.values().stream().mapToInt(x -> x.isEmpty() ? 0 : 1).sum(); int completedPercent = this.finishes.size() == 0 ? 0 : completed * 100 / this.finishes.size(); @@ -617,10 +617,10 @@ public class LoadTest { String finishInfo = this.finishes.getOrDefault(taskNumber, ""); if (finishInfo.isEmpty()) { // active - return "+" + turn; + return turn + ".__"; } else { // done - return "=" + turn + "." + finishInfo; + return turn + "." + finishInfo; } }) .collect(Collectors.joining(", "));