diff --git a/src/main/java/com/gregor/jprototerm/LinuxPty.java b/src/main/java/com/gregor/jprototerm/LinuxPty.java index 02f3637..4427f4c 100644 --- a/src/main/java/com/gregor/jprototerm/LinuxPty.java +++ b/src/main/java/com/gregor/jprototerm/LinuxPty.java @@ -15,6 +15,8 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; /** * A Linux PTY backed by libc via the Foreign Function & Memory API. @@ -33,6 +35,11 @@ import java.util.Map; public final class LinuxPty implements AutoCloseable { static final Linker LINKER = Linker.nativeLinker(); private static final SymbolLookup LIBC = LINKER.defaultLookup(); + private static final ExecutorService REAPER = Executors.newCachedThreadPool(runnable -> { + Thread thread = new Thread(runnable, "pty-reaper"); + thread.setDaemon(true); + return thread; + }); static final AddressLayout C_POINTER = (AddressLayout) LINKER.canonicalLayouts().get("void*"); static final ValueLayout.OfShort C_SHORT = (ValueLayout.OfShort) LINKER.canonicalLayouts().get("short"); @@ -275,14 +282,43 @@ public final class LinuxPty implements AutoCloseable { @Override public void close() { - if (closed) { + if (!markClosed()) { return; } + closeMaster(); + try { + reap(); + } finally { + arena.close(); + } + } + + /** Send the configured close signal and close the master fd now; reap off the caller thread. */ + public void closeDetached() { + if (!markClosed()) { + return; + } + closeMaster(); + REAPER.submit(() -> { + try { + reap(); + } finally { + arena.close(); + } + }); + } + + private synchronized boolean markClosed() { + if (closed) { + return false; + } closed = true; + return true; + } + + private void closeMaster() { callKill(pid, closeSignal); callInt(CLOSE, masterFd); - reap(); - arena.close(); } private void reap() { diff --git a/src/main/java/com/gregor/jprototerm/ShellSession.java b/src/main/java/com/gregor/jprototerm/ShellSession.java index 826759e..dbafc96 100644 --- a/src/main/java/com/gregor/jprototerm/ShellSession.java +++ b/src/main/java/com/gregor/jprototerm/ShellSession.java @@ -180,8 +180,27 @@ public final class ShellSession implements AutoCloseable { @Override public void close() { - closed = true; + if (!markClosed()) { + return; + } reader.shutdownNow(); pty.close(); } + + /** Signal and disconnect the pty immediately, but leave child reaping to a background thread. */ + public void closeDetached() { + if (!markClosed()) { + return; + } + reader.shutdownNow(); + pty.closeDetached(); + } + + private synchronized boolean markClosed() { + if (closed) { + return false; + } + closed = true; + return true; + } } diff --git a/src/main/java/com/gregor/jprototerm/TerminalPane.java b/src/main/java/com/gregor/jprototerm/TerminalPane.java index 915477f..be67215 100644 --- a/src/main/java/com/gregor/jprototerm/TerminalPane.java +++ b/src/main/java/com/gregor/jprototerm/TerminalPane.java @@ -383,7 +383,7 @@ public final class TerminalPane implements AutoCloseable, RenderTarget { @Override public void close() { if (session != null) { - session.close(); + session.closeDetached(); session = null; } mouseEncoder.close(); diff --git a/src/main/java/com/gregor/jprototerm/TerminalWindow.java b/src/main/java/com/gregor/jprototerm/TerminalWindow.java index 3851181..a950b77 100644 --- a/src/main/java/com/gregor/jprototerm/TerminalWindow.java +++ b/src/main/java/com/gregor/jprototerm/TerminalWindow.java @@ -86,7 +86,7 @@ final class TerminalWindow { /** * Fully tears this window down (FX thread, idempotent): stops rendering, closes the compositor — - * which reaps the pane shells via the configured {@code close_signal} — disposes the Stage, and + * which signals pane shells via the configured {@code close_signal} — disposes the Stage, and * notifies the manager so it can drop the window (and, in standalone mode, exit the JVM). Both * the WM close button and the last-pane-closed hook route through here. */