Improved server's reconnection and drafts stability:

* draft: fixed miss or empty draft panels on reconnect;
* draft: fixed tourney freezes for richman drafts on disconnects;
* draft: fixed tourney freezes on rare use cases with bad connection;
This commit is contained in:
Oleg Agafonov 2025-04-18 09:38:52 +04:00
parent 5e27be4dfa
commit 30d44ce869
17 changed files with 201 additions and 133 deletions

View file

@ -73,6 +73,7 @@ public class Session {
private final ReentrantLock lock;
private final ReentrantLock callBackLock;
private String lastCallbackInfo = "";
public Session(ManagerFactory managerFactory, String sessionId, InvokerCallbackHandler callbackHandler) {
this.managerFactory = managerFactory;
@ -429,8 +430,10 @@ public class Session {
*/
public void fireCallback(final ClientCallback call) {
boolean lockSet = false; // TODO: research about locks, why it here? 2023-12-06
try {
if (valid && callBackLock.tryLock(50, TimeUnit.MILLISECONDS)) {
lastCallbackInfo = call.getInfo();
call.setMessageId(messageId.incrementAndGet());
lockSet = true;
Callback callback = new Callback(call);
@ -440,11 +443,14 @@ public class Session {
}
} catch (InterruptedException ex) {
// already sending another command (connection problem?)
// TODO: un-support multiple games/drafts at the same time?!?!?!?!
if (call.getMethod().equals(ClientCallbackMethod.GAME_INIT)
|| call.getMethod().equals(ClientCallbackMethod.START_GAME)) {
// it's ok use case, user has connection problem so can't send game init (see sendInfoAboutPlayersNotJoinedYetAndTryToFixIt)
// it's ok, possible use cases:
// - user has connection problem so can't send game init (see sendInfoAboutPlayersNotJoinedYetAndTryToFixIt)
} else {
logger.warn("SESSION LOCK, possible connection problem - fireCallback - userId: " + userId + " messageId: " + call.getMessageId(), ex);
logger.warn("SESSION LOCK, possible connection problem - fireCallback - userId: "
+ userId + ", prev call: " + lastCallbackInfo + ", current call: " + call.getInfo(), ex);
}
} catch (HandleCallbackException ex) {
// general error

View file

@ -4,6 +4,7 @@ import mage.cards.decks.Deck;
import mage.constants.ManaType;
import mage.constants.TableState;
import mage.game.Table;
import mage.game.draft.DraftPlayer;
import mage.game.result.ResultProtos;
import mage.game.tournament.TournamentPlayer;
import mage.interfaces.callback.ClientCallback;
@ -441,6 +442,15 @@ public class User {
ccDraftStarted(entry.getValue().getDraft().getTableId(), entry.getValue().getDraft().getId(), entry.getKey());
entry.getValue().init();
entry.getValue().update();
// on reconnect must resend booster data to new player
// TODO: there are possible rare race conditions and user can get draft panel without game info, miss tourney panel, etc
// TODO: client side - it must show active panel like current game or draft instead tourney panel
DraftPlayer draftPlayer = entry.getValue().getDraftPlayer();
if (draftPlayer != null) {
draftPlayer.setBoosterNotLoaded();
entry.getValue().getDraft().boosterSendingStart();
}
}
// active constructing

View file

@ -1,6 +1,7 @@
package mage.server.draft;
import mage.game.draft.Draft;
import mage.game.draft.DraftPlayer;
import mage.interfaces.callback.ClientCallback;
import mage.interfaces.callback.ClientCallbackMethod;
import mage.server.User;
@ -156,6 +157,10 @@ public class DraftSession {
return new DraftPickView(draft.getPlayer(playerId), timeout);
}
public DraftPlayer getDraftPlayer() {
return draft.getPlayer(playerId);
}
public Draft getDraft() {
return draft;
}

View file

@ -38,20 +38,17 @@ public class ThreadExecutorImpl implements ThreadExecutor {
*/
public ThreadExecutorImpl(ConfigSettings config) {
//callExecutor = Executors.newCachedThreadPool();
callExecutor = new CachedThreadPoolWithException();
((ThreadPoolExecutor) callExecutor).setKeepAliveTime(60, TimeUnit.SECONDS);
((ThreadPoolExecutor) callExecutor).allowCoreThreadTimeOut(true);
((ThreadPoolExecutor) callExecutor).setThreadFactory(new XmageThreadFactory(ThreadUtils.THREAD_PREFIX_CALL_REQUEST));
//gameExecutor = Executors.newFixedThreadPool(config.getMaxGameThreads());
gameExecutor = new FixedThreadPoolWithException(config.getMaxGameThreads());
((ThreadPoolExecutor) gameExecutor).setKeepAliveTime(60, TimeUnit.SECONDS);
((ThreadPoolExecutor) gameExecutor).allowCoreThreadTimeOut(true);
((ThreadPoolExecutor) gameExecutor).setThreadFactory(new XmageThreadFactory(ThreadUtils.THREAD_PREFIX_GAME));
//tourney = Executors.newFixedThreadPool(config.getMaxGameThreads() / GAMES_PER_TOURNEY_RATIO);
tourneyExecutor = new FixedThreadPoolWithException(config.getMaxGameThreads() / GAMES_PER_TOURNEY_RATIO);
tourneyExecutor = new FixedThreadPoolWithException(Math.min(2, config.getMaxGameThreads() / GAMES_PER_TOURNEY_RATIO));
((ThreadPoolExecutor) tourneyExecutor).setKeepAliveTime(60, TimeUnit.SECONDS);
((ThreadPoolExecutor) tourneyExecutor).allowCoreThreadTimeOut(true);
((ThreadPoolExecutor) tourneyExecutor).setThreadFactory(new XmageThreadFactory(ThreadUtils.THREAD_PREFIX_TOURNEY));