From dfb84b09f3500ba0d99be847d03b22e98899aa5c Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sun, 29 Jun 2025 00:59:31 +0400 Subject: [PATCH] server: improved performance and stability with broken AI and human games, e.g. less memory leaks and out of memory errors with AI (related to #11285, #5023); --- .../card/images/DownloadPicturesService.java | 2 +- .../src/mage/player/ai/ComputerPlayer6.java | 16 +++++----------- Mage.Sets/src/mage/cards/e/EtaliPrimalStorm.java | 3 +-- Mage/src/main/java/mage/game/GameImpl.java | 4 +++- Mage/src/main/java/mage/players/PlayerImpl.java | 6 ++++-- 5 files changed, 14 insertions(+), 17 deletions(-) diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPicturesService.java b/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPicturesService.java index a8fb8ddc6c1..704395e0378 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPicturesService.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPicturesService.java @@ -157,7 +157,7 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements @Override public boolean isNeedCancel() { - return this.needCancel || (this.errorCount > MAX_ERRORS_COUNT_BEFORE_CANCEL) || Thread.interrupted(); + return this.needCancel || (this.errorCount > MAX_ERRORS_COUNT_BEFORE_CANCEL) || Thread.currentThread().isInterrupted(); } private void setNeedCancel(boolean needCancel) { diff --git a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java index da953db2726..425d92bd61f 100644 --- a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java +++ b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayer6.java @@ -213,10 +213,8 @@ public class ComputerPlayer6 extends ComputerPlayer { logger.trace("Add Action [" + depth + "] " + node.getAbilities().toString() + " a: " + alpha + " b: " + beta); } Game game = node.getGame(); - if (!COMPUTER_DISABLE_TIMEOUT_IN_GAME_SIMULATIONS - && Thread.interrupted()) { - Thread.currentThread().interrupt(); - logger.debug("interrupted"); + if (!COMPUTER_DISABLE_TIMEOUT_IN_GAME_SIMULATIONS && Thread.currentThread().isInterrupted()) { + logger.debug("AI game sim interrupted by timeout"); return GameStateEvaluator2.evaluate(playerId, game).getTotalScore(); } // Condition to stop deeper simulation @@ -476,10 +474,8 @@ public class ComputerPlayer6 extends ComputerPlayer { } protected int simulatePriority(SimulationNode2 node, Game game, int depth, int alpha, int beta) { - if (!COMPUTER_DISABLE_TIMEOUT_IN_GAME_SIMULATIONS - && Thread.interrupted()) { - Thread.currentThread().interrupt(); - logger.info("interrupted"); + if (!COMPUTER_DISABLE_TIMEOUT_IN_GAME_SIMULATIONS && Thread.currentThread().isInterrupted()) { + logger.debug("AI game sim interrupted by timeout"); return GameStateEvaluator2.evaluate(playerId, game).getTotalScore(); } node.setGameValue(game.getState().getValue(true).hashCode()); @@ -507,9 +503,7 @@ public class ComputerPlayer6 extends ComputerPlayer { int bestValSubNodes = Integer.MIN_VALUE; for (Ability action : allActions) { actionNumber++; - if (!COMPUTER_DISABLE_TIMEOUT_IN_GAME_SIMULATIONS - && Thread.interrupted()) { - Thread.currentThread().interrupt(); + if (!COMPUTER_DISABLE_TIMEOUT_IN_GAME_SIMULATIONS && Thread.currentThread().isInterrupted()) { logger.info("Sim Prio [" + depth + "] -- interrupted"); break; } diff --git a/Mage.Sets/src/mage/cards/e/EtaliPrimalStorm.java b/Mage.Sets/src/mage/cards/e/EtaliPrimalStorm.java index 18a1d80e468..c4997deae4a 100644 --- a/Mage.Sets/src/mage/cards/e/EtaliPrimalStorm.java +++ b/Mage.Sets/src/mage/cards/e/EtaliPrimalStorm.java @@ -29,8 +29,7 @@ public final class EtaliPrimalStorm extends CardImpl { this.power = new MageInt(6); this.toughness = new MageInt(6); - // Whenever Etali, Primal Storm attacks, exile the top card of each player's library, - // then you may cast any number of nonland cards exiled this way without paying their mana costs. + // Whenever Etali, Primal Storm attacks, exile the top card of each player's library, then you may cast any number of nonland cards exiled this way without paying their mana costs. this.addAbility(new AttacksTriggeredAbility(new EtaliPrimalStormEffect(), false)); } diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index 10ce4cd1fab..c0ef421ee6b 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -897,7 +897,9 @@ public abstract class GameImpl implements Game { numLosers++; } } - if (remainingPlayers <= 1 || numLosers >= state.getPlayers().size() - 1) { + boolean noMorePlayers = remainingPlayers <= 1 || numLosers >= state.getPlayers().size() - 1; + // stop on no more players or on stopped game sim thread + if (noMorePlayers || Thread.currentThread().isInterrupted()) { end(); if (remainingPlayers == 0 && logger.isDebugEnabled()) { logger.debug("DRAW for gameId: " + getId()); diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 017765f20ef..94d126e11f5 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -2834,8 +2834,10 @@ public abstract class PlayerImpl implements Player, Serializable { } @Override - public boolean canRespond() { // abort is checked here to get out of player requests (as example: after disconnect) - return isInGame() && !abort; + public boolean canRespond() { + // abort is checked here to get out of player requests (as example: after disconnect) + // thread is checked here to get out of AI game simulations or close by third party tools + return isInGame() && !abort && !Thread.currentThread().isInterrupted(); } @Override