perf(proxies): avoid stack capture with explicit ownership - #8
Merged
Conversation
Amp-Thread-ID: https://ampcode.com/threads/T-01a01745-fa3a-754d-a85c-052e07555f9c Co-authored-by: Upd4ting <upd4ting@gmail.com>
Amp-Thread-ID: https://ampcode.com/threads/T-01a01745-fa3a-754d-a85c-052e07555f9c Co-authored-by: Upd4ting <upd4ting@gmail.com>
Amp-Thread-ID: https://ampcode.com/threads/T-01a01745-fa3a-754d-a85c-052e07555f9c Co-authored-by: Upd4ting <upd4ting@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🔗 Linked issue
N/A
❓ Type of change
📚 Description
GetResponsibleModulecurrently changesError.prepareStackTraceandError.stackTraceLimit, captures an unlimited stack, then walks every frame for every provider/handler registration. This PR adds the smallest explicit ownership boundary:The context uses
AsyncLocalStorage, so nested contexts, thrown errors, promises, and async continuations retain the correct owner. Each loaded generation has an opaque token.ModuleDestroyedinvalidates that token before cleanup, making detached work from the destroyed generation fail withModuleContextInvalidatedErrorbefore it can mutate a proxy. A reload of the same module ID receives a fresh token; old nested or concurrent contexts remain invalid.GetResponsibleModulechecks explicit ownership first. Calls outside a context still execute the existing stack resolver unchanged, preserving backward compatibility and automatic detach/cleanup.No attribution is bypassed: proxy attachment and registration continue to store the resolved module ID, and
ModuleDestroyedcontinues to detach providers and remove registrations/events.Integration left for
@antelopejs/coreA later core change should wrap known module-owned boundaries (module evaluation and lifecycle callbacks) with
RunWithResponsibleModule(moduleId, callback). Nested module loads should establish nested contexts. Core must userun, not a process-wideenterWith, so concurrent module work remains isolated. This PR does not change another repository.Benchmark
Protocol:
dist),node --expose-gcAsyncLocalStorageinitializationbenchmarkownerImplementInterfacebootstrap fixtureAsyncProxy.onCallprovidersRegisteringProxy.onRegisterprovidersRegisteringProxy.registerhandlersEventProxy.registerhandlersImplementInterfacebootstrapNegative tiny heap deltas are GC noise. Larger registration workloads retain comparable heap to stack attribution. A separate 9-process cold-start measure puts the first ever context entry at 576.9 µs median (22.3 µs IQR); normal module work amortizes this once per process. Heap at this scale was below GC noise.
The post-change fallback remains 5,323.2 ns/op median (267.2 ns IQR), matching the 5,326.1 ns baseline and confirming that compatibility is preserved rather than short-circuited.
Tests
Validation:
📝 Checklist