connection: improved error processing and stability in connect dialog

This commit is contained in:
Oleg Agafonov 2023-11-30 21:52:47 +04:00
parent adf57a0677
commit 0b0e947741
6 changed files with 55 additions and 24 deletions

View file

@ -34,7 +34,7 @@ public class MageUI {
super.afterExecute(r, t); super.afterExecute(r, t);
// catch errors in popup threads (example: card popup over cards or chat/log messages) // catch errors in popup threads (example: card popup over cards or chat/log messages)
t = ThreadUtils.findRealException(r, t); t = ThreadUtils.findRunnableException(r, t);
if (t != null && !(t instanceof CancellationException)) { if (t != null && !(t instanceof CancellationException)) {
logger.error("Catch unhandled error in POPUP thread: " + t.getMessage(), t); logger.error("Catch unhandled error in POPUP thread: " + t.getMessage(), t);
} }

View file

@ -11,6 +11,7 @@ import mage.client.util.gui.countryBox.CountryItemEditor;
import mage.client.util.sets.ConstructedFormats; import mage.client.util.sets.ConstructedFormats;
import mage.remote.Connection; import mage.remote.Connection;
import mage.utils.StreamUtils; import mage.utils.StreamUtils;
import mage.utils.ThreadUtils;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import javax.swing.*; import javax.swing.*;
@ -61,6 +62,7 @@ public class ConnectDialog extends MageDialog {
} }
public void showDialog() { public void showDialog() {
this.lblStatus.setText("");
String serverAddress = MagePreferences.getServerAddressWithDefault(ClientDefaultSettings.serverName); String serverAddress = MagePreferences.getServerAddressWithDefault(ClientDefaultSettings.serverName);
this.txtServer.setText(serverAddress); this.txtServer.setText(serverAddress);
this.txtPort.setText(Integer.toString(MagePreferences.getServerPortWithDefault(ClientDefaultSettings.port))); this.txtPort.setText(Integer.toString(MagePreferences.getServerPortWithDefault(ClientDefaultSettings.port)));
@ -83,6 +85,7 @@ public class ConnectDialog extends MageDialog {
} }
private void saveSettings() { private void saveSettings() {
ThreadUtils.sleep(3000);
String serverAddress = txtServer.getText().trim(); String serverAddress = txtServer.getText().trim();
MagePreferences.setServerAddress(serverAddress); MagePreferences.setServerAddress(serverAddress);
MagePreferences.setServerPort(Integer.parseInt(txtPort.getText().trim())); MagePreferences.setServerPort(Integer.parseInt(txtPort.getText().trim()));
@ -678,8 +681,10 @@ public class ConnectDialog extends MageDialog {
@Override @Override
protected Boolean doInBackground() throws Exception { protected Boolean doInBackground() throws Exception {
lblStatus.setText("Connecting..."); SwingUtilities.invokeLater(() -> {
setConnectButtonsState(false); lblStatus.setText("Connecting...");
setConnectButtonsState(false);
});
result = MageFrame.connect(connection); result = MageFrame.connect(connection);
lastConnectError = SessionHandler.getLastConnectError(); lastConnectError = SessionHandler.getLastConnectError();
return result; return result;
@ -688,32 +693,44 @@ public class ConnectDialog extends MageDialog {
@Override @Override
protected void done() { protected void done() {
try { try {
get(CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS); get(CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS); // catch exceptions
if (result) {
lblStatus.setText(""); SwingUtilities.invokeLater(() -> {
connected(); if (result) {
MageFrame.getInstance().prepareAndShowTablesPane(); lblStatus.setText("Connected");
} else {
lblStatus.setText("Could not connect: " + lastConnectError); // for ux: after connection client can load additional resources and data,
} // so the connection dialog will be visible all that time
SwingUtilities.invokeLater(() -> {
doAfterConnected();
MageFrame.getInstance().prepareAndShowTablesPane();
btnConnect.setEnabled(true);
});
} else {
lblStatus.setText("Could not connect: " + lastConnectError);
}
});
} catch (InterruptedException | ExecutionException ex) { } catch (InterruptedException | ExecutionException ex) {
logger.fatal("Update Players Task error", ex); logger.fatal("Connection: can't load data from server", ex);
} catch (CancellationException ex) { } catch (CancellationException ex) {
logger.info("Connect: canceled"); logger.info("Connect: canceled");
lblStatus.setText("Connect was canceled"); lblStatus.setText("Connect was canceled");
} catch (TimeoutException ex) { } catch (TimeoutException ex) {
logger.fatal("Connection timeout: ", ex); logger.fatal("Connection: timeout", ex);
} finally { } finally {
MageFrame.stopConnecting(); if (!result) {
setConnectButtonsState(true); MageFrame.stopConnecting();
SwingUtilities.invokeLater(() -> {
setConnectButtonsState(true);
});
}
} }
} }
} }
private void connected() { private void doAfterConnected() {
this.saveSettings(); this.saveSettings();
this.hideDialog(); this.hideDialog();
ConstructedFormats.ensureLists();
} }
private void keyTyped(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_keyTyped private void keyTyped(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_keyTyped

View file

@ -41,7 +41,7 @@ public class LinePool {
@Override @Override
protected void afterExecute(Runnable r, Throwable t) { protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t); super.afterExecute(r, t);
t = ThreadUtils.findRealException(r, t); t = ThreadUtils.findRunnableException(r, t);
if (t != null && !(t instanceof CancellationException)) { if (t != null && !(t instanceof CancellationException)) {
// TODO: show sound errors in client logs? // TODO: show sound errors in client logs?
//logger.error("Catch unhandled error in SOUND thread: " + t.getMessage(), t); //logger.error("Catch unhandled error in SOUND thread: " + t.getMessage(), t);

View file

@ -18,6 +18,7 @@ import mage.interfaces.callback.ClientCallback;
import mage.players.PlayerType; import mage.players.PlayerType;
import mage.players.net.UserData; import mage.players.net.UserData;
import mage.utils.CompressUtil; import mage.utils.CompressUtil;
import mage.utils.ThreadUtils;
import mage.view.*; import mage.view.*;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import org.jboss.remoting.*; import org.jboss.remoting.*;
@ -118,10 +119,13 @@ public class SessionImpl implements Session {
} }
private void showMessageToUser(String message) { private void showMessageToUser(String message) {
if (message == null) {
message = "Unknown error, look at logs for details";
}
if (message.contains("free port for use")) { if (message.contains("free port for use")) {
message += " (try to close and restart a client app)"; message += " (try to close and restart a client app)";
} }
client.showMessage("Remote task error. " + message); client.showMessage("Remote task error: " + message);
} }
private boolean doRemoteWorkAndHandleErrors(boolean closeConnectionOnFinish, boolean mustWaitServerMessageOnFail, private boolean doRemoteWorkAndHandleErrors(boolean closeConnectionOnFinish, boolean mustWaitServerMessageOnFail,
@ -180,17 +184,18 @@ public class SessionImpl implements Session {
logger.warn("Connect: wrong versions"); logger.warn("Connect: wrong versions");
connectStop(false); connectStop(false);
if (!canceled) { if (!canceled) {
showMessageToUser(ex.getMessage()); showMessageToUser(ex.toString());
} }
} catch (CannotConnectException ex) { } catch (CannotConnectException ex) {
if (!canceled) { if (!canceled) {
handleCannotConnectException(ex); handleCannotConnectException(ex);
} }
} catch (Throwable t) { } catch (Throwable t) {
Throwable ex = ThreadUtils.findRootException(t);
logger.fatal("Connect: FAIL", t); logger.fatal("Connect: FAIL", t);
connectStop(false); connectStop(false);
if (!canceled) { if (!canceled) {
showMessageToUser(t.getMessage()); showMessageToUser(ex.toString());
} }
} finally { } finally {
lastRemotingTask = null; lastRemotingTask = null;
@ -470,6 +475,8 @@ public class SessionImpl implements Session {
private void handleCannotConnectException(CannotConnectException ex) { private void handleCannotConnectException(CannotConnectException ex) {
logger.warn("Cannot connect", ex); logger.warn("Cannot connect", ex);
// try to find a known error
Throwable t = ex.getCause(); Throwable t = ex.getCause();
String message = ""; String message = "";
while (t != null) { while (t != null) {
@ -493,6 +500,7 @@ public class SessionImpl implements Session {
t = t.getCause(); t = t.getCause();
} }
client.showMessage("Unable connect to server. " + message); client.showMessage("Unable connect to server. " + message);
setLastError(message);
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("StackTrace", t); logger.trace("StackTrace", t);
} }

View file

@ -1,5 +1,7 @@
package mage.utils; package mage.utils;
import com.google.common.base.Throwables;
import java.util.concurrent.CancellationException; import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future; import java.util.concurrent.Future;
@ -31,7 +33,7 @@ public final class ThreadUtils {
* Find real exception object after thread task completed. Can be used in afterExecute * Find real exception object after thread task completed. Can be used in afterExecute
* *
*/ */
public static Throwable findRealException(Runnable r, Throwable t) { public static Throwable findRunnableException(Runnable r, Throwable t) {
// executer.submit - return exception in result // executer.submit - return exception in result
// executer.execute - return exception in t // executer.execute - return exception in t
if (t == null && r instanceof Future<?>) { if (t == null && r instanceof Future<?>) {
@ -47,4 +49,8 @@ public final class ThreadUtils {
} }
return t; return t;
} }
public static Throwable findRootException(Throwable t) {
return Throwables.getRootCause(t);
}
} }

View file

@ -64,7 +64,7 @@ public class ThreadExecutorImpl implements ThreadExecutor {
super.afterExecute(r, t); super.afterExecute(r, t);
// catch errors in CALL threads (from client commands) // catch errors in CALL threads (from client commands)
t = ThreadUtils.findRealException(r, t); t = ThreadUtils.findRunnableException(r, t);
if (t != null && !(t instanceof CancellationException)) { if (t != null && !(t instanceof CancellationException)) {
logger.error("Catch unhandled error in CALL thread: " + t.getMessage(), t); logger.error("Catch unhandled error in CALL thread: " + t.getMessage(), t);
} }
@ -83,7 +83,7 @@ public class ThreadExecutorImpl implements ThreadExecutor {
super.afterExecute(r, t); super.afterExecute(r, t);
// catch errors in GAME threads (from game processing) // catch errors in GAME threads (from game processing)
t = ThreadUtils.findRealException(r, t); t = ThreadUtils.findRunnableException(r, t);
if (t != null && !(t instanceof CancellationException)) { if (t != null && !(t instanceof CancellationException)) {
// it's impossible to brake game thread in normal use case, so each bad use case must be researched // it's impossible to brake game thread in normal use case, so each bad use case must be researched
logger.error("Catch unhandled error in GAME thread: " + t.getMessage(), t); logger.error("Catch unhandled error in GAME thread: " + t.getMessage(), t);