Server improves:

* improved /fix command to support disconnected/freezed players;
 * added chat messages about connection problem with opponents (checks players status every minute);
 * reduced timeout before remove disconnected player from waiting game dialog (from 4 to 2 minutes);
 * reduced timeout between client's pings (from 60 secs to 20 secs);
This commit is contained in:
Oleg Agafonov 2019-12-31 02:57:37 +04:00
parent 174f38d589
commit 335f046357
7 changed files with 60 additions and 16 deletions

View file

@ -26,6 +26,7 @@ import mage.client.plugins.impl.Plugins;
import mage.client.preference.MagePreferences; import mage.client.preference.MagePreferences;
import mage.client.remote.CallbackClientImpl; import mage.client.remote.CallbackClientImpl;
import mage.client.table.TablesPane; import mage.client.table.TablesPane;
import mage.client.table.TablesPanel;
import mage.client.tournament.TournamentPane; import mage.client.tournament.TournamentPane;
import mage.client.util.*; import mage.client.util.*;
import mage.client.util.audio.MusicPlayer; import mage.client.util.audio.MusicPlayer;
@ -261,7 +262,7 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
desktopPane.add(errorDialog, JLayeredPane.MODAL_LAYER); desktopPane.add(errorDialog, JLayeredPane.MODAL_LAYER);
UI.addComponent(MageComponents.DESKTOP_PANE, desktopPane); UI.addComponent(MageComponents.DESKTOP_PANE, desktopPane);
PING_TASK_EXECUTOR.scheduleAtFixedRate(() -> SessionHandler.ping(), 60, 60, TimeUnit.SECONDS); PING_TASK_EXECUTOR.scheduleAtFixedRate(() -> SessionHandler.ping(), TablesPanel.PING_SERVER_SECS, TablesPanel.PING_SERVER_SECS, TimeUnit.SECONDS);
updateMemUsageTask = new UpdateMemUsageTask(jMemUsageLabel); updateMemUsageTask = new UpdateMemUsageTask(jMemUsageLabel);

View file

@ -64,6 +64,9 @@ public class TablesPanel extends javax.swing.JPanel {
private static final Logger LOGGER = Logger.getLogger(TablesPanel.class); 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}; 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)
public static final int PING_SERVER_SECS = 20;
// refresh timeouts for data downloads from server // refresh timeouts for data downloads from server
public static final int REFRESH_ACTIVE_TABLES_SECS = 5; public static final int REFRESH_ACTIVE_TABLES_SECS = 5;
public static final int REFRESH_FINISHED_TABLES_SECS = 30; public static final int REFRESH_FINISHED_TABLES_SECS = 30;

View file

@ -67,7 +67,7 @@ public class ConsoleFrame extends javax.swing.JFrame implements MageClient {
logger.fatal("", ex); logger.fatal("", ex);
} }
pingTaskExecutor.scheduleAtFixedRate(() -> session.ping(), 60, 60, TimeUnit.SECONDS); pingTaskExecutor.scheduleAtFixedRate(() -> session.ping(), 20, 20, TimeUnit.SECONDS);
} }
public boolean connect(Connection connection) { public boolean connect(Connection connection) {

View file

@ -5,6 +5,7 @@ import mage.cards.repository.CardRepository;
import mage.server.exceptions.UserNotFoundException; import mage.server.exceptions.UserNotFoundException;
import mage.server.game.GameController; import mage.server.game.GameController;
import mage.server.game.GameManager; import mage.server.game.GameManager;
import mage.server.game.GamesRoomManager;
import mage.server.util.SystemUtil; import mage.server.util.SystemUtil;
import mage.view.ChatMessage.MessageColor; import mage.view.ChatMessage.MessageColor;
import mage.view.ChatMessage.MessageType; import mage.view.ChatMessage.MessageType;
@ -316,12 +317,16 @@ public enum ChatManager {
} }
public void sendLostConnectionMessage(UUID userId, DisconnectReason reason) { public void sendLostConnectionMessage(UUID userId, DisconnectReason reason) {
UserManager.instance.getUser(userId).ifPresent(user -> sendMessageToUserChats(userId, user.getName() + " " + reason.getMessage()));
}
public void sendMessageToUserChats(UUID userId, String message) {
UserManager.instance.getUser(userId).ifPresent(user UserManager.instance.getUser(userId).ifPresent(user
-> getChatSessions() -> getChatSessions()
.stream() .stream()
.filter(chat -> !chat.getChatId().equals(GamesRoomManager.instance.getMainChatId())) // ignore main lobby
.filter(chat -> chat.hasUser(userId)) .filter(chat -> chat.hasUser(userId))
.forEach(chatSession -> chatSession.broadcast(null, user.getName() + reason.getMessage(), MessageColor.BLUE, true, MessageType.STATUS, null))); .forEach(chatSession -> chatSession.broadcast(null, message, MessageColor.BLUE, true, MessageType.STATUS, null)));
} }
public void removeUser(UUID userId, DisconnectReason reason) { public void removeUser(UUID userId, DisconnectReason reason) {

View file

@ -22,6 +22,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
public enum UserManager { public enum UserManager {
instance; instance;
private static final int SERVER_TIMEOUTS_USER_INFORM_OPPONENTS_ABOUT_DISCONNECT_AFTER_SECS = 30; // send to chat info about disconnection troubles, must be more than ping timeout
private static final int SERVER_TIMEOUTS_USER_DISCONNECT_FROM_SERVER_AFTER_SECS = 3 * 60; // removes from all games and chats too (can be seen in users list with disconnected status) private static final int SERVER_TIMEOUTS_USER_DISCONNECT_FROM_SERVER_AFTER_SECS = 3 * 60; // removes from all games and chats too (can be seen in users list with disconnected status)
private static final int SERVER_TIMEOUTS_USER_REMOVE_FROM_SERVER_AFTER_SECS = 8 * 60; // removes from users list private static final int SERVER_TIMEOUTS_USER_REMOVE_FROM_SERVER_AFTER_SECS = 8 * 60; // removes from users list
@ -145,6 +146,22 @@ public enum UserManager {
} }
} }
public void informUserOpponents(final UUID userId, final String message) {
if (userId != null) {
getUser(userId).ifPresent(user
-> USER_EXECUTOR.execute(
() -> {
try {
logger.info("INFORM OPPONENTS by " + user.getName() + ": " + message);
ChatManager.instance.sendMessageToUserChats(user.getId(), message);
} catch (Exception ex) {
handleException(ex);
}
}
));
}
}
public boolean extendUserSession(UUID userId, String pingInfo) { public boolean extendUserSession(UUID userId, String pingInfo) {
if (userId != null) { if (userId != null) {
User user = users.get(userId); User user = users.get(userId);
@ -163,6 +180,8 @@ public enum UserManager {
*/ */
private void checkExpired() { private void checkExpired() {
try { try {
Calendar calendarInform = Calendar.getInstance();
calendarInform.add(Calendar.SECOND, -1 * SERVER_TIMEOUTS_USER_INFORM_OPPONENTS_ABOUT_DISCONNECT_AFTER_SECS);
Calendar calendarExp = Calendar.getInstance(); Calendar calendarExp = Calendar.getInstance();
calendarExp.add(Calendar.SECOND, -1 * SERVER_TIMEOUTS_USER_DISCONNECT_FROM_SERVER_AFTER_SECS); calendarExp.add(Calendar.SECOND, -1 * SERVER_TIMEOUTS_USER_DISCONNECT_FROM_SERVER_AFTER_SECS);
Calendar calendarRemove = Calendar.getInstance(); Calendar calendarRemove = Calendar.getInstance();
@ -179,6 +198,12 @@ public enum UserManager {
} }
for (User user : userList) { for (User user : userList) {
try { try {
if (user.getUserState() != UserState.Offline
&& user.isExpired(calendarInform.getTime())) {
long secsInfo = (Calendar.getInstance().getTimeInMillis() - user.getLastActivity().getTime()) / 1000;
informUserOpponents(user.getId(), user.getName() + " got connection problem for " + secsInfo + " secs");
}
if (user.getUserState() == UserState.Offline) { if (user.getUserState() == UserState.Offline) {
if (user.isExpired(calendarRemove.getTime())) { if (user.isExpired(calendarRemove.getTime())) {
// removes from users list // removes from users list

View file

@ -50,7 +50,7 @@ import java.util.zip.GZIPOutputStream;
public class GameController implements GameCallback { public class GameController implements GameCallback {
private static final int GAME_TIMEOUTS_CHECK_JOINING_STATUS_EVERY_SECS = 15; // checks and inform players about joining status private static final int GAME_TIMEOUTS_CHECK_JOINING_STATUS_EVERY_SECS = 15; // checks and inform players about joining status
private static final int GAME_TIMEOUTS_CANCEL_PLAYER_GAME_JOINING_AFTER_INACTIVE_SECS = 4 * 60; // leave player from game if it don't join and inactive on server private static final int GAME_TIMEOUTS_CANCEL_PLAYER_GAME_JOINING_AFTER_INACTIVE_SECS = 2 * 60; // leave player from game if it don't join and inactive on server
private static final ExecutorService gameExecutor = ThreadExecutor.instance.getGameExecutor(); private static final ExecutorService gameExecutor = ThreadExecutor.instance.getGameExecutor();
private static final Logger logger = Logger.getLogger(GameController.class); private static final Logger logger = Logger.getLogger(GameController.class);
@ -336,7 +336,10 @@ public class GameController implements GameCallback {
GameManager.instance.joinGame(game.getId(), user.getId()); GameManager.instance.joinGame(game.getId(), user.getId());
logger.debug("Player " + player.getName() + " (disconnected) has joined gameId: " + game.getId()); logger.debug("Player " + player.getName() + " (disconnected) has joined gameId: " + game.getId());
} }
ChatManager.instance.broadcast(chatId, player.getName(), user.getPingInfo() + " is pending to join the game", MessageColor.BLUE, true, ChatMessage.MessageType.STATUS, null); ChatManager.instance.broadcast(chatId, player.getName(), user.getPingInfo()
+ " is pending to join the game (waiting " + user.getSecondsDisconnected() + " of "
+ GAME_TIMEOUTS_CANCEL_PLAYER_GAME_JOINING_AFTER_INACTIVE_SECS + " secs)",
MessageColor.BLUE, true, ChatMessage.MessageType.STATUS, null);
if (user.getSecondsDisconnected() > GAME_TIMEOUTS_CANCEL_PLAYER_GAME_JOINING_AFTER_INACTIVE_SECS) { if (user.getSecondsDisconnected() > GAME_TIMEOUTS_CANCEL_PLAYER_GAME_JOINING_AFTER_INACTIVE_SECS) {
// TODO: 2019.04.22 - if user playing another game on server but not joining (that's the reason?), then that's check will never trigger // TODO: 2019.04.22 - if user playing another game on server but not joining (that's the reason?), then that's check will never trigger
// Cancel player join possibility lately after 4 minutes // Cancel player join possibility lately after 4 minutes
@ -1201,6 +1204,7 @@ public class GameController implements GameCallback {
if (activePlayer != null && activePlayer.hasLeft()) { if (activePlayer != null && activePlayer.hasLeft()) {
sb.append("<br>Found disconnected player! Concede..."); sb.append("<br>Found disconnected player! Concede...");
activePlayer.concede(game); activePlayer.concede(game);
activePlayer.leave(); // abort any wait response actions
Phase currentPhase = game.getPhase(); Phase currentPhase = game.getPhase();
if (currentPhase != null) { if (currentPhase != null) {
@ -1221,6 +1225,7 @@ public class GameController implements GameCallback {
Player p = game.getPlayer(state.getChoosingPlayerId()); Player p = game.getPlayer(state.getChoosingPlayerId());
if (p != null) { if (p != null) {
p.concede(game); p.concede(game);
p.leave(); // abort any wait response actions
} }
Phase currentPhase = game.getPhase(); Phase currentPhase = game.getPhase();
if (currentPhase != null && !fixedAlready) { if (currentPhase != null && !fixedAlready) {
@ -1235,14 +1240,14 @@ public class GameController implements GameCallback {
} }
// fix lost priority // fix lost priority
Player p = game.getPlayer(state.getPriorityPlayerId());
sb.append("<br>Checking priority player: " + getName(game.getPlayer(state.getPriorityPlayerId()))); sb.append("<br>Checking priority player: " + getName(game.getPlayer(state.getPriorityPlayerId())));
if (state.getPriorityPlayerId() != null) { if (p != null) {
if (game.getPlayer(state.getPriorityPlayerId()).hasLeft()) { if (p.hasLeft()) {
sb.append("<br>Found disconnected player! Concede..."); sb.append("<br>Found disconnected player! Concede...");
Player p = game.getPlayer(state.getPriorityPlayerId()); p.concede(game);
if (p != null) { p.leave(); // abort any wait response actions
p.concede(game);
}
Phase currentPhase = game.getPhase(); Phase currentPhase = game.getPhase();
if (currentPhase != null && !fixedAlready) { if (currentPhase != null && !fixedAlready) {
currentPhase.getStep().skipStep(game, state.getActivePlayerId()); currentPhase.getStep().skipStep(game, state.getActivePlayerId());
@ -1250,7 +1255,6 @@ public class GameController implements GameCallback {
sb.append("<br>Forcibly passing the phase!"); sb.append("<br>Forcibly passing the phase!");
} }
} }
sb.append(game.getPlayer(state.getPriorityPlayerId()).getName());
sb.append("</font>"); sb.append("</font>");
} }
@ -1272,6 +1276,8 @@ public class GameController implements GameCallback {
sb.append("Not using future Timeout!"); sb.append("Not using future Timeout!");
} }
sb.append("</font>"); sb.append("</font>");
return sb.toString(); return sb.toString();
} }
} }

View file

@ -1,5 +1,3 @@
package mage.server.game; package mage.server.game;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
@ -16,12 +14,14 @@ public enum GamesRoomManager {
private final ConcurrentHashMap<UUID, GamesRoom> rooms = new ConcurrentHashMap<>(); private final ConcurrentHashMap<UUID, GamesRoom> rooms = new ConcurrentHashMap<>();
private final UUID mainRoomId; private final UUID mainRoomId;
private final UUID mainChatId;
private static final Logger logger = Logger.getLogger(GamesRoomManager.class); private static final Logger logger = Logger.getLogger(GamesRoomManager.class);
GamesRoomManager() { GamesRoomManager() {
GamesRoom mainRoom = new GamesRoomImpl(); GamesRoom mainRoom = new GamesRoomImpl();
mainRoomId = mainRoom.getRoomId(); mainRoomId = mainRoom.getRoomId();
mainChatId = mainRoom.getChatId();
rooms.put(mainRoomId, mainRoom); rooms.put(mainRoomId, mainRoom);
} }
@ -35,8 +35,12 @@ public enum GamesRoomManager {
return mainRoomId; return mainRoomId;
} }
public UUID getMainChatId() {
return mainChatId;
}
public Optional<GamesRoom> getRoom(UUID roomId) { public Optional<GamesRoom> getRoom(UUID roomId) {
if(rooms.containsKey(roomId)) { if (rooms.containsKey(roomId)) {
return Optional.of(rooms.get(roomId)); return Optional.of(rooms.get(roomId));
} }
logger.error("room not found : " + roomId); logger.error("room not found : " + roomId);