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.
4.9 KiB
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 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.logandregistered-ide.login the local evidence directory above; per-cycle logs are undertarget/connected-workflow/worker-lifecycle-*.log.
The native lifecycle issue is closed for this qualified baseline. The human Component-navigation review remains separate.