shutdown hook

This commit is contained in:
2026-06-02 09:35:41 +02:00
parent dcb70243aa
commit c0ce81f125
4 changed files with 42 additions and 1 deletions

View File

@@ -236,6 +236,18 @@ public final class Compositor {
tabs.clear();
}
/**
* Signals and reaps every pane's shell process across all tabs, without tearing down render
* state. Intended for a JVM shutdown hook (SIGTERM/SIGINT/SIGHUP), so child shells get the
* configured close signal instead of being orphaned when jprototerm itself is killed. Safe to
* call off the FX thread and idempotent; see {@link TerminalPane#terminateSession()}.
*/
public void terminateSessions() {
for (Tab tab : List.copyOf(tabs)) {
tab.terminateSessions();
}
}
private Tab currentTab() {
return tabs.get(currentTabIndex);
}

View File

@@ -41,6 +41,12 @@ public final class Main extends Application {
compositor.close();
Platform.exit();
});
// If jprototerm itself is killed (SIGTERM/SIGINT/SIGHUP, e.g. a logout or `kill`), the JVM
// runs shutdown hooks before exiting. Send each pane's configured close signal here so the
// child shells are terminated rather than orphaned. Only the ptys are touched (not ghostty's
// native state), so this is safe to run concurrently with the still-live render loop. A
// SIGKILL of jprototerm bypasses hooks entirely; nothing can help there.
Runtime.getRuntime().addShutdownHook(new Thread(compositor::terminateSessions, "shell-cleanup"));
StackPane root = new StackPane(compositor.canvas(), compositor.imageOverlay());
compositor.canvas().widthProperty().bind(root.widthProperty());

View File

@@ -423,4 +423,14 @@ final class Tab implements AutoCloseable {
tiled.clear();
floating.clear();
}
/**
* Signals and reaps every pane's shell process without tearing down render state. Safe to call
* off the FX thread (see {@link TerminalPane#terminateSession()}); iterates snapshots so a
* concurrent close on the FX thread can't trigger a {@link java.util.ConcurrentModificationException}.
*/
public void terminateSessions() {
List.copyOf(tiled).forEach(TerminalPane::terminateSession);
List.copyOf(floating).forEach(TerminalPane::terminateSession);
}
}

View File

@@ -39,7 +39,7 @@ public final class TerminalPane implements AutoCloseable, RenderTarget {
// tracking meaningful: update() accumulates dirty since the last resetDirty().
private final RenderState renderState = new RenderState();
private RenderStateSnapshot cachedSnapshot;
private ShellSession session;
private volatile ShellSession session;
// Run once (on the FX thread) when this pane's process exits on its own, so the owning tab can
// remove it. Set by the Tab that creates the pane; null until then.
private Runnable onExit;
@@ -380,4 +380,17 @@ public final class TerminalPane implements AutoCloseable, RenderTarget {
renderState.close();
terminal.close();
}
/**
* Signals and reaps just the shell process, leaving the render/native state untouched. Unlike
* {@link #close()} this is safe to call off the FX thread — notably from a JVM shutdown hook,
* which runs concurrently with the live render loop — because it only touches the pty (a child
* process and fd), not ghostty's terminal handles. Idempotent; the OS reclaims the rest on exit.
*/
public void terminateSession() {
ShellSession current = session;
if (current != null) {
current.close();
}
}
}