Error handler improvements

This commit is contained in:
crschnick 2023-04-14 08:53:55 +00:00
parent 8abc82971a
commit c3793ed0c0
14 changed files with 127 additions and 63 deletions

View file

@ -6,6 +6,7 @@ import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.update.XPipeDistributionType;
import io.xpipe.app.util.PlatformState;
import io.xpipe.core.process.OsType;
import javafx.application.Application;
import javafx.application.Platform;
@ -23,10 +24,6 @@ public class App extends Application {
private Stage stage;
private Image icon;
public static boolean isPlatformRunning() {
return APP != null;
}
public static App getApp() {
return APP;
}
@ -35,6 +32,7 @@ public class App extends Application {
public void start(Stage primaryStage) {
TrackEvent.info("Application launched");
APP = this;
PlatformState.setCurrent(PlatformState.RUNNING);
stage = primaryStage;
icon = AppImages.image("logo.png");

View file

@ -3,10 +3,7 @@ package io.xpipe.app.core.mode;
import io.xpipe.app.comp.storage.collection.SourceCollectionViewState;
import io.xpipe.app.comp.storage.store.StoreViewState;
import io.xpipe.app.core.*;
import io.xpipe.app.issue.ErrorAction;
import io.xpipe.app.issue.ErrorHandler;
import io.xpipe.app.issue.LogErrorHandler;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.issue.*;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.util.FileBridge;
@ -59,9 +56,9 @@ public class BaseMode extends OperationMode {
@Override
public ErrorHandler getErrorHandler() {
var log = new LogErrorHandler();
return event -> {
return new SyncErrorHandler(event -> {
log.handle(event);
ErrorAction.ignore().handle(event);
};
});
}
}

View file

@ -4,6 +4,7 @@ import io.xpipe.app.core.App;
import io.xpipe.app.core.AppGreetings;
import io.xpipe.app.issue.*;
import io.xpipe.app.update.UpdateChangelogAlert;
import io.xpipe.app.util.PlatformState;
import javafx.application.Platform;
import java.util.concurrent.CountDownLatch;
@ -17,7 +18,7 @@ public class GuiMode extends PlatformMode {
@Override
public void onSwitchTo() {
if (!App.isPlatformRunning()) {
if (PlatformState.getCurrent() == PlatformState.NOT_INITIALIZED) {
super.platformSetup();
}
@ -44,7 +45,7 @@ public class GuiMode extends PlatformMode {
@Override
public void onSwitchFrom() {
if (App.isPlatformRunning()) {
if (PlatformState.getCurrent() == PlatformState.RUNNING) {
TrackEvent.info("mode", "Closing window");
App.getApp().close();
waitForPlatform();
@ -54,9 +55,9 @@ public class GuiMode extends PlatformMode {
@Override
public ErrorHandler getErrorHandler() {
var log = new LogErrorHandler();
return event -> {
return new SyncErrorHandler(event -> {
log.handle(event);
ErrorHandlerComp.showAndWait(event);
};
});
}
}

View file

@ -6,12 +6,11 @@ import io.xpipe.app.core.AppLogs;
import io.xpipe.app.core.AppProperties;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.ErrorHandler;
import io.xpipe.app.issue.SentryErrorHandler;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.launcher.LauncherCommand;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.util.XPipeDaemonMode;
import io.xpipe.app.util.XPipeSession;
import io.xpipe.core.util.XPipeDaemonMode;
import org.apache.commons.lang3.function.FailableRunnable;
import java.util.ArrayList;
@ -77,6 +76,11 @@ public abstract class OperationMode {
OperationMode.shutdown(true, false);
}));
// Handle uncaught exceptions
Thread.setDefaultUncaughtExceptionHandler((thread, ex) -> {
ErrorEvent.fromThrowable(ex).build().handle();
});
// if (true) {
// throw new OutOfMemoryError();
// }
@ -84,7 +88,6 @@ public abstract class OperationMode {
TrackEvent.info("mode", "Initial setup");
AppProperties.init();
XPipeSession.init(AppProperties.get().getBuildUuid());
SentryErrorHandler.init();
AppChecks.checkDirectoryPermissions();
AppLogs.init();
AppProperties.logArguments(args);

View file

@ -6,6 +6,7 @@ import io.xpipe.app.core.*;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.update.UpdateAvailableAlert;
import io.xpipe.app.util.PlatformState;
import io.xpipe.app.util.ThreadHelper;
import javafx.application.Application;
import javafx.application.Platform;
@ -89,7 +90,7 @@ public abstract class PlatformMode extends OperationMode {
protected void waitForPlatform() {
// The platform thread waits for the shutdown hook to finish in case SIGTERM is sent.
// Therefore, we do not wait for the platform when being in a shutdown hook.
if (App.isPlatformRunning() && !Platform.isFxApplicationThread() && !OperationMode.isInShutdownHook()) {
if (PlatformState.getCurrent() == PlatformState.RUNNING && !Platform.isFxApplicationThread() && !OperationMode.isInShutdownHook()) {
TrackEvent.info("mode", "Waiting for platform thread ...");
CountDownLatch latch = new CountDownLatch(1);
Platform.runLater(latch::countDown);
@ -116,6 +117,7 @@ public abstract class PlatformMode extends OperationMode {
TrackEvent.info("mode", "Shutting down platform components");
onSwitchFrom();
Platform.exit();
PlatformState.setCurrent(PlatformState.EXITED);
TrackEvent.info("mode", "Platform shutdown finished");
BACKGROUND.finalTeardown();
}

View file

@ -1,9 +1,9 @@
package io.xpipe.app.core.mode;
import com.dustinredmond.fxtrayicon.FXTrayIcon;
import io.xpipe.app.core.App;
import io.xpipe.app.core.AppTray;
import io.xpipe.app.issue.*;
import io.xpipe.app.util.PlatformState;
public class TrayMode extends PlatformMode {
@ -19,7 +19,7 @@ public class TrayMode extends PlatformMode {
@Override
public void onSwitchTo() {
if (!App.isPlatformRunning()) {
if (PlatformState.getCurrent() == PlatformState.NOT_INITIALIZED) {
super.platformSetup();
}
@ -45,13 +45,13 @@ public class TrayMode extends PlatformMode {
@Override
public ErrorHandler getErrorHandler() {
var log = new LogErrorHandler();
return event -> {
return new SyncErrorHandler(event -> {
// Check if tray initialization is finished
if (AppTray.get() != null) {
AppTray.get().getErrorHandler().handle(event);
}
log.handle(event);
ErrorAction.ignore().handle(event);
};
});
}
}

View file

@ -39,7 +39,7 @@ public interface ErrorAction {
@Override
public boolean handle(ErrorEvent event) {
event.clearAttachments();
SentryErrorHandler.report(event);
SentryErrorHandler.getInstance().handle(event);
return true;
}
};

View file

@ -27,6 +27,12 @@ public class ErrorEvent {
@Singular
private List<Path> attachments;
private String userReport;
public void attachUserReport(String text) {
userReport = text;
}
public static ErrorEventBuilder fromThrowable(Throwable t) {
return builder().throwable(t).description(ExceptionConverter.convertMessage(t));
}

View file

@ -2,7 +2,6 @@ package io.xpipe.app.issue;
import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.comp.base.TitledPaneComp;
import io.xpipe.app.core.App;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.AppWindowHelper;
@ -10,6 +9,7 @@ import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.augment.GrowAugment;
import io.xpipe.app.util.JfxHelper;
import io.xpipe.app.util.PlatformState;
import javafx.application.Platform;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
@ -44,7 +44,7 @@ public class ErrorHandlerComp extends SimpleComp {
}
public static void showAndWait(ErrorEvent event) {
if (!App.isPlatformRunning() || event.isOmitted()) {
if (PlatformState.getCurrent() != PlatformState.RUNNING || event.isOmitted()) {
ErrorAction.ignore().handle(event);
return;
}

View file

@ -13,9 +13,19 @@ import java.nio.file.Files;
import java.util.Date;
import java.util.stream.Collectors;
public class SentryErrorHandler {
public class SentryErrorHandler implements ErrorHandler {
public static void init() {
private static final ErrorHandler INSTANCE = new SyncErrorHandler(new SentryErrorHandler());
public static ErrorHandler getInstance() {
return INSTANCE;
}
private boolean init;
public void handle(ErrorEvent ee) {
// Assume that this object is wrapped by a synchronous error handler
if (!init) {
AppProperties.init();
if (AppProperties.get().getSentryUrl() != null) {
Sentry.init(options -> {
@ -29,16 +39,14 @@ public class SentryErrorHandler {
options.setTag("os", System.getProperty("os.name"));
options.setTag("osVersion", System.getProperty("os.version"));
options.setTag("arch", System.getProperty("os.arch"));
options.setDist(XPipeDistributionType.get().getId());
});
}
Thread.setDefaultUncaughtExceptionHandler((thread, ex) -> {
ErrorEvent.fromThrowable(ex).build().handle();
});
init = true;
}
public static void report(ErrorEvent e, String text) {
var id = report(e);
var id = createReport(ee);
var text = ee.getUserReport();
if (text != null && text.length() > 0) {
var fb = new UserFeedback(id);
fb.setComments(text);
@ -46,7 +54,7 @@ public class SentryErrorHandler {
}
}
public static SentryId report(ErrorEvent ee) {
private static SentryId createReport(ErrorEvent ee) {
/*
TODO: Ignore breadcrumbs for now
*/
@ -86,7 +94,6 @@ public class SentryErrorHandler {
.toList();
atts.forEach(attachment -> s.addAttachment(attachment));
s.setTag("dist", XPipeDistributionType.get().getId());
s.setTag("developerMode", AppPrefs.get() != null ? AppPrefs.get().developerMode().getValue().toString() : "false");
s.setTag("terminal", Boolean.toString(ee.isTerminal()));
s.setTag("omitted", Boolean.toString(ee.isOmitted()));
@ -97,7 +104,6 @@ public class SentryErrorHandler {
}
}
var user = new User();
user.setId(AppCache.getCachedUserId().toString());
s.setUser(user);

View file

@ -0,0 +1,38 @@
package io.xpipe.app.issue;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
public class SyncErrorHandler implements ErrorHandler {
private final Queue<ErrorEvent> eventQueue = new LinkedBlockingQueue<>();
private final ErrorHandler errorHandler;
private final AtomicBoolean busy = new AtomicBoolean();
public SyncErrorHandler(ErrorHandler errorHandler) {
this.errorHandler = errorHandler;
}
@Override
public void handle(ErrorEvent event) {
synchronized (busy) {
if (busy.get()) {
synchronized (eventQueue) {
eventQueue.add(event);
}
return;
}
busy.set(true);
}
errorHandler.handle(event);
synchronized (eventQueue) {
eventQueue.forEach(errorEvent -> {
System.out.println("Event happened during error handling: " + errorEvent.toString());
});
eventQueue.clear();
}
busy.set(false);
}
}

View file

@ -5,6 +5,7 @@ import io.xpipe.app.core.*;
import io.xpipe.app.core.mode.OperationMode;
import io.xpipe.app.util.Hyperlinks;
import io.xpipe.app.update.XPipeDistributionType;
import io.xpipe.app.util.PlatformState;
import javafx.application.Platform;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonBar;
@ -23,7 +24,7 @@ public class TerminalErrorHandler implements ErrorHandler {
if (!OperationMode.GUI.isSupported()) {
event.clearAttachments();
SentryErrorHandler.report(event, null);
SentryErrorHandler.getInstance().handle(event);
OperationMode.halt(1);
}
@ -31,20 +32,20 @@ public class TerminalErrorHandler implements ErrorHandler {
}
private void handleSentry(ErrorEvent event) {
SentryErrorHandler.init();
if (OperationMode.isInStartup()) {
Sentry.setExtra("initError", "true");
}
}
private void handleGui(ErrorEvent event) {
if (!App.isPlatformRunning()) {
if (PlatformState.getCurrent() == PlatformState.NOT_INITIALIZED) {
try {
CountDownLatch latch = new CountDownLatch(1);
Platform.setImplicitExit(false);
Platform.startup(latch::countDown);
try {
latch.await();
PlatformState.setCurrent(PlatformState.RUNNING);
} catch (InterruptedException ignored) {
}
} catch (Throwable r) {
@ -78,8 +79,8 @@ public class TerminalErrorHandler implements ErrorHandler {
}
private static void handleSecondaryException(ErrorEvent event, Throwable t) {
SentryErrorHandler.report(event, null);
SentryErrorHandler.report(ErrorEvent.fromThrowable(t).build(), null);
SentryErrorHandler.getInstance().handle(event);
SentryErrorHandler.getInstance().handle(ErrorEvent.fromThrowable(t).build());
Sentry.flush(5000);
t.printStackTrace();
OperationMode.halt(1);
@ -106,7 +107,7 @@ public class TerminalErrorHandler implements ErrorHandler {
}
}
} catch (Throwable t) {
SentryErrorHandler.report(ErrorEvent.fromThrowable(t).build(), null);
SentryErrorHandler.getInstance().handle(ErrorEvent.fromThrowable(t).build());
Sentry.flush(5000);
t.printStackTrace();
OperationMode.halt(1);

View file

@ -1,6 +1,5 @@
package io.xpipe.app.issue;
import io.sentry.Sentry;
import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.comp.base.ListSelectorComp;
import io.xpipe.app.comp.base.TitledPaneComp;
@ -112,12 +111,10 @@ public class UserReportComp extends SimpleComp {
}
private void send() {
Sentry.withScope(scope -> {
event.clearAttachments();
includedDiagnostics.forEach(event::addAttachment);
SentryErrorHandler.report(event, text.get());
});
event.attachUserReport(text.get());
SentryErrorHandler.getInstance().handle(event);
stage.close();
}
}

View file

@ -0,0 +1,15 @@
package io.xpipe.app.util;
import lombok.Getter;
import lombok.Setter;
public enum PlatformState {
NOT_INITIALIZED,
RUNNING,
EXITED;
@Getter
@Setter
private static PlatformState current = PlatformState.NOT_INITIALIZED;
}