diff --git a/src/main/java/dev/jlibghostty/Terminal.java b/src/main/java/dev/jlibghostty/Terminal.java index d19b9cc..1ed3f6e 100644 --- a/src/main/java/dev/jlibghostty/Terminal.java +++ b/src/main/java/dev/jlibghostty/Terminal.java @@ -11,8 +11,9 @@ import java.util.concurrent.atomic.AtomicBoolean; public final class Terminal implements AutoCloseable { private final GhosttyLibrary library; private final MemorySegment handle; - private final Arena callbackArena = Arena.ofShared(); private final AtomicBoolean closed = new AtomicBoolean(); + private Arena ptyWriterArena; + private Arena deviceAttributesArena; private MemorySegment ptyWriterStub = MemorySegment.NULL; private MemorySegment deviceAttributesStub = MemorySegment.NULL; private PtyWriter ptyWriter; @@ -40,31 +41,51 @@ public final class Terminal implements AutoCloseable { public void setPtyWriter(PtyWriter writer) { ensureOpen(); ptyWriter = writer; + + // Each upcall stub lives in its own arena so that reassigning (or clearing) the + // writer can free the previous stub. The native pointer is repointed before the old + // arena is closed, so native never holds a dangling stub. + Arena previous = ptyWriterArena; if (writer == null) { ptyWriterStub = MemorySegment.NULL; + ptyWriterArena = null; library.terminalSetPointer(handle, GhosttyLibrary.TERMINAL_OPT_WRITE_PTY, MemorySegment.NULL); - return; + } else { + Arena arena = Arena.ofShared(); + ptyWriterStub = library.upcallPtyWriter(writer, arena); + ptyWriterArena = arena; + library.terminalSetPointer(handle, GhosttyLibrary.TERMINAL_OPT_WRITE_PTY, ptyWriterStub); } - ptyWriterStub = library.upcallPtyWriter(writer, callbackArena); - library.terminalSetPointer(handle, GhosttyLibrary.TERMINAL_OPT_WRITE_PTY, ptyWriterStub); + if (previous != null) { + previous.close(); + } } public void setDeviceAttributesProvider(DeviceAttributesProvider provider) { ensureOpen(); deviceAttributesProvider = provider; + + // See setPtyWriter: a per-stub arena lets reassignment free the previous stub. + Arena previous = deviceAttributesArena; if (provider == null) { deviceAttributesStub = MemorySegment.NULL; + deviceAttributesArena = null; library.terminalSetPointer(handle, GhosttyLibrary.TERMINAL_OPT_DEVICE_ATTRIBUTES, MemorySegment.NULL); - return; + } else { + Arena arena = Arena.ofShared(); + deviceAttributesStub = library.upcallDeviceAttributesProvider(provider, arena); + deviceAttributesArena = arena; + library.terminalSetPointer( + handle, + GhosttyLibrary.TERMINAL_OPT_DEVICE_ATTRIBUTES, + deviceAttributesStub + ); } - deviceAttributesStub = library.upcallDeviceAttributesProvider(provider, callbackArena); - library.terminalSetPointer( - handle, - GhosttyLibrary.TERMINAL_OPT_DEVICE_ATTRIBUTES, - deviceAttributesStub - ); + if (previous != null) { + previous.close(); + } } public String text() { @@ -156,7 +177,14 @@ public final class Terminal implements AutoCloseable { public void close() { if (closed.compareAndSet(false, true)) { library.terminalFree(handle); - callbackArena.close(); + if (ptyWriterArena != null) { + ptyWriterArena.close(); + ptyWriterArena = null; + } + if (deviceAttributesArena != null) { + deviceAttributesArena.close(); + deviceAttributesArena = null; + } } }