Keep Glass attached through target-worker shutdown
Early Platform.exit detached JavaFX while queued macOS activation callbacks still called Java. Close the owned example before terminating its JVM, make shutdown idempotent, and reject late preview work. Add real startup-cancellation regressions and native crash-report checks.
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
# Native target-worker shutdown correction
|
||||
|
||||
The stage-3 JavaFX 21 worker crash was reproduced with the actual `PreviewWorker` transport and immutable classpath snapshot. This is a worker lifecycle correction; neither the IntelliJ JavaFX 26 toolkit nor NeitzelLib needs a patch.
|
||||
|
||||
## Reproduction and diagnosis
|
||||
|
||||
Cancelling the `full` interactive example during startup produced `SIGSEGV`, exit 134, and the original `libglass.dylib` frame (`JNI_OnLoad+0x11d0`). Worker output identified the actual callback: `Java has been detached already` in `GlassApplication applicationWillUnhide:`. The committed regression failed at iteration 8, cancelling 100 ms after sending the request. Earlier probes also reproduced it at 150 ms.
|
||||
|
||||
The old shutdown path queued `Platform.exit()` while the worker remained alive for up to its 1.5-second shutdown deadline. macOS could deliver a queued application activation notification after Glass detached its Java thread. The [OpenJFX 21 native callback](https://raw.githubusercontent.com/openjdk/jfx/jfx21/modules/javafx.graphics/src/main/native-glass/mac/GlassApplication.m) calls into Java when receiving this notification, consistent with both crash reports. The symbol shown by the native report is not evidence that the crash occurred in initial library loading.
|
||||
|
||||
The isolated experiment removed only the early toolkit exit and passed 40 worker cycles. One earlier run required the host's permitted forced-kill fallback during cancellation; that is distinct from a native crash. Forty ready-window closes, twenty canvas-only cancellations and twenty-five simple-example cancellations had passed before the fix; those narrower cases alone were insufficient regression coverage.
|
||||
|
||||
Local before/after evidence, including fatal reports, is retained in `/tmp/neitzel-native-crash-20261112/`. Temporary probes are outside production sources. The permanent regression is `WorkerLifecycleSmoke`, invoked for both launch arrangements by `scripts/check-connected-workflow.py` after building a fresh fixture snapshot.
|
||||
|
||||
## Shutdown behavior
|
||||
|
||||
- An atomic transition admits shutdown once, regardless of whether socket closure, owner EOF, a window close or an error triggers it.
|
||||
- Preview registration and shutdown coordinate so a newly constructed preview cannot escape ownership. Readiness and queued response callbacks stop creating/rendering windows once shutdown starts.
|
||||
- A ready preview closes on the FX thread, running its example cleanup and closing its window. The worker then terminates as one process, keeping Glass attached until termination; it does not call `Platform.exit()` first.
|
||||
- The independent 1.5-second watchdog still terminates a worker whose FX thread or application cleanup is unresponsive. The host's bounded forced-kill fallback remains in place. Snapshot leases remain held until actual process exit.
|
||||
- Discovery-only workers and cancellation before FX initialization terminate without starting application cleanup that has no corresponding session. Application shutdown hooks are not the preview cleanup contract: use the example session's cleanup hook, which still cannot be guaranteed after forced termination or owner/process death.
|
||||
|
||||
This is deliberately confined to disposable target JVMs. Normal JavaFX application shutdown in either editor host is unchanged.
|
||||
|
||||
## Regression and qualification
|
||||
|
||||
`WorkerLifecycleSmoke` uses actual target processes and authenticated requests, varying cancellation across 100, 150, 200 and 250 ms during full interactive startup, then closing ready previews. It checks process exit, rejects native fatal output and detached-thread warnings, and verifies the fixture's cleanup marker after ready close. Cancellation may use the documented forced-kill fallback; ready previews must exit normally and execute cleanup.
|
||||
|
||||
The connected suite runs 24 cycles per launch arrangement (48 total). The registered IntelliJ suite now waits for owned workers to exit after host shutdown. Both runners reject newly created JVM fatal-error reports, independently of UI success markers.
|
||||
|
||||
Qualification on 2026-11-12 passed on macOS arm64, host JDK 25 / JavaFX 26, target JDK 21 / JavaFX 21.0.7:
|
||||
|
||||
- Full bootstrap: 64 Maven tests, 7 IntelliJ adapter tests, bytecode/dependency/package checks and documentation validation.
|
||||
- 32 target-fixture processes, 402 assertions, plus 14 real Build and Refresh checks.
|
||||
- All 48 lifecycle cycles exited normally (no forced-kill fallback needed), including 8 ready-preview cleanup checks.
|
||||
- Connected standalone/IntelliJ workflows, native Kit checks, crash/retry and abrupt owner-death cleanup passed.
|
||||
- Both registered IntelliJ launch arrangements passed exclusive JavaFX 26 checks and worker shutdown verification.
|
||||
- No new fatal JVM report was produced. Logs are `bootstrap.log` and `registered-ide.log` in the local evidence directory above; per-cycle logs are under `target/connected-workflow/worker-lifecycle-*.log`.
|
||||
|
||||
The native lifecycle issue is closed for this qualified baseline. The human Component-navigation review remains separate.
|
||||
Reference in New Issue
Block a user