forked from External/mage
* Match handling - some changes to solve/workaround the draw games that cause matches not to end. Changed disconnect handling to not block the client (needs tests if works correctly).
This commit is contained in:
parent
8567c1cd38
commit
3c12b23928
9 changed files with 100 additions and 58 deletions
|
|
@ -521,12 +521,15 @@ public class TableController {
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
if (!user.isConnected()) {
|
if (!user.isConnected()) {
|
||||||
// if the user is not connected but exits, the user is currently disconnected. So it's neccessary
|
// if the user is not connected but exits, the user is currently disconnected. So it's neccessary
|
||||||
// to join the user to the game here, so he can join the game, if he reconnects in time.
|
// to join the user to the game here (instead the client does it) , so he can join the game, if he reconnects in time.
|
||||||
// remove an existing constructing for the player if it exists
|
// remove an existing constructing for the player if it exists
|
||||||
user.removeConstructing(match.getPlayer(entry.getValue()).getPlayer().getId());
|
user.removeConstructing(match.getPlayer(entry.getValue()).getPlayer().getId());
|
||||||
GameManager.getInstance().joinGame(match.getGame().getId(), user.getId());
|
GameManager.getInstance().joinGame(match.getGame().getId(), user.getId());
|
||||||
|
logger.debug("Joined currently not connected user " + user.getName() + " matchId: " + match.getId());
|
||||||
|
} else {
|
||||||
|
user.gameStarted(match.getGame().getId(), entry.getValue());
|
||||||
}
|
}
|
||||||
user.gameStarted(match.getGame().getId(), entry.getValue());
|
|
||||||
if (creator == null) {
|
if (creator == null) {
|
||||||
creator = user.getName();
|
creator = user.getName();
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -543,6 +546,9 @@ public class TableController {
|
||||||
matchPlayer.setQuit(true);
|
matchPlayer.setQuit(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Match player has already quit
|
||||||
|
throw new MageException("Can't start game - user already quit userId " + entry.getKey());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Append AI opponents to the log file
|
// Append AI opponents to the log file
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ import mage.MageException;
|
||||||
import mage.cards.decks.Deck;
|
import mage.cards.decks.Deck;
|
||||||
import mage.cards.decks.DeckCardLists;
|
import mage.cards.decks.DeckCardLists;
|
||||||
import mage.constants.TableState;
|
import mage.constants.TableState;
|
||||||
|
import mage.game.Game;
|
||||||
import mage.game.GameException;
|
import mage.game.GameException;
|
||||||
import mage.game.Table;
|
import mage.game.Table;
|
||||||
import mage.game.draft.Draft;
|
import mage.game.draft.Draft;
|
||||||
|
|
@ -55,6 +56,7 @@ import mage.players.Player;
|
||||||
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.game.GamesRoomManager;
|
||||||
|
import mage.server.util.ThreadExecutor;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -65,6 +67,8 @@ public class TableManager {
|
||||||
|
|
||||||
protected static ScheduledExecutorService expireExecutor = Executors.newSingleThreadScheduledExecutor();
|
protected static ScheduledExecutorService expireExecutor = Executors.newSingleThreadScheduledExecutor();
|
||||||
|
|
||||||
|
// protected static ScheduledExecutorService expireExecutor = ThreadExecutor.getInstance().getExpireExecutor();
|
||||||
|
|
||||||
private static final TableManager INSTANCE = new TableManager();
|
private static final TableManager INSTANCE = new TableManager();
|
||||||
private static final Logger logger = Logger.getLogger(TableManager.class);
|
private static final Logger logger = Logger.getLogger(TableManager.class);
|
||||||
private static final DateFormat formatter = new SimpleDateFormat("HH:mm:ss");
|
private static final DateFormat formatter = new SimpleDateFormat("HH:mm:ss");
|
||||||
|
|
@ -77,7 +81,7 @@ public class TableManager {
|
||||||
*
|
*
|
||||||
* In minutes.
|
* In minutes.
|
||||||
*/
|
*/
|
||||||
private static final int EXPIRE_CHECK_PERIOD = 5;
|
private static final int EXPIRE_CHECK_PERIOD = 1;
|
||||||
|
|
||||||
public static TableManager getInstance() {
|
public static TableManager getInstance() {
|
||||||
return INSTANCE;
|
return INSTANCE;
|
||||||
|
|
@ -340,14 +344,23 @@ public class TableManager {
|
||||||
|
|
||||||
Table table = tables.get(tableId);
|
Table table = tables.get(tableId);
|
||||||
tables.remove(tableId);
|
tables.remove(tableId);
|
||||||
|
Match match = table.getMatch();
|
||||||
|
Game game = null;
|
||||||
|
if (match != null) {
|
||||||
|
game = match.getGame();
|
||||||
|
if (game != null && !game.hasEnded()) {
|
||||||
|
game.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If table is not finished, the table has to be removed completly because it's not a normal state (if finished it will be removed in GamesRoomImpl.Update())
|
// If table is not finished, the table has to be removed completly because it's not a normal state (if finished it will be removed in GamesRoomImpl.Update())
|
||||||
if (!table.getState().equals(TableState.FINISHED)) {
|
if (!table.getState().equals(TableState.FINISHED)) {
|
||||||
|
if (game != null) {
|
||||||
|
GameManager.getInstance().removeGame(game.getId());
|
||||||
|
}
|
||||||
GamesRoomManager.getInstance().removeTable(tableId);
|
GamesRoomManager.getInstance().removeTable(tableId);
|
||||||
}
|
}
|
||||||
if (table.getMatch() != null && table.getMatch().getGame() != null) {
|
|
||||||
table.getMatch().getGame().end();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -364,6 +377,7 @@ public class TableManager {
|
||||||
logger.debug(chatSession.getChatId() + " " +formatter.format(chatSession.getCreateTime()) +" " + chatSession.getInfo()+ " "+ chatSession.getClients().values().toString());
|
logger.debug(chatSession.getChatId() + " " +formatter.format(chatSession.getCreateTime()) +" " + chatSession.getInfo()+ " "+ chatSession.getClients().values().toString());
|
||||||
}
|
}
|
||||||
logger.debug("------- Games: " + GameManager.getInstance().getNumberActiveGames() + " --------------------------------------------");
|
logger.debug("------- Games: " + GameManager.getInstance().getNumberActiveGames() + " --------------------------------------------");
|
||||||
|
logger.debug(" Active Game Worker: " + ThreadExecutor.getInstance().getActiveThreads(ThreadExecutor.getInstance().getGameExecutor()));
|
||||||
for (Entry<UUID, GameController> entry: GameManager.getInstance().getGameController().entrySet()) {
|
for (Entry<UUID, GameController> entry: GameManager.getInstance().getGameController().entrySet()) {
|
||||||
logger.debug(entry.getKey() + entry.getValue().getPlayerNameList());
|
logger.debug(entry.getKey() + entry.getValue().getPlayerNameList());
|
||||||
}
|
}
|
||||||
|
|
@ -404,6 +418,7 @@ public class TableManager {
|
||||||
}
|
}
|
||||||
for (UUID tableId : toRemove) {
|
for (UUID tableId : toRemove) {
|
||||||
try {
|
try {
|
||||||
|
logger.warn("Removing unhealthy tableId " + tableId);
|
||||||
removeTable(tableId);
|
removeTable(tableId);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error(e);
|
logger.error(e);
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ public class UserManager {
|
||||||
private static final Logger logger = Logger.getLogger(UserManager.class);
|
private static final Logger logger = Logger.getLogger(UserManager.class);
|
||||||
|
|
||||||
private final ConcurrentHashMap<UUID, User> users = new ConcurrentHashMap<>();
|
private final ConcurrentHashMap<UUID, User> users = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
private static final ExecutorService callExecutor = ThreadExecutor.getInstance().getCallExecutor();
|
private static final ExecutorService callExecutor = ThreadExecutor.getInstance().getCallExecutor();
|
||||||
|
|
||||||
private static final UserManager INSTANCE = new UserManager();
|
private static final UserManager INSTANCE = new UserManager();
|
||||||
|
|
@ -131,18 +132,29 @@ public class UserManager {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeUser(UUID userId, DisconnectReason reason) {
|
public void removeUser(final UUID userId, final DisconnectReason reason) {
|
||||||
if (userId != null) {
|
if (userId != null) {
|
||||||
User user = users.get(userId);
|
final User user = users.get(userId);
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
logger.debug("User " + user.getName() + " will be removed (" + reason.toString() + ") userId: " + userId);
|
callExecutor.execute(
|
||||||
user.remove(reason);
|
new Runnable() {
|
||||||
users.remove(userId);
|
@Override
|
||||||
logger.debug("User " + user.getName() + " removed");
|
public void run() {
|
||||||
|
try {
|
||||||
|
logger.debug("User " + user.getName() + " will be removed (" + reason.toString() + ") userId: " + userId);
|
||||||
|
user.remove(reason);
|
||||||
|
users.remove(userId);
|
||||||
|
logger.debug("User " + user.getName() + " removed");
|
||||||
|
} catch (Exception ex) {
|
||||||
|
handleException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
logger.warn(new StringBuilder("Trying to remove userId: ").append(userId).append(" but it does not exist."));
|
logger.warn(new StringBuilder("Trying to remove userId: ").append(userId).append(" but it does not exist."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean extendUserSession(UUID userId, String pingInfo) {
|
public boolean extendUserSession(UUID userId, String pingInfo) {
|
||||||
|
|
@ -160,34 +172,16 @@ public class UserManager {
|
||||||
* Is the connection lost for more than 3 minutes, the user will be removed (within 3 minutes the user can reconnect)
|
* Is the connection lost for more than 3 minutes, the user will be removed (within 3 minutes the user can reconnect)
|
||||||
*/
|
*/
|
||||||
private void checkExpired() {
|
private void checkExpired() {
|
||||||
// calling this with executer saves the sceduled job to be dying becuase of exception.
|
Calendar expired = Calendar.getInstance();
|
||||||
// Also exceptions were not reported as now with this handling
|
expired.add(Calendar.MINUTE, -3);
|
||||||
try {
|
List<User> usersToCheck = new ArrayList<>();
|
||||||
callExecutor.execute(
|
usersToCheck.addAll(users.values());
|
||||||
new Runnable() {
|
for (User user : usersToCheck) {
|
||||||
@Override
|
if (user.isExpired(expired.getTime())) {
|
||||||
public void run() {
|
logger.info(new StringBuilder(user.getName()).append(": session expired userId: ").append(user.getId())
|
||||||
try {
|
.append(" Host: ").append(user.getHost()));
|
||||||
Calendar expired = Calendar.getInstance();
|
removeUser(user.getId(), DisconnectReason.SessionExpired);
|
||||||
expired.add(Calendar.MINUTE, -3);
|
}
|
||||||
List<User> usersToCheck = new ArrayList<>();
|
|
||||||
usersToCheck.addAll(users.values());
|
|
||||||
for (User user : usersToCheck) {
|
|
||||||
if (user.isExpired(expired.getTime())) {
|
|
||||||
logger.info(new StringBuilder(user.getName()).append(": session expired userId: ").append(user.getId())
|
|
||||||
.append(" Host: ").append(user.getHost()));
|
|
||||||
removeUser(user.getId(), DisconnectReason.SessionExpired);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
|
||||||
handleException(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
} catch (Exception ex) {
|
|
||||||
handleException(ex);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -302,6 +302,7 @@ public class GameController implements GameCallback {
|
||||||
if (!entry.getValue().init()) {
|
if (!entry.getValue().init()) {
|
||||||
logger.fatal("Unable to initialize client");
|
logger.fatal("Unable to initialize client");
|
||||||
//TODO: generate client error message
|
//TODO: generate client error message
|
||||||
|
GameManager.getInstance().removeGame(game.getId());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ public class GameWorker implements Callable {
|
||||||
@Override
|
@Override
|
||||||
public Object call() {
|
public Object call() {
|
||||||
try {
|
try {
|
||||||
|
logger.debug("GameWorker started gameId "+ game.getId());
|
||||||
game.start(choosingPlayerId);
|
game.start(choosingPlayerId);
|
||||||
game.fireUpdatePlayersEvent();
|
game.fireUpdatePlayersEvent();
|
||||||
result.gameResult(game.getWinner());
|
result.gameResult(game.getWinner());
|
||||||
|
|
|
||||||
|
|
@ -59,10 +59,10 @@ public class ThreadExecutor {
|
||||||
((ThreadPoolExecutor)callExecutor).setThreadFactory(new XMageThreadFactory("CALL"));
|
((ThreadPoolExecutor)callExecutor).setThreadFactory(new XMageThreadFactory("CALL"));
|
||||||
((ThreadPoolExecutor)gameExecutor).setKeepAliveTime(60, TimeUnit.SECONDS);
|
((ThreadPoolExecutor)gameExecutor).setKeepAliveTime(60, TimeUnit.SECONDS);
|
||||||
((ThreadPoolExecutor)gameExecutor).allowCoreThreadTimeOut(true);
|
((ThreadPoolExecutor)gameExecutor).allowCoreThreadTimeOut(true);
|
||||||
((ThreadPoolExecutor)callExecutor).setThreadFactory(new XMageThreadFactory("GAME"));
|
((ThreadPoolExecutor)gameExecutor).setThreadFactory(new XMageThreadFactory("GAME"));
|
||||||
((ThreadPoolExecutor)timeoutExecutor).setKeepAliveTime(60, TimeUnit.SECONDS);
|
((ThreadPoolExecutor)timeoutExecutor).setKeepAliveTime(60, TimeUnit.SECONDS);
|
||||||
((ThreadPoolExecutor)timeoutExecutor).allowCoreThreadTimeOut(true);
|
((ThreadPoolExecutor)timeoutExecutor).allowCoreThreadTimeOut(true);
|
||||||
((ThreadPoolExecutor)callExecutor).setThreadFactory(new XMageThreadFactory("TIME"));
|
((ThreadPoolExecutor)timeoutExecutor).setThreadFactory(new XMageThreadFactory("TIME"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final ThreadExecutor INSTANCE = new ThreadExecutor();
|
private static final ThreadExecutor INSTANCE = new ThreadExecutor();
|
||||||
|
|
@ -73,10 +73,17 @@ public class ThreadExecutor {
|
||||||
|
|
||||||
private ThreadExecutor() {}
|
private ThreadExecutor() {}
|
||||||
|
|
||||||
|
public int getActiveThreads(ExecutorService executerService) {
|
||||||
|
if (executerService instanceof ThreadPoolExecutor) {
|
||||||
|
return ((ThreadPoolExecutor)executerService).getActiveCount();
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
public ExecutorService getCallExecutor() {
|
public ExecutorService getCallExecutor() {
|
||||||
return callExecutor;
|
return callExecutor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ExecutorService getGameExecutor() {
|
public ExecutorService getGameExecutor() {
|
||||||
return gameExecutor;
|
return gameExecutor;
|
||||||
}
|
}
|
||||||
|
|
@ -84,7 +91,7 @@ public class ThreadExecutor {
|
||||||
public ScheduledExecutorService getTimeoutExecutor() {
|
public ScheduledExecutorService getTimeoutExecutor() {
|
||||||
return timeoutExecutor;
|
return timeoutExecutor;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class XMageThreadFactory implements ThreadFactory {
|
class XMageThreadFactory implements ThreadFactory {
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ import mage.game.events.TableEventSource;
|
||||||
import mage.players.Player;
|
import mage.players.Player;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import org.apache.log4j.Logger;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -46,6 +47,8 @@ import java.util.*;
|
||||||
*/
|
*/
|
||||||
public abstract class MatchImpl implements Match {
|
public abstract class MatchImpl implements Match {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(MatchImpl.class);
|
||||||
|
|
||||||
protected UUID id = UUID.randomUUID();
|
protected UUID id = UUID.randomUUID();
|
||||||
protected List<MatchPlayer> players = new ArrayList<>();
|
protected List<MatchPlayer> players = new ArrayList<>();
|
||||||
protected List<Game> games = new ArrayList<>();
|
protected List<Game> games = new ArrayList<>();
|
||||||
|
|
@ -128,6 +131,19 @@ public abstract class MatchImpl implements Match {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasEnded() {
|
public boolean hasEnded() {
|
||||||
|
// Some workarounds to end match if for unknown reason the match was not ended regularly
|
||||||
|
if (getGame() == null && isDoneSideboarding()) {
|
||||||
|
checkIfMatchEnds();
|
||||||
|
}
|
||||||
|
if (getGame() != null && getGame().hasEnded()) {
|
||||||
|
for (MatchPlayer matchPlayer:players) {
|
||||||
|
if (matchPlayer.getPlayer().hasQuit() && !matchPlayer.hasQuit()) {
|
||||||
|
logger.warn("MatchPlayer was not set to quit matchId " + this.getId()+ " - " + matchPlayer.getName());
|
||||||
|
matchPlayer.setQuit(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checkIfMatchEnds();
|
||||||
|
}
|
||||||
return endTime != null;
|
return endTime != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ public class MatchPlayer {
|
||||||
private final String name;
|
private final String name;
|
||||||
|
|
||||||
private boolean quit;
|
private boolean quit;
|
||||||
private boolean timerTimeout;
|
private final boolean timerTimeout;
|
||||||
private boolean doneSideboarding;
|
private boolean doneSideboarding;
|
||||||
private int priorityTimeLeft;
|
private int priorityTimeLeft;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -72,18 +72,20 @@ public class Round {
|
||||||
public boolean isRoundOver() {
|
public boolean isRoundOver() {
|
||||||
boolean roundIsOver = true;
|
boolean roundIsOver = true;
|
||||||
for (TournamentPairing pair: pairs) {
|
for (TournamentPairing pair: pairs) {
|
||||||
if (pair.getMatch() != null && !pair.getMatch().hasEnded()) {
|
if (pair.getMatch() != null) {
|
||||||
roundIsOver = false;
|
if (!pair.getMatch().hasEnded()) {
|
||||||
} else {
|
roundIsOver = false;
|
||||||
if (!pair.isAlreadyPublished()) {
|
} else {
|
||||||
tournament.updateResults();
|
if (!pair.isAlreadyPublished()) {
|
||||||
pair.setAlreadyPublished(true);
|
tournament.updateResults();
|
||||||
if (tournament instanceof TournamentSingleElimination) {
|
pair.setAlreadyPublished(true);
|
||||||
pair.eliminatePlayers();
|
if (tournament instanceof TournamentSingleElimination) {
|
||||||
}
|
pair.eliminatePlayers();
|
||||||
// if it's the last round, finish all players for the tournament if their match is finished
|
}
|
||||||
if (getRoundNumber() == tournament.getNumberRounds()) {
|
// if it's the last round, finish all players for the tournament if their match is finished
|
||||||
pair.finishPlayersThatPlayedLastRound();
|
if (getRoundNumber() == tournament.getNumberRounds()) {
|
||||||
|
pair.finishPlayersThatPlayedLastRound();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue