[GH-ISSUE #4753] [Bug] NativeAOT shared derivation outlives final waiters and session close #2476

Closed
opened 2026-10-17 21:05:22 +00:00 by bsakaguchi · 1 comment
Owner

Originally created by @morluto on GitHub (Oct 17, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/4753

Area

Native analysis or decompilation

Observed behavior

At73965f943, two controlled provider tests fail: closing a session returns while its NativeAOT snapshot derivation is still pending, and cancellation of the final waiting caller returns without aborting that derivation. An independent cross-operation failure-attribution control passes.

Reproduction

Executed three selected tests in src/ghidra/GhidraProvider.behavior.test.ts on73965f943:2failed/1passed,107others deselected. A deferred readTargetSnapshot is injected through the existing production GhidraProvider client factory. For close, begin inspect_native_load_image, wait for snapshot admission, call client.close, cross an event-loop phase: close has resolved and the underlying client close was called while the read is unresolved. For final departure, abort the only caller with an explicit object reason after snapshot admission: the source signal remains unaborted. Test teardown releases the deferred source and closes the client.

These are production provider-layer lifecycle controls with a simulated snapshot reader, not OS descriptor faults, real Ghidra, public CLI/MCP, or investigated executable execution.

Expected behavior

Per docs/tool-design.md#own-the-lifecycle: shared work keeps running for interested peers; final departure stops and joins the producer including cleanup; session close joins before resource release; exact caller reasons and independent producer/cleanup failures survive. Retained cleanup blocks replacement until its exact owner releases.

Impact and scope

Observed premature provider-layer completion in two controlled schedules. Source reasoning: createNativeAotSnapshotOwner uses a bare shared promise with independent abort races, clears pending on close without joining, and post-wait closed/aborted checks can replace producer errors (including cleanup). Natural failure incidence, real-provider impact and OS-resource leakage:NOT_ENOUGH_INFO; not measured. #4752 already repairs the physical snapshot reader and descriptor owner; this report concerns the separate NativeAOT derivation/waiter owner above it.

REA version / Runtime and platform

Source73965f9431eb64e6386949d308e67d629829796b,REA6.5.0; Node24.18.0,macOS arm64.

Target and artifact context

Tiny repository-authored2560-byte PE/RTR fixture. Fake installation/session facts and deferred captured bytes; no investigated target or engine execution.

Interface / Earliest failure stage

Other:shared provider implementation used by CLI/MCP / cleanup,cancellation,lifecycle.

Relevant output or evidence

joins the in-flight NativeAOT producer before closing its owning client:expected closed:false,received true.
joins final NativeAOT waiter cancellation and retains exact producer cleanup:expected sourceSignal.aborted:true,received false.
attributes shared NativeAOT producer failure to each requesting operation:passes.
Source:src/ghidra/GhidraProviderClient.ts:createNativeAotSnapshotOwner derive/close; provider close invokes owner.close synchronously before client.close.

Reuse the existing SharedStartupAttempt for waiter ownership, keep captured identity binding and immutable result reuse scoped to the session, retain source errors before interruption projection, and fence replacements behind live cleanup. Open/all-state NativeAOT,cancellation and snapshot/waiter issue searches plus open PR search reviewed. #4752 is the producing file reader; #4702 is Kuna cleanup retry; #4227 tracks general lifecycle contracts. NativeAOT budget issue#4445/PR#4559 is separate.

Originally created by @morluto on GitHub (Oct 17, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/4753 ### Area Native analysis or decompilation ### Observed behavior At73965f943, two controlled provider tests fail: closing a session returns while its NativeAOT snapshot derivation is still pending, and cancellation of the final waiting caller returns without aborting that derivation. An independent cross-operation failure-attribution control passes. ### Reproduction Executed three selected tests in src/ghidra/GhidraProvider.behavior.test.ts on73965f943:2failed/1passed,107others deselected. A deferred readTargetSnapshot is injected through the existing production GhidraProvider client factory. For close, begin inspect_native_load_image, wait for snapshot admission, call client.close, cross an event-loop phase: close has resolved and the underlying client close was called while the read is unresolved. For final departure, abort the only caller with an explicit object reason after snapshot admission: the source signal remains unaborted. Test teardown releases the deferred source and closes the client. These are production provider-layer lifecycle controls with a simulated snapshot reader, not OS descriptor faults, real Ghidra, public CLI/MCP, or investigated executable execution. ### Expected behavior Per docs/tool-design.md#own-the-lifecycle: shared work keeps running for interested peers; final departure stops and joins the producer including cleanup; session close joins before resource release; exact caller reasons and independent producer/cleanup failures survive. Retained cleanup blocks replacement until its exact owner releases. ### Impact and scope Observed premature provider-layer completion in two controlled schedules. Source reasoning: createNativeAotSnapshotOwner uses a bare shared promise with independent abort races, clears pending on close without joining, and post-wait closed/aborted checks can replace producer errors (including cleanup). Natural failure incidence, real-provider impact and OS-resource leakage:NOT_ENOUGH_INFO; not measured. #4752 already repairs the physical snapshot reader and descriptor owner; this report concerns the separate NativeAOT derivation/waiter owner above it. ### REA version / Runtime and platform Source73965f9431eb64e6386949d308e67d629829796b,REA6.5.0; Node24.18.0,macOS arm64. ### Target and artifact context Tiny repository-authored2560-byte PE/RTR fixture. Fake installation/session facts and deferred captured bytes; no investigated target or engine execution. ### Interface / Earliest failure stage Other:shared provider implementation used by CLI/MCP / cleanup,cancellation,lifecycle. ### Relevant output or evidence `joins the in-flight NativeAOT producer before closing its owning client`:expected closed:false,received true. `joins final NativeAOT waiter cancellation and retains exact producer cleanup`:expected sourceSignal.aborted:true,received false. `attributes shared NativeAOT producer failure to each requesting operation`:passes. Source:src/ghidra/GhidraProviderClient.ts:createNativeAotSnapshotOwner derive/close; provider close invokes owner.close synchronously before client.close. ### Analysis / Related work Reuse the existing SharedStartupAttempt for waiter ownership, keep captured identity binding and immutable result reuse scoped to the session, retain source errors before interruption projection, and fence replacements behind live cleanup. Open/all-state NativeAOT,cancellation and snapshot/waiter issue searches plus open PR search reviewed. #4752 is the producing file reader; #4702 is Kuna cleanup retry; #4227 tracks general lifecycle contracts. NativeAOT budget issue#4445/PR#4559 is separate.
Author
Owner

@morluto commented on GitHub (Oct 17, 2026):

Pushed 5120eb441 to main. Rebased over the disjoint packed-bridge verifier change f0d175dac; affected source hashes stayed unchanged, and the focused checks were rerun after rebase.

The new GhidraNativeAotSnapshotOwner replaces the private promise/abort races with SharedStartupAttempt. Peers cancel independently; final departure aborts and joins the producer; new callers wait for draining work before retrying. Session close joins derivation before closing the source client. Retained source cleanup blocks replacement and remains bound to its exact owner. Completed physical failures outrank later interruption. Named capture/derivation facts survive late cancellation and identity conflict, with current caller reasons preserved. The old private owner and its redundant state were removed from GhidraProviderClient.

Executed on the rebased revision:31 selected provider/NativeAOT checks (88 unrelated cases deselected),80 checks across shared attempts, named snapshot schema, error composition/projection and provider/CLI/SDK MCP diagnostics; typecheck/lint/builds and commit/pre-push hooks passed. Includes source close joining, final departure, exact null/string/Error reasons, peer isolation, transient retry, retained cleanup retry, draining callers, late physical failure, late capture facts, and changed-base invalidation. Two scoped source reviewers identified drain precedence/partial-fact gaps during implementation; both were fixed and regression controls passed.

Qualification:provider controls use the production provider plus simulated installation/session/snapshot factories and a tiny authored2560-byte PE/RTR fixture. CLI/MCP fault projection uses controlled session errors. No real Ghidra, investigated target, heavy CPU test, stress run or Flameox profiler was launched.

Equivalent complete summary→cached type detail→close tasks returned byte-identical2399-token full replies with one snapshot read. Baseline73965f943/candidate warm medians:elapsed117.1/116.4ms,CPU135.9/138.4ms,OS wait4 family peak101.8/102.0MB. This task has one Node process and no children; periodic RSS samples are incomplete estimates, while wait4 captures that known single-process family peak. Separate first processes and two alternating warm repeats; no true cold-cache or statistical performance claim. Whole-agent/catalog tokens, real provider, large inputs and fault-path costs are unmeasured. Owned baseline worktree was removed after checking identity, cleanliness and process absence.

Context7 primary Node AbortSignal docs and supplemental DeepWiki Node source research informed the ownership review. Node AbortSignal.any preserves the triggering reason; joining application cleanup remains an explicit owner responsibility. Broader PE section/overlay and load-image consumer audit remains ongoing.

<!-- gh-comment-id:6232908124 --> @morluto commented on GitHub (Oct 17, 2026): Pushed `5120eb441` to main. Rebased over the disjoint packed-bridge verifier change `f0d175dac`; affected source hashes stayed unchanged, and the focused checks were rerun after rebase. The new GhidraNativeAotSnapshotOwner replaces the private promise/abort races with SharedStartupAttempt. Peers cancel independently; final departure aborts and joins the producer; new callers wait for draining work before retrying. Session close joins derivation before closing the source client. Retained source cleanup blocks replacement and remains bound to its exact owner. Completed physical failures outrank later interruption. Named capture/derivation facts survive late cancellation and identity conflict, with current caller reasons preserved. The old private owner and its redundant state were removed from GhidraProviderClient. Executed on the rebased revision:31 selected provider/NativeAOT checks (88 unrelated cases deselected),80 checks across shared attempts, named snapshot schema, error composition/projection and provider/CLI/SDK MCP diagnostics; typecheck/lint/builds and commit/pre-push hooks passed. Includes source close joining, final departure, exact null/string/Error reasons, peer isolation, transient retry, retained cleanup retry, draining callers, late physical failure, late capture facts, and changed-base invalidation. Two scoped source reviewers identified drain precedence/partial-fact gaps during implementation; both were fixed and regression controls passed. Qualification:provider controls use the production provider plus simulated installation/session/snapshot factories and a tiny authored2560-byte PE/RTR fixture. CLI/MCP fault projection uses controlled session errors. No real Ghidra, investigated target, heavy CPU test, stress run or Flameox profiler was launched. Equivalent complete summary→cached type detail→close tasks returned byte-identical2399-token full replies with one snapshot read. Baseline73965f943/candidate warm medians:elapsed117.1/116.4ms,CPU135.9/138.4ms,OS wait4 family peak101.8/102.0MB. This task has one Node process and no children; periodic RSS samples are incomplete estimates, while wait4 captures that known single-process family peak. Separate first processes and two alternating warm repeats; no true cold-cache or statistical performance claim. Whole-agent/catalog tokens, real provider, large inputs and fault-path costs are unmeasured. Owned baseline worktree was removed after checking identity, cleanliness and process absence. Context7 primary Node AbortSignal docs and supplemental DeepWiki Node source research informed the ownership review. [Node AbortSignal.any](https://nodejs.org/docs/latest-v24.x/api/globals.html#static-method-abortsignalanysignals) preserves the triggering reason; joining application cleanup remains an explicit owner responsibility. Broader PE section/overlay and load-image consumer audit remains ongoing.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#2476
No description provided.