fix memory leak

This commit is contained in:
2026-06-02 03:26:24 +02:00
parent 06a9d5d3ec
commit 1cd908e5d0

View File

@@ -11,8 +11,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
public final class Terminal implements AutoCloseable { public final class Terminal implements AutoCloseable {
private final GhosttyLibrary library; private final GhosttyLibrary library;
private final MemorySegment handle; private final MemorySegment handle;
private final Arena callbackArena = Arena.ofShared();
private final AtomicBoolean closed = new AtomicBoolean(); private final AtomicBoolean closed = new AtomicBoolean();
private Arena ptyWriterArena;
private Arena deviceAttributesArena;
private MemorySegment ptyWriterStub = MemorySegment.NULL; private MemorySegment ptyWriterStub = MemorySegment.NULL;
private MemorySegment deviceAttributesStub = MemorySegment.NULL; private MemorySegment deviceAttributesStub = MemorySegment.NULL;
private PtyWriter ptyWriter; private PtyWriter ptyWriter;
@@ -40,26 +41,41 @@ public final class Terminal implements AutoCloseable {
public void setPtyWriter(PtyWriter writer) { public void setPtyWriter(PtyWriter writer) {
ensureOpen(); ensureOpen();
ptyWriter = writer; 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) { if (writer == null) {
ptyWriterStub = MemorySegment.NULL; ptyWriterStub = MemorySegment.NULL;
ptyWriterArena = null;
library.terminalSetPointer(handle, GhosttyLibrary.TERMINAL_OPT_WRITE_PTY, MemorySegment.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); if (previous != null) {
library.terminalSetPointer(handle, GhosttyLibrary.TERMINAL_OPT_WRITE_PTY, ptyWriterStub); previous.close();
}
} }
public void setDeviceAttributesProvider(DeviceAttributesProvider provider) { public void setDeviceAttributesProvider(DeviceAttributesProvider provider) {
ensureOpen(); ensureOpen();
deviceAttributesProvider = provider; deviceAttributesProvider = provider;
// See setPtyWriter: a per-stub arena lets reassignment free the previous stub.
Arena previous = deviceAttributesArena;
if (provider == null) { if (provider == null) {
deviceAttributesStub = MemorySegment.NULL; deviceAttributesStub = MemorySegment.NULL;
deviceAttributesArena = null;
library.terminalSetPointer(handle, GhosttyLibrary.TERMINAL_OPT_DEVICE_ATTRIBUTES, MemorySegment.NULL); library.terminalSetPointer(handle, GhosttyLibrary.TERMINAL_OPT_DEVICE_ATTRIBUTES, MemorySegment.NULL);
return; } else {
} Arena arena = Arena.ofShared();
deviceAttributesStub = library.upcallDeviceAttributesProvider(provider, arena);
deviceAttributesStub = library.upcallDeviceAttributesProvider(provider, callbackArena); deviceAttributesArena = arena;
library.terminalSetPointer( library.terminalSetPointer(
handle, handle,
GhosttyLibrary.TERMINAL_OPT_DEVICE_ATTRIBUTES, GhosttyLibrary.TERMINAL_OPT_DEVICE_ATTRIBUTES,
@@ -67,6 +83,11 @@ public final class Terminal implements AutoCloseable {
); );
} }
if (previous != null) {
previous.close();
}
}
public String text() { public String text() {
return format(TerminalFormat.PLAIN); return format(TerminalFormat.PLAIN);
} }
@@ -156,7 +177,14 @@ public final class Terminal implements AutoCloseable {
public void close() { public void close() {
if (closed.compareAndSet(false, true)) { if (closed.compareAndSet(false, true)) {
library.terminalFree(handle); library.terminalFree(handle);
callbackArena.close(); if (ptyWriterArena != null) {
ptyWriterArena.close();
ptyWriterArena = null;
}
if (deviceAttributesArena != null) {
deviceAttributesArena.close();
deviceAttributesArena = null;
}
} }
} }