From 7d675de8769e691c1489e33e2ab8d7eae688d367 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sun, 23 Jun 2024 15:58:25 +0400 Subject: [PATCH] server: fixed server app freeze on another instance already running, improved threads usage (related to #11285); --- .../src/main/java/mage/client/MageFrame.java | 10 ++++- .../client/deckeditor/DeckEditorPanel.java | 9 +++- .../java/mage/client/game/FeedbackPanel.java | 10 +++-- .../org/mage/plugins/card/dl/Downloader.java | 8 +++- .../dl/sources/WizardCardsImageSource.java | 10 ----- .../card/images/DownloadPicturesService.java | 11 ++++- .../mage/server/console/ConsoleFrame.java | 8 +++- .../src/mage/player/ai/ComputerPlayer6.java | 8 ++-- .../mage/player/ai/ComputerPlayerMCTS.java | 8 ++-- .../java/mage/server/MainManagerFactory.java | 4 -- .../java/mage/server/UserManagerImpl.java | 16 ++++--- .../java/mage/server/game/GameController.java | 20 +++++++-- .../java/mage/server/game/GamesRoomImpl.java | 41 +++++++++++------- .../mage/server/util/ServerMessagesUtil.java | 8 +++- .../mage/server/util/ThreadExecutorImpl.java | 17 +------- .../java/org/mage/test/load/LoadTest.java | 6 ++- .../mage/cards/repository/CardRepository.java | 2 +- .../cards/repository/ExpansionRepository.java | 10 ++--- .../main/java/mage/game/draft/DraftImpl.java | 30 ++++++++----- Mage/src/main/java/mage/util/ThreadUtils.java | 31 ++++++++++---- .../java/mage/util/XMageThreadFactory.java | 42 +++++++++++++++++++ 21 files changed, 203 insertions(+), 106 deletions(-) create mode 100644 Mage/src/main/java/mage/util/XMageThreadFactory.java diff --git a/Mage.Client/src/main/java/mage/client/MageFrame.java b/Mage.Client/src/main/java/mage/client/MageFrame.java index 3261de8b9c5..2f36cc42f61 100644 --- a/Mage.Client/src/main/java/mage/client/MageFrame.java +++ b/Mage.Client/src/main/java/mage/client/MageFrame.java @@ -44,6 +44,8 @@ import mage.interfaces.callback.ClientCallback; import mage.remote.Connection; import mage.remote.Connection.ProxyType; import mage.util.DebugUtil; +import mage.util.ThreadUtils; +import mage.util.XMageThreadFactory; import mage.utils.MageVersion; import mage.view.GameEndView; import mage.view.UserRequestMessage; @@ -79,6 +81,8 @@ import java.util.concurrent.TimeUnit; import java.util.prefs.Preferences; /** + * Client app + * * @author BetaSteward_at_googlemail.com, JayDi85 */ public class MageFrame extends javax.swing.JFrame implements MageClient { @@ -129,7 +133,9 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { private static final Map DRAFTS = new HashMap<>(); private static final MageUI UI = new MageUI(); - private static final ScheduledExecutorService PING_TASK_EXECUTOR = Executors.newSingleThreadScheduledExecutor(); + private static final ScheduledExecutorService PING_SENDER_EXECUTOR = Executors.newSingleThreadScheduledExecutor( + new XMageThreadFactory(ThreadUtils.THREAD_PREFIX_CLIENT_PING_SENDER) + ); private static UpdateMemUsageTask updateMemUsageTask; private static long startTime; @@ -317,7 +323,7 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { desktopPane.add(errorDialog, JLayeredPane.MODAL_LAYER); UI.addComponent(MageComponents.DESKTOP_PANE, desktopPane); - PING_TASK_EXECUTOR.scheduleAtFixedRate(() -> SessionHandler.ping(), TablesPanel.PING_SERVER_SECS, TablesPanel.PING_SERVER_SECS, TimeUnit.SECONDS); + PING_SENDER_EXECUTOR.scheduleAtFixedRate(SessionHandler::ping, TablesPanel.PING_SERVER_SECS, TablesPanel.PING_SERVER_SECS, TimeUnit.SECONDS); updateMemUsageTask = new UpdateMemUsageTask(jMemUsageLabel); diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java b/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java index 2ed8dff85fe..ed27da771e3 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java +++ b/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java @@ -25,6 +25,8 @@ import mage.components.CardInfoPane; import mage.game.GameException; import mage.remote.Session; import mage.util.DeckUtil; +import mage.util.ThreadUtils; +import mage.util.XMageThreadFactory; import mage.view.CardView; import mage.view.SimpleCardView; import org.apache.log4j.Logger; @@ -1493,11 +1495,14 @@ public class DeckEditorPanel extends javax.swing.JPanel { private void btnSubmitTimerActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnSubmitTimerActionPerformed - ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5); + ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor( + new XMageThreadFactory(ThreadUtils.THREAD_PREFIX_CLIENT_SUBMIT_TIMER) + ); timeToSubmit = 60; this.btnSubmitTimer.setEnabled(false); - scheduledExecutorService.schedule(() -> { + // TODO: need code and feature review. It sends deck every minute -- is it useless? There is another feature with auto-save + executorService.schedule(() -> { if (updateDeckTask != null) { updateDeckTask.cancel(true); } diff --git a/Mage.Client/src/main/java/mage/client/game/FeedbackPanel.java b/Mage.Client/src/main/java/mage/client/game/FeedbackPanel.java index c16429e0a97..a562a947a0b 100644 --- a/Mage.Client/src/main/java/mage/client/game/FeedbackPanel.java +++ b/Mage.Client/src/main/java/mage/client/game/FeedbackPanel.java @@ -4,11 +4,12 @@ import mage.client.MageFrame; import mage.client.SessionHandler; import mage.client.chat.ChatPanelBasic; import mage.client.dialog.MageDialog; -import mage.client.util.GUISizeHelper; import mage.client.util.audio.AudioManager; import mage.client.util.gui.ArrowBuilder; import mage.constants.PlayerAction; import mage.constants.TurnPhase; +import mage.util.ThreadUtils; +import mage.util.XMageThreadFactory; import org.apache.log4j.Logger; import javax.swing.*; @@ -43,7 +44,9 @@ public class FeedbackPanel extends javax.swing.JPanel { private Map lastOptions = new HashMap<>(); private static final int AUTO_CLOSE_END_DIALOG_TIMEOUT_SECS = 8; - private static final ScheduledExecutorService WORKER = Executors.newSingleThreadScheduledExecutor(); + private static final ScheduledExecutorService AUTO_CLOSE_EXECUTOR = Executors.newSingleThreadScheduledExecutor( + new XMageThreadFactory(ThreadUtils.THREAD_PREFIX_CLIENT_AUTO_CLOSE_TIMER) + ); /** * Creates new form FeedbackPanel @@ -158,6 +161,7 @@ public class FeedbackPanel extends javax.swing.JPanel { * Close game window by pressing OK button after 8 seconds */ private void endWithTimeout() { + // TODO: add auto-close disable, e.g. keep opened game and chat for longer period like 5 minutes Runnable task = () -> { SwingUtilities.invokeLater(() -> { LOGGER.info("Ending game..."); @@ -170,7 +174,7 @@ public class FeedbackPanel extends javax.swing.JPanel { } }); }; - WORKER.schedule(task, AUTO_CLOSE_END_DIALOG_TIMEOUT_SECS, TimeUnit.SECONDS); + AUTO_CLOSE_EXECUTOR.schedule(task, AUTO_CLOSE_END_DIALOG_TIMEOUT_SECS, TimeUnit.SECONDS); } public void updateOptions(Map options) { diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/Downloader.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/Downloader.java index c4af2b2d452..6281a8dc69a 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/Downloader.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/Downloader.java @@ -1,5 +1,7 @@ package org.mage.plugins.card.dl; +import mage.util.ThreadUtils; +import mage.util.XMageThreadFactory; import org.apache.log4j.Logger; import org.jetlang.channels.Channel; import org.jetlang.channels.MemoryChannel; @@ -22,7 +24,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; /** - * Downloader + * Symbols downloader * * @author Clemens Koza, JayDi85 */ @@ -34,7 +36,9 @@ public class Downloader extends AbstractLaternaBean { private final Channel jobsQueue = new MemoryChannel<>(); private CountDownLatch worksCount = null; - private final ExecutorService pool = Executors.newCachedThreadPool(); + private final ExecutorService pool = Executors.newCachedThreadPool( + new XMageThreadFactory(ThreadUtils.THREAD_PREFIX_CLIENT_SYMBOLS_DOWNLOADER, false) + ); private final List fibers = new ArrayList<>(); public Downloader() { diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/WizardCardsImageSource.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/WizardCardsImageSource.java index 1fae9aa419b..b32a9437f97 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/WizardCardsImageSource.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/WizardCardsImageSource.java @@ -508,7 +508,6 @@ public enum WizardCardsImageSource implements CardImageSource { private Map getSetLinks(String cardSet) { LinkedHashMap setLinks = new LinkedHashMap<>(); - ExecutorService executor = Executors.newFixedThreadPool(10); try { String setNames = setsAliases.get(cardSet); if (setNames == null) { @@ -563,15 +562,6 @@ public enum WizardCardsImageSource implements CardImageSource { logger.error("Exception when parsing the wizards page: " + ex.getMessage()); } - executor.shutdown(); - - while (!executor.isTerminated()) { - try { - TimeUnit.SECONDS.sleep(1); - } catch (InterruptedException ie) { - } - } - return setLinks; } 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 cd6539125aa..20e545d7b61 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 @@ -13,6 +13,8 @@ import mage.client.util.CardLanguage; import mage.client.util.GUISizeHelper; import mage.client.util.sets.ConstructedFormats; import mage.remote.Connection; +import mage.util.ThreadUtils; +import mage.util.XMageThreadFactory; import net.java.truevfs.access.TFile; import net.java.truevfs.access.TFileOutputStream; import net.java.truevfs.access.TVFS; @@ -38,6 +40,8 @@ import java.util.stream.Collectors; import static org.mage.plugins.card.utils.CardImageUtils.getImagesDir; /** + * Images downloader + * * @author JayDi85 */ public class DownloadPicturesService extends DefaultBoundedRangeModel implements DownloadServiceInfo, Runnable { @@ -629,7 +633,7 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements } } - int downloadThreadsAmount = Integer.parseInt((String) uiDialog.getDownloadThreadsCombo().getSelectedItem()); + int downloadThreadsAmount = Math.max(1, Integer.parseInt((String) uiDialog.getDownloadThreadsCombo().getSelectedItem())); if (proxy != null) { logger.info("Started download of " + cardsDownloadQueue.size() + " images" @@ -639,7 +643,10 @@ public class DownloadPicturesService extends DefaultBoundedRangeModel implements updateProgressMessage("Preparing download list..."); if (selectedSource.prepareDownloadList(this, cardsDownloadQueue)) { update(0, cardsDownloadQueue.size()); - ExecutorService executor = Executors.newFixedThreadPool(downloadThreadsAmount); + ExecutorService executor = Executors.newFixedThreadPool( + downloadThreadsAmount, + new XMageThreadFactory(ThreadUtils.THREAD_PREFIX_CLIENT_IMAGES_DOWNLOADER, false) + ); for (int i = 0; i < cardsDownloadQueue.size() && !this.isNeedCancel(); i++) { try { CardDownloadData card = cardsDownloadQueue.get(i); diff --git a/Mage.Server.Console/src/main/java/mage/server/console/ConsoleFrame.java b/Mage.Server.Console/src/main/java/mage/server/console/ConsoleFrame.java index dc44a69f160..393b8efba55 100644 --- a/Mage.Server.Console/src/main/java/mage/server/console/ConsoleFrame.java +++ b/Mage.Server.Console/src/main/java/mage/server/console/ConsoleFrame.java @@ -5,6 +5,8 @@ import mage.interfaces.callback.ClientCallback; import mage.remote.Connection; import mage.remote.Session; import mage.remote.SessionImpl; +import mage.util.ThreadUtils; +import mage.util.XMageThreadFactory; import mage.utils.MageVersion; import org.apache.log4j.Logger; @@ -29,7 +31,9 @@ public class ConsoleFrame extends javax.swing.JFrame implements MageClient { private static final Preferences prefs = Preferences.userNodeForPackage(ConsoleFrame.class); private static final MageVersion version = new MageVersion(ConsoleFrame.class); - private static final ScheduledExecutorService pingTaskExecutor = Executors.newSingleThreadScheduledExecutor(); + private static final ScheduledExecutorService PING_SENDER_EXECUTOR = Executors.newSingleThreadScheduledExecutor( + new XMageThreadFactory(ThreadUtils.THREAD_PREFIX_CLIENT_PING_SENDER) + ); /** * @return the session @@ -75,7 +79,7 @@ public class ConsoleFrame extends javax.swing.JFrame implements MageClient { logger.fatal("", ex); } - pingTaskExecutor.scheduleAtFixedRate(() -> session.ping(), 20, 20, TimeUnit.SECONDS); + PING_SENDER_EXECUTOR.scheduleAtFixedRate(() -> session.ping(), 20, 20, TimeUnit.SECONDS); } public boolean connect(Connection connection) { 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 167a71aff07..0498e89f07c 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 @@ -32,6 +32,7 @@ import mage.target.TargetCard; import mage.util.CardUtil; import mage.util.RandomUtil; import mage.util.ThreadUtils; +import mage.util.XMageThreadFactory; import org.apache.log4j.Logger; import java.util.*; @@ -60,11 +61,8 @@ public class ComputerPlayer6 extends ComputerPlayer { 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), - r -> { - Thread thread = new Thread(r); - thread.setName(ThreadUtils.THREAD_PREFIX_AI_SIMULATION + "-" + thread.getId()); - return thread; - }); + new XMageThreadFactory(ThreadUtils.THREAD_PREFIX_AI_SIMULATION_MAD) + ); protected int maxDepth; protected int maxNodes; protected int maxThinkTimeSecs; diff --git a/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/ComputerPlayerMCTS.java b/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/ComputerPlayerMCTS.java index aa20af794a3..1b19a95b4dc 100644 --- a/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/ComputerPlayerMCTS.java +++ b/Mage.Server.Plugins/Mage.Player.AIMCTS/src/mage/player/ai/ComputerPlayerMCTS.java @@ -13,6 +13,7 @@ import mage.game.combat.CombatGroup; import mage.player.ai.MCTSPlayer.NextAction; import mage.players.Player; import mage.util.ThreadUtils; +import mage.util.XMageThreadFactory; import org.apache.log4j.Logger; import java.util.ArrayList; @@ -172,11 +173,8 @@ public class ComputerPlayerMCTS extends ComputerPlayer { 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), - r -> { - Thread thread = new Thread(r); - thread.setName(ThreadUtils.THREAD_PREFIX_AI_SIMULATION + "-MCTS-" + thread.getId()); - return thread; - }); + new XMageThreadFactory(ThreadUtils.THREAD_PREFIX_AI_SIMULATION_MCTS) // TODO: add player/game to thread name? + ); } List tasks = new ArrayList<>(); diff --git a/Mage.Server/src/main/java/mage/server/MainManagerFactory.java b/Mage.Server/src/main/java/mage/server/MainManagerFactory.java index 38685ec311e..61c9f322426 100644 --- a/Mage.Server/src/main/java/mage/server/MainManagerFactory.java +++ b/Mage.Server/src/main/java/mage/server/MainManagerFactory.java @@ -74,8 +74,6 @@ public class MainManagerFactory implements ManagerFactory { threadExecutor().getServerHealthExecutor().scheduleAtFixedRate(() -> { try { - //logger.info("---"); - //logger.info("Server health check started"); this.tableManager().checkHealth(); this.chatManager().checkHealth(); this.userManager().checkHealth(); @@ -83,8 +81,6 @@ public class MainManagerFactory implements ManagerFactory { } catch (Exception ex) { logger.fatal("Server health check: catch unknown error - " + ex, ex); } - //logger.info("Server health check end"); - //logger.info("---"); }, SERVER_HEALTH_CHECK_TIMEOUT_MINS, SERVER_HEALTH_CHECK_TIMEOUT_MINS, TimeUnit.MINUTES); } diff --git a/Mage.Server/src/main/java/mage/server/UserManagerImpl.java b/Mage.Server/src/main/java/mage/server/UserManagerImpl.java index 14406afa7c7..5ca39c8c2c8 100644 --- a/Mage.Server/src/main/java/mage/server/UserManagerImpl.java +++ b/Mage.Server/src/main/java/mage/server/UserManagerImpl.java @@ -5,6 +5,8 @@ import mage.server.managers.UserManager; import mage.server.record.UserStats; import mage.server.record.UserStatsRepository; import mage.server.util.ServerMessagesUtil; +import mage.util.ThreadUtils; +import mage.util.XMageThreadFactory; import mage.view.UserView; import org.apache.log4j.Logger; @@ -27,12 +29,16 @@ public class UserManagerImpl implements UserManager { private static final int USER_CONNECTION_TIMEOUT_SESSION_EXPIRE_AFTER_SECS = 3 * 60; // session expire - remove from all tables and chats (can't reconnect after it) private static final int USER_CONNECTION_TIMEOUT_REMOVE_FROM_SERVER_SECS = 8 * 60; // removes from users list - private static final int SERVER_USERS_LIST_UPDATE_SECS = 4; // server side updates (client use own timeouts to request users list) + private static final int SERVER_USERS_LIST_UPDATE_SECS = 10; // server side updates (client use own timeouts to request users list) private static final Logger logger = Logger.getLogger(UserManagerImpl.class); - protected final ScheduledExecutorService expireExecutor = Executors.newSingleThreadScheduledExecutor(); - protected final ScheduledExecutorService userListExecutor = Executors.newSingleThreadScheduledExecutor(); + protected final ScheduledExecutorService CONNECTION_EXPIRED_EXECUTOR = Executors.newSingleThreadScheduledExecutor( + new XMageThreadFactory(ThreadUtils.THREAD_PREFIX_SERVICE_CONNECTION_EXPIRED_CHECK) + ); + protected final ScheduledExecutorService USERS_LIST_REFRESH_EXECUTOR = Executors.newSingleThreadScheduledExecutor( + new XMageThreadFactory(ThreadUtils.THREAD_PREFIX_SERVICE_USERS_LIST_REFRESH) + ); private List userInfoList = new ArrayList<>(); // all users list for main room/chat private int maxUsersOnline = 0; @@ -50,8 +56,8 @@ public class UserManagerImpl implements UserManager { public void init() { USER_EXECUTOR = managerFactory.threadExecutor().getCallExecutor(); - expireExecutor.scheduleAtFixedRate(this::checkExpired, USER_CONNECTION_TIMEOUTS_CHECK_SECS, USER_CONNECTION_TIMEOUTS_CHECK_SECS, TimeUnit.SECONDS); - userListExecutor.scheduleAtFixedRate(this::updateUserInfoList, SERVER_USERS_LIST_UPDATE_SECS, SERVER_USERS_LIST_UPDATE_SECS, TimeUnit.SECONDS); + CONNECTION_EXPIRED_EXECUTOR.scheduleAtFixedRate(this::checkExpired, USER_CONNECTION_TIMEOUTS_CHECK_SECS, USER_CONNECTION_TIMEOUTS_CHECK_SECS, TimeUnit.SECONDS); + USERS_LIST_REFRESH_EXECUTOR.scheduleAtFixedRate(this::updateUserInfoList, SERVER_USERS_LIST_UPDATE_SECS, SERVER_USERS_LIST_UPDATE_SECS, TimeUnit.SECONDS); } @Override 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 6018fde920c..53f5a733abc 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameController.java +++ b/Mage.Server/src/main/java/mage/server/game/GameController.java @@ -25,6 +25,8 @@ import mage.server.Main; import mage.server.User; import mage.server.managers.ManagerFactory; import mage.util.MultiAmountMessage; +import mage.util.ThreadUtils; +import mage.util.XMageThreadFactory; import mage.utils.StreamUtils; import mage.utils.timer.PriorityTimer; import mage.view.*; @@ -53,7 +55,7 @@ public class GameController implements GameCallback { private final ExecutorService gameExecutor; private static final Logger logger = Logger.getLogger(GameController.class); - protected final ScheduledExecutorService joinWaitingExecutor = Executors.newSingleThreadScheduledExecutor(); + private ScheduledExecutorService JOIN_WAITING_EXECUTOR = null; private ScheduledFuture futureTimeout; private final ManagerFactory managerFactory; @@ -238,13 +240,21 @@ public class GameController implements GameCallback { } } ); - joinWaitingExecutor.scheduleAtFixedRate(() -> { + + // wait all players + if (JOIN_WAITING_EXECUTOR == null) { + JOIN_WAITING_EXECUTOR = Executors.newSingleThreadScheduledExecutor( + new XMageThreadFactory(ThreadUtils.THREAD_PREFIX_GAME_JOIN_WAITING + " " + game.getId()) + ); + } + JOIN_WAITING_EXECUTOR.scheduleAtFixedRate(() -> { try { sendInfoAboutPlayersNotJoinedYetAndTryToFixIt(); } catch (Exception ex) { logger.fatal("Send info about player not joined yet:", ex); } }, GAME_TIMEOUTS_CHECK_JOINING_STATUS_EVERY_SECS, GAME_TIMEOUTS_CHECK_JOINING_STATUS_EVERY_SECS, TimeUnit.SECONDS); + checkJoinAndStart(); } @@ -347,7 +357,9 @@ public class GameController implements GameCallback { } private void sendInfoAboutPlayersNotJoinedYetAndTryToFixIt() { - // runs every 10 secs untill all players join + // TODO: need code and feature review - is it useful? 2024-06-23 + // runs every 10 secs until all players join + // runs BEFORE game start, so it's safe to call player.leave here for (Player player : game.getPlayers().values()) { if (player.canRespond() && player.isHuman()) { Optional requestedUser = getUserByPlayerId(player.getId()); @@ -412,7 +424,7 @@ public class GameController implements GameCallback { private void checkJoinAndStart() { if (isAllJoined()) { - joinWaitingExecutor.shutdownNow(); + JOIN_WAITING_EXECUTOR.shutdownNow(); managerFactory.threadExecutor().getCallExecutor().execute(this::startGame); } } diff --git a/Mage.Server/src/main/java/mage/server/game/GamesRoomImpl.java b/Mage.Server/src/main/java/mage/server/game/GamesRoomImpl.java index a161afcb1cb..a86b9c716e4 100644 --- a/Mage.Server/src/main/java/mage/server/game/GamesRoomImpl.java +++ b/Mage.Server/src/main/java/mage/server/game/GamesRoomImpl.java @@ -11,6 +11,8 @@ import mage.players.PlayerType; import mage.server.RoomImpl; import mage.server.User; import mage.server.managers.ManagerFactory; +import mage.util.ThreadUtils; +import mage.util.XMageThreadFactory; import mage.view.MatchView; import mage.view.RoomUsersView; import mage.view.TableView; @@ -25,18 +27,21 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; /** - * @author BetaSteward_at_googlemail.com + * @author BetaSteward_at_googlemail.com, JayDi85 */ public class GamesRoomImpl extends RoomImpl implements GamesRoom, Serializable { private static final Logger LOGGER = Logger.getLogger(GamesRoomImpl.class); - private static final int MAX_FINISHED_TABLES = 50; + private static final int MAX_FINISHED_TABLES = 25; - private static final ScheduledExecutorService UPDATE_EXECUTOR = Executors.newSingleThreadScheduledExecutor(); - private static List tableView = new ArrayList<>(); - private static List matchView = new ArrayList<>(); - private static List roomUsersView = new ArrayList<>(); + // server's lobby + private static List lobbyTables = new ArrayList<>(); + private static List lobbyMatches = new ArrayList<>(); + private static List lobbyUsers = new ArrayList<>(); + private static final ScheduledExecutorService UPDATE_LOBBY_EXECUTOR = Executors.newSingleThreadScheduledExecutor( + new XMageThreadFactory(ThreadUtils.THREAD_PREFIX_SERVICE_LOBBY_REFRESH) + ); private final ManagerFactory managerFactory; private final ConcurrentHashMap tables = new ConcurrentHashMap<>(); @@ -44,9 +49,11 @@ public class GamesRoomImpl extends RoomImpl implements GamesRoom, Serializable { public GamesRoomImpl(ManagerFactory managerFactory) { super(managerFactory.chatManager()); this.managerFactory = managerFactory; - UPDATE_EXECUTOR.scheduleAtFixedRate(() -> { + + // update lobby's data + UPDATE_LOBBY_EXECUTOR.scheduleAtFixedRate(() -> { try { - update(); + updateLobby(); } catch (Exception e) { LOGGER.fatal("Games room update error: " + e.getMessage(), e); } @@ -56,10 +63,11 @@ public class GamesRoomImpl extends RoomImpl implements GamesRoom, Serializable { @Override public List getTables() { - return tableView; + return lobbyTables; } - private void update() { + private void updateLobby() { + // tables and matches List allTables = new ArrayList<>(tables.values()); allTables.sort(new TableListSorter()); List matchList = new ArrayList<>(); @@ -77,8 +85,10 @@ public class GamesRoomImpl extends RoomImpl implements GamesRoom, Serializable { this.removeTable(table.getId()); } } - tableView = tableList; - matchView = matchList; + lobbyTables = tableList; + lobbyMatches = matchList; + + // users List users = new ArrayList<>(); for (User user : managerFactory.userManager().getUsers()) { if (user.isOnlineUser()) { @@ -105,7 +115,6 @@ public class GamesRoomImpl extends RoomImpl implements GamesRoom, Serializable { } } } - users.sort((one, two) -> one.getUserName().compareToIgnoreCase(two.getUserName())); List roomUserInfo = new ArrayList<>(); roomUserInfo.add(new RoomUsersView(users, @@ -113,12 +122,12 @@ public class GamesRoomImpl extends RoomImpl implements GamesRoom, Serializable { managerFactory.threadExecutor().getActiveThreads(managerFactory.threadExecutor().getGameExecutor()), managerFactory.configSettings().getMaxGameThreads() )); - roomUsersView = roomUserInfo; + lobbyUsers = roomUserInfo; } @Override public List getFinished() { - return matchView; + return lobbyMatches; } @Override @@ -190,7 +199,7 @@ public class GamesRoomImpl extends RoomImpl implements GamesRoom, Serializable { @Override public List getRoomUsersInfo() { - return roomUsersView; + return lobbyUsers; } } diff --git a/Mage.Server/src/main/java/mage/server/util/ServerMessagesUtil.java b/Mage.Server/src/main/java/mage/server/util/ServerMessagesUtil.java index 73b8e084a6b..317f5841140 100644 --- a/Mage.Server/src/main/java/mage/server/util/ServerMessagesUtil.java +++ b/Mage.Server/src/main/java/mage/server/util/ServerMessagesUtil.java @@ -1,5 +1,7 @@ package mage.server.util; +import mage.util.ThreadUtils; +import mage.util.XMageThreadFactory; import mage.utils.StreamUtils; import org.apache.log4j.Logger; @@ -44,8 +46,10 @@ public enum ServerMessagesUtil { private static final AtomicInteger reconnects = new AtomicInteger(0); ServerMessagesUtil() { - ScheduledExecutorService updateExecutor = Executors.newSingleThreadScheduledExecutor(); - updateExecutor.scheduleAtFixedRate(this::reloadMessages, 5, SERVER_MSG_REFRESH_RATE_SECS, TimeUnit.SECONDS); + ScheduledExecutorService NEWS_MESSAGES_EXECUTOR = Executors.newSingleThreadScheduledExecutor( + new XMageThreadFactory(ThreadUtils.THREAD_PREFIX_SERVICE_NEWS_REFRESH) + ); + NEWS_MESSAGES_EXECUTOR.scheduleAtFixedRate(this::reloadMessages, 5, SERVER_MSG_REFRESH_RATE_SECS, TimeUnit.SECONDS); } public List getMessages() { diff --git a/Mage.Server/src/main/java/mage/server/util/ThreadExecutorImpl.java b/Mage.Server/src/main/java/mage/server/util/ThreadExecutorImpl.java index e5f692afc46..ddffccc083c 100644 --- a/Mage.Server/src/main/java/mage/server/util/ThreadExecutorImpl.java +++ b/Mage.Server/src/main/java/mage/server/util/ThreadExecutorImpl.java @@ -3,6 +3,7 @@ package mage.server.util; import mage.server.managers.ConfigSettings; import mage.server.managers.ThreadExecutor; import mage.util.ThreadUtils; +import mage.util.XMageThreadFactory; import org.apache.log4j.Logger; import java.util.concurrent.*; @@ -146,19 +147,3 @@ public class ThreadExecutorImpl implements ThreadExecutor { } } -class XMageThreadFactory implements ThreadFactory { - - private final String prefix; - - XMageThreadFactory(String prefix) { - this.prefix = prefix; - } - - @Override - public Thread newThread(Runnable r) { - Thread thread = new Thread(r); - // default name, but threads can change it (example: on game or tourney start) - thread.setName(prefix + " " + thread.getThreadGroup().getName() + "-" + thread.getId()); - return thread; - } -} 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 47292fdce93..1f587846341 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 @@ -13,6 +13,8 @@ import mage.remote.MageRemoteException; import mage.remote.Session; import mage.remote.SessionImpl; import mage.util.RandomUtil; +import mage.util.ThreadUtils; +import mage.util.XMageThreadFactory; import mage.view.*; import org.apache.log4j.Logger; import org.junit.Assert; @@ -326,9 +328,9 @@ public class LoadTest { ExecutorService executerService; if (isRunParallel) { - executerService = Executors.newFixedThreadPool(gamesAmount); + executerService = Executors.newFixedThreadPool(gamesAmount, new XMageThreadFactory(ThreadUtils.THREAD_PREFIX_TESTS_AI_VS_AI_GAMES)); } else { - executerService = Executors.newSingleThreadExecutor(); + executerService = Executors.newSingleThreadExecutor(new XMageThreadFactory(ThreadUtils.THREAD_PREFIX_TESTS_AI_VS_AI_GAMES)); } // save random seeds for repeated results (in decks generating) diff --git a/Mage/src/main/java/mage/cards/repository/CardRepository.java b/Mage/src/main/java/mage/cards/repository/CardRepository.java index b1cb5bf3ce2..9bef7dccd62 100644 --- a/Mage/src/main/java/mage/cards/repository/CardRepository.java +++ b/Mage/src/main/java/mage/cards/repository/CardRepository.java @@ -636,7 +636,7 @@ public enum CardRepository { ConnectionSource connectionSource = new JdbcConnectionSource(JDBC_URL); RepositoryUtil.updateVersion(connectionSource, VERSION_ENTITY_NAME + "Content", version); } catch (SQLException e) { - Logger.getLogger(CardRepository.class).error("Error getting content version - " + e, e); + Logger.getLogger(CardRepository.class).error("Error setting content version - " + e, e); processMemoryErrors(e); } } diff --git a/Mage/src/main/java/mage/cards/repository/ExpansionRepository.java b/Mage/src/main/java/mage/cards/repository/ExpansionRepository.java index 23088f04e98..9a671424d28 100644 --- a/Mage/src/main/java/mage/cards/repository/ExpansionRepository.java +++ b/Mage/src/main/java/mage/cards/repository/ExpansionRepository.java @@ -27,7 +27,7 @@ public enum ExpansionRepository { private static final Logger logger = Logger.getLogger(ExpansionRepository.class); - private static final String JDBC_URL = "jdbc:h2:file:./db/cards.h2;AUTO_SERVER=TRUE"; + private static final String JDBC_URL = "jdbc:h2:file:./db/cards.h2;AUTO_SERVER=TRUE;IGNORECASE=TRUE"; private static final String VERSION_ENTITY_NAME = "expansion"; private static final long EXPANSION_DB_VERSION = 5; private static final long EXPANSION_CONTENT_VERSION = 18; @@ -87,7 +87,7 @@ public enum ExpansionRepository { expansionDao.create(exp); } } catch (SQLException ex) { - Logger.getLogger(CardRepository.class).error("Error adding expansions to DB - ", ex); + logger.error("Error adding expansions to DB - ", ex); } } @@ -99,7 +99,7 @@ public enum ExpansionRepository { expansionDao.update(exp); } } catch (SQLException ex) { - Logger.getLogger(CardRepository.class).error("Error adding expansions to DB - ", ex); + logger.error("Error adding expansions to DB - ", ex); } } @@ -230,8 +230,8 @@ public enum ExpansionRepository { try { ConnectionSource connectionSource = new JdbcConnectionSource(JDBC_URL); RepositoryUtil.updateVersion(connectionSource, VERSION_ENTITY_NAME + "Content", version); - } catch (SQLException ex) { - ex.printStackTrace(); + } catch (SQLException e) { + logger.error("Error setting content version - " + e, e); } } diff --git a/Mage/src/main/java/mage/game/draft/DraftImpl.java b/Mage/src/main/java/mage/game/draft/DraftImpl.java index c36a3e4c5e4..f9a08345f53 100644 --- a/Mage/src/main/java/mage/game/draft/DraftImpl.java +++ b/Mage/src/main/java/mage/game/draft/DraftImpl.java @@ -1,6 +1,5 @@ package mage.game.draft; -import java.util.*; import mage.cards.Card; import mage.cards.ExpansionSet; import mage.game.draft.DraftOptions.TimingOption; @@ -8,15 +7,17 @@ import mage.game.events.*; import mage.game.events.TableEvent.EventType; import mage.players.Player; import mage.players.PlayerList; +import mage.util.ThreadUtils; +import mage.util.XMageThreadFactory; import org.apache.log4j.Logger; +import java.util.*; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; /** - * * @author BetaSteward_at_googlemail.com */ public abstract class DraftImpl implements Draft { @@ -41,9 +42,9 @@ public abstract class DraftImpl implements Draft { protected transient TableEventSource tableEventSource = new TableEventSource(); protected transient PlayerQueryEventSource playerQueryEventSource = new PlayerQueryEventSource(); - + protected ScheduledFuture boosterLoadingHandle; - protected final ScheduledExecutorService boosterLoadingExecutor = Executors.newSingleThreadScheduledExecutor(); + protected ScheduledExecutorService boosterLoadingExecutor = null; public DraftImpl(DraftOptions options, List sets) { id = UUID.randomUUID(); @@ -240,13 +241,20 @@ public abstract class DraftImpl implements Draft { cardNum++; return true; } - + protected void setupBoosterLoadingHandle() { cancelBoosterLoadingHandle(); boosterLoadingCounter = 0; + + if (this.boosterLoadingExecutor == null) { + this.boosterLoadingExecutor = Executors.newSingleThreadScheduledExecutor( + new XMageThreadFactory(ThreadUtils.THREAD_PREFIX_TOURNEY_BOOSTERS_SEND) + ); + } + boosterLoadingHandle = boosterLoadingExecutor.scheduleAtFixedRate(() -> { try { - if (loadBoosters() == true) { + if (loadBoosters()) { cancelBoosterLoadingHandle(); } else { boosterLoadingCounter++; @@ -256,14 +264,14 @@ public abstract class DraftImpl implements Draft { } }, 0, BOOSTER_LOADING_INTERVAL, TimeUnit.SECONDS); } - + protected void cancelBoosterLoadingHandle() { if (boosterLoadingHandle != null) { boosterLoadingHandle.cancel(true); } } - - protected boolean loadBoosters () { + + protected boolean loadBoosters() { boolean allBoostersLoaded = true; for (DraftPlayer player : players.values()) { if (player.isPicking() && !player.isBoosterLoaded()) { @@ -273,7 +281,7 @@ public abstract class DraftImpl implements Draft { } return allBoostersLoaded; } - + protected boolean donePicking() { if (isAbort()) { return true; @@ -345,7 +353,7 @@ public abstract class DraftImpl implements Draft { } return !player.isPicking(); } - + @Override public void setBoosterLoaded(UUID playerId) { DraftPlayer player = players.get(playerId); diff --git a/Mage/src/main/java/mage/util/ThreadUtils.java b/Mage/src/main/java/mage/util/ThreadUtils.java index b0c0560c8fa..42afe91bbb5 100644 --- a/Mage/src/main/java/mage/util/ThreadUtils.java +++ b/Mage/src/main/java/mage/util/ThreadUtils.java @@ -8,7 +8,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; /** - * Util method to work with threads. + * Helper class to work with threads * * @author ayrat, JayDi85 */ @@ -16,20 +16,37 @@ public final class ThreadUtils { // basic public final static String THREAD_PREFIX_GAME = "GAME"; - public final static String THREAD_PREFIX_AI_SIMULATION = "AI-SIM"; + public final static String THREAD_PREFIX_AI_SIMULATION_MAD = "AI-SIM-MAD"; + public final static String THREAD_PREFIX_AI_SIMULATION_MCTS = "AI-SIM-MCTS"; public final static String THREAD_PREFIX_CALL_REQUEST = "CALL"; public final static String THREAD_PREFIX_TOURNEY = "TOURNEY"; public final static String THREAD_PREFIX_TOURNEY_DRAFT = "TOURNEY DRAFT"; + public final static String THREAD_PREFIX_TOURNEY_BOOSTERS_SEND = "TOURNEY BOOSTERS SEND"; + + // game + public final static String THREAD_PREFIX_GAME_JOIN_WAITING = "XMAGE game join waiting"; // services - public final static String THREAD_PREFIX_SERVICE_HEALTH = "XMAGE HEALTH"; + public final static String THREAD_PREFIX_SERVICE_HEALTH = "XMAGE service health"; + public final static String THREAD_PREFIX_SERVICE_USERS_LIST_REFRESH = "XMAGE users list refresh"; + public final static String THREAD_PREFIX_SERVICE_CONNECTION_EXPIRED_CHECK = "XMAGE connection expired check"; + public final static String THREAD_PREFIX_SERVICE_LOBBY_REFRESH = "XMAGE lobby refresh"; + public final static String THREAD_PREFIX_SERVICE_NEWS_REFRESH = "XMAGE news refresh"; // etc - public final static String THREAD_PREFIX_TIMEOUT = "XMAGE TIMEOUT"; - public final static String THREAD_PREFIX_TIMEOUT_IDLE = "XMAGE TIMEOUT_IDLE"; - + public final static String THREAD_PREFIX_TIMEOUT = "XMAGE timeout"; + public final static String THREAD_PREFIX_TIMEOUT_IDLE = "XMAGE timeout_idle"; + // client + // TODO: replace single GUI tasks by swing thread (invoke later) or by single executor like (like CALL for server side) + public final static String THREAD_PREFIX_CLIENT_SYMBOLS_DOWNLOADER = "XMAGE symbols downloader"; + public final static String THREAD_PREFIX_CLIENT_IMAGES_DOWNLOADER = "XMAGE images downloader"; + public final static String THREAD_PREFIX_CLIENT_PING_SENDER = "XMAGE ping sender"; + public final static String THREAD_PREFIX_CLIENT_SUBMIT_TIMER = "XMAGE submit timer"; + public final static String THREAD_PREFIX_CLIENT_AUTO_CLOSE_TIMER = "XMAGE auto-close timer"; + // tests + public final static String THREAD_PREFIX_TESTS_AI_VS_AI_GAMES = "XMAGE tests ai vs ai"; public static void sleep(int millis) { try { @@ -88,7 +105,7 @@ public final class ThreadUtils { if (name.startsWith(THREAD_PREFIX_GAME)) { // server game return true; - } else if (name.startsWith(THREAD_PREFIX_AI_SIMULATION)) { + } else if (name.startsWith(THREAD_PREFIX_AI_SIMULATION_MAD)) { // ai simulation return true; } else if (name.equals("main")) { diff --git a/Mage/src/main/java/mage/util/XMageThreadFactory.java b/Mage/src/main/java/mage/util/XMageThreadFactory.java new file mode 100644 index 00000000000..be96e40fc9a --- /dev/null +++ b/Mage/src/main/java/mage/util/XMageThreadFactory.java @@ -0,0 +1,42 @@ +package mage.util; + +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Helper class to give debug friendly names for a threads + * + * @author JayDi85 + */ +public class XMageThreadFactory implements ThreadFactory { + + private final String prefix; + private final AtomicInteger counter = new AtomicInteger(); + private final boolean isDaemon; + + public XMageThreadFactory(String prefix) { + this(prefix, true); + } + + /** + * @param prefix thread's starting name (can be changed by thread itself later) + * @param isDaemon mark thread as daemon on non-writeable tasks (e.g. can be terminated at any time without data loss) + */ + public XMageThreadFactory(String prefix, boolean isDaemon) { + this.prefix = prefix; + this.isDaemon = isDaemon; + } + + @Override + public Thread newThread(Runnable r) { + int instanceNumber = this.counter.incrementAndGet(); + + Thread thread = new Thread(r); + thread.setDaemon(this.isDaemon); + + // gives default name, but threads can change it by Thread.currentThread().setName (example: on game or tourney start) + thread.setName(String.format("%s - %d", this.prefix, instanceNumber)); + + return thread; + } +}