diff --git a/Mage.Client/src/main/java/mage/client/table/TablesPanel.java b/Mage.Client/src/main/java/mage/client/table/TablesPanel.java index 957fc9b991a..d94baacf55f 100644 --- a/Mage.Client/src/main/java/mage/client/table/TablesPanel.java +++ b/Mage.Client/src/main/java/mage/client/table/TablesPanel.java @@ -66,7 +66,7 @@ public class TablesPanel extends javax.swing.JPanel { private static final Logger LOGGER = Logger.getLogger(TablesPanel.class); private static final int[] DEFAULT_COLUMNS_WIDTH = {35, 150, 100, 50, 120, 180, 80, 120, 80, 60, 40, 40, 60}; - // ping timeout (warning, must be less than SERVER_TIMEOUTS_USER_INFORM_OPPONENTS_ABOUT_DISCONNECT_AFTER_SECS) + // ping timeout (warning, must be less than UserManagerImpl.USER_CONNECTION_TIMEOUTS_CHECK_SECS) public static final int PING_SERVER_SECS = 20; // refresh timeouts for data downloads from server diff --git a/Mage.Common/src/main/java/mage/interfaces/callback/ClientCallback.java b/Mage.Common/src/main/java/mage/interfaces/callback/ClientCallback.java index 6988a14f220..1155facff78 100644 --- a/Mage.Common/src/main/java/mage/interfaces/callback/ClientCallback.java +++ b/Mage.Common/src/main/java/mage/interfaces/callback/ClientCallback.java @@ -14,8 +14,8 @@ import java.util.UUID; */ public class ClientCallback implements Serializable { - // for debug only: simulate bad connection on client side, use launcher's client param like -Dxmage.badconnection - private static final String SIMULATE_BAD_CONNECTION_PROP = "xmage.badconnection"; + // for debug only: simulate bad connection on client side, use launcher's client param like -Dxmage.badConnection + private static final String SIMULATE_BAD_CONNECTION_PROP = "xmage.badConnection"; public static final boolean SIMULATE_BAD_CONNECTION; static { diff --git a/Mage.Common/src/main/java/mage/remote/SessionImpl.java b/Mage.Common/src/main/java/mage/remote/SessionImpl.java index 1a777ed2355..963c12a4600 100644 --- a/Mage.Common/src/main/java/mage/remote/SessionImpl.java +++ b/Mage.Common/src/main/java/mage/remote/SessionImpl.java @@ -42,6 +42,11 @@ public class SessionImpl implements Session { private static final Logger logger = Logger.getLogger(SessionImpl.class); + // connection validation on client side (jboss's ping implementation, depends on server config's leasePeriod) + // client's validation ping must be less than server's leasePeriod + private static final int SESSION_VALIDATOR_PING_PERIOD_SECS = 4; + private static final int SESSION_VALIDATOR_PING_TIMEOUT_SECS = 3; + public static final String ADMIN_NAME = "Admin"; // if you change here then change in User too public static final String KEEP_MY_OLD_SESSION = "keep_my_old_session"; // for disconnects without active session lose (keep tables/games) @@ -65,16 +70,11 @@ public class SessionImpl implements Session { private static final int PING_CYCLES = 10; private final LinkedList pingTime = new LinkedList<>(); private String lastPingInfo = ""; - private static boolean debugMode = false; private boolean canceled = false; private boolean jsonLogActive = false; private String lastError = ""; - static { - debugMode = System.getProperty("debug.mage") != null; - } - public SessionImpl(MageClient client) { this.client = client; } @@ -381,7 +381,7 @@ public class SessionImpl implements Session { clientMetadata.put("generalizeSocketException", "true"); /* A remoting server also has the capability to detect when a client is no longer available. - * This is done by estabilishing a lease with the remoting clients that connect to a server. + * This is done by establishing a lease with the remoting clients that connect to a server. * On the client side, an org.jboss.remoting.LeasePinger periodically sends PING messages to * the server, and on the server side an org.jboss.remoting.Lease informs registered listeners * if the PING doesn't arrive withing the specified timeout period. */ @@ -437,15 +437,10 @@ public class SessionImpl implements Session { clientMetadata.put(Remoting.USE_CLIENT_CONNECTION_IDENTITY, "true"); callbackClient = new Client(clientLocator, "callback", clientMetadata); + // client side connection validator (jboss's implementation of ping) Map listenerMetadata = new HashMap<>(); - if (debugMode) { - // prevent client from disconnecting while debugging - listenerMetadata.put(ConnectionValidator.VALIDATOR_PING_PERIOD, "1000000"); - listenerMetadata.put(ConnectionValidator.VALIDATOR_PING_TIMEOUT, "900000"); - } else { - listenerMetadata.put(ConnectionValidator.VALIDATOR_PING_PERIOD, "15000"); - listenerMetadata.put(ConnectionValidator.VALIDATOR_PING_TIMEOUT, "13000"); - } + listenerMetadata.put(ConnectionValidator.VALIDATOR_PING_PERIOD, String.valueOf(SESSION_VALIDATOR_PING_PERIOD_SECS * 1000)); + listenerMetadata.put(ConnectionValidator.VALIDATOR_PING_TIMEOUT, String.valueOf(SESSION_VALIDATOR_PING_TIMEOUT_SECS * 1000)); callbackClient.connect(new MageClientConnectionListener(), listenerMetadata); Map callbackMetadata = new HashMap<>(); @@ -589,13 +584,13 @@ public class SessionImpl implements Session { @Override public synchronized boolean sendFeedback(String title, String type, String message, String email) { - if (isConnected()) { - try { + try { + if (isConnected()) { server.serverAddFeedbackMessage(sessionId, connection.getUsername(), title, type, message, email); return true; - } catch (MageException e) { - logger.error(e); } + } catch (MageException ex) { + handleMageException(ex); } return false; } @@ -1706,10 +1701,12 @@ public class SessionImpl implements Session { @Override public void ping() { try { + // client side connection validator (xmage's implementation) + // // jboss uses lease mechanic for connection check but xmage needs additional data like pings stats // ping must work after login only, all other actions are single call (example: register new user) - // sessionId fills on connection - // serverState fills on good login + // - sessionId fills on connection + // - serverState fills on good login if (!isConnected() || sessionId == null || serverState == null) { return; } diff --git a/Mage.Server/src/main/java/mage/server/Main.java b/Mage.Server/src/main/java/mage/server/Main.java index d23a7b438e2..9503aacb383 100644 --- a/Mage.Server/src/main/java/mage/server/Main.java +++ b/Mage.Server/src/main/java/mage/server/Main.java @@ -54,13 +54,13 @@ public final class Main { private static final MageVersion version = new MageVersion(Main.class); // Server threads: - // - worker threads: creates for each connection, controls by maxPoolSize; - // - acceptor threads: processing requests to start a new connection, controls by numAcceptThreads; - // - backlog threads: processing waiting queue if maxPoolSize reached, controls by backlogSize; + // - acceptor threads: processing new connection from a client, controlled by numAcceptThreads; + // - worker threads (server threads): processing income requests from a client, controlled by maxPoolSize; + // - backlog: max size of income queue if maxPoolSize reached, controlled by backlogSize; // Usage hints: // - if maxPoolSize reached then new clients will freeze in connection dialog until backlog queue overflow; - // - so for active server must increase maxPoolSize to big value like "max online * 10" or enable worker idle timeout - // - worker idle time will free unused worker thread, so new client can connect; + // - so for active server must increase maxPoolSize to bigger value like "max online * 20" + // - worker idle timeout will free unused worker thread, so new client can connect again; private static final int SERVER_WORKER_THREAD_IDLE_TIMEOUT_SECS = 5 * 60; // no needs to config, must be enabled for all // arg settings can be setup by run script or IDE's program arguments like -xxx=yyy @@ -85,6 +85,7 @@ public final class Main { // - fast game buttons; // - cheat commands; // - no deck validation; + // - no connection validation by pings (no disconnects on IDE's debugger usage) // - load any deck in sideboarding; // - simplified registration and login (no password check); // - debug main menu for GUI and rendering testing (must use -debug arg for client app); @@ -342,6 +343,8 @@ public final class Main { @Override public void handleConnectionException(Throwable throwable, Client client) { + // called on client disconnect or on failed network (depends on server config's leasePeriod) + String sessionId = client.getSessionId(); Session session = managerFactory.sessionManager().getSession(sessionId).orElse(null); if (session == null) { @@ -357,7 +360,7 @@ public final class Main { } else { sessionInfo.append("[no user]"); } - sessionInfo.append(" at ").append(session.getHost()).append(", sessionId: ").append(session.getId()); + sessionInfo.append(" at ").append(session.getHost()); // check disconnection reason // lease ping is inner jboss feature to check connection status @@ -371,13 +374,13 @@ public final class Main { } else if (throwable == null) { // lease timeout (ping), so server lost connection with a client // must keep tables - logger.info("LOST CONNECTION - " + sessionInfo); + logger.info("LOST CONNECTION (bad network) - " + sessionInfo); logger.debug("- cause: lease expired"); managerFactory.sessionManager().disconnect(client.getSessionId(), DisconnectReason.LostConnection, true); } else { // unknown error // must keep tables - logger.info("LOST CONNECTION - " + sessionInfo); + logger.info("LOST CONNECTION (unknown) - " + sessionInfo); logger.debug("- cause: unknown error - " + throwable); managerFactory.sessionManager().disconnect(client.getSessionId(), DisconnectReason.LostConnection, true); } @@ -396,7 +399,8 @@ public final class Main { connector.addInvocationHandler("callback", serverInvocationHandler); // commands processing // connection monitoring and errors processing - connector.setLeasePeriod(managerFactory.configSettings().getLeasePeriod()); + boolean isTestMode = ((MageServerImpl) target).getServerState().isTestMode(); + connector.setLeasePeriod(isTestMode ? 3600 * 1000 : managerFactory.configSettings().getLeasePeriod()); connector.addConnectionListener(new MageServerConnectionListener(managerFactory)); } diff --git a/Mage.Server/src/main/java/mage/server/SessionManagerImpl.java b/Mage.Server/src/main/java/mage/server/SessionManagerImpl.java index a02bc605e63..a3fe09ef6b6 100644 --- a/Mage.Server/src/main/java/mage/server/SessionManagerImpl.java +++ b/Mage.Server/src/main/java/mage/server/SessionManagerImpl.java @@ -4,6 +4,7 @@ import mage.MageException; import mage.players.net.UserData; import mage.server.managers.ManagerFactory; import mage.server.managers.SessionManager; +import mage.util.ThreadUtils; import org.apache.log4j.Logger; import org.jboss.remoting.callback.InvokerCallbackHandler; @@ -63,8 +64,7 @@ public class SessionManagerImpl implements SessionManager { if (session != null) { String errorMessage = session.connectUser(userName, password, restoreSessionId); if (errorMessage == null) { - logger.info(userName + " connected to server by sessionId " + sessionId - + (restoreSessionId.isEmpty() ? "" : ", restoreSessionId " + restoreSessionId)); + logger.info(userName + " connected to server" + (restoreSessionId.isEmpty() ? "" : " with restored session")); if (detailsMode) { logger.info("- details: " + userInfo); } @@ -153,6 +153,8 @@ public class SessionManagerImpl implements SessionManager { } user.showUserMessage("Admin action", "Your session was disconnected by admin"); + ThreadUtils.sleep(1000); + logger.warn(user.getName() + " disconnected by admin"); disconnect(userSessionId, DisconnectReason.DisconnectedByAdmin, true); admin.showUserMessage("Admin result", "User " + user.getName() + " was disconnected"); } diff --git a/Mage.Server/src/main/java/mage/server/UserManagerImpl.java b/Mage.Server/src/main/java/mage/server/UserManagerImpl.java index 571142a9b8d..82c1ab5ad0e 100644 --- a/Mage.Server/src/main/java/mage/server/UserManagerImpl.java +++ b/Mage.Server/src/main/java/mage/server/UserManagerImpl.java @@ -234,6 +234,7 @@ public class UserManagerImpl implements UserManager { } if (isBadSession) { // full disconnect + logger.info(user.getName() + " disconnected due connection problems"); disconnect(user.getId(), DisconnectReason.SessionExpired); } break; 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 a8bb71ae863..a13df85c70d 100644 --- a/Mage.Server/src/main/java/mage/server/util/ServerMessagesUtil.java +++ b/Mage.Server/src/main/java/mage/server/util/ServerMessagesUtil.java @@ -127,7 +127,7 @@ public enum ServerMessagesUtil { private String getServerStatsMessage() { long current = System.currentTimeMillis(); - long hours = ((current - startDate) / (1000 * 60 * 60)); + long hours = startDate <= 0 ? 0 : ((current - startDate) / (1000 * 60 * 60)); String updated = new Date().toInstant().atOffset(ZoneOffset.UTC).toLocalDateTime().format(DateTimeFormatter.ofPattern("HH:mm:ss")); return String.format("Server uptime: %d hours; max online: %d; active games: %d of %d, tourneys: %d of %d; stats from %s", hours,