Repository navigation
Fix stub-lock deadlock in DynamicCodeGenerated #895
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -135,6 +135,11 @@ | |
| X(WALKVM_STUB_INFO_HIT, "walkvm_stub_info_hit") \ | ||
| X(WALKVM_STUB_INFO_FALLBACK, "walkvm_stub_info_fallback") \ | ||
| X(WALKVM_STUB_INFO_REGISTRATION_FAILED, "walkvm_stub_info_registration_failed") \ | ||
| /* Runtime-stub lookups (JitCodeCache::findRuntimeStub/findRuntimeStubInfo) \ | ||
| * that returned "not found" without searching because the shared stubs \ | ||
| * lock was held exclusively by a registration or the shared spin budget \ | ||
| * ran out; the stack walk continued with its stub-not-found fallback. */ \ | ||
| X(STUB_LOOKUP_LOCK_UNAVAILABLE, "stub_lookup_lock_unavailable") \ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Optional: up to you, not blocking. This counter combines "a writer held the lock" (an expected, short registration window) with "the spin budget ran out" (reader contention). If you switch to the no-argument |
||
| X(WALKVM_FP_CHAIN_ATTEMPT, "walkvm_fp_chain_attempt") \ | ||
| X(WALKVM_FP_CHAIN_REACHED_CODEHEAP, "walkvm_fp_chain_reached_codeheap") \ | ||
| X(WALKVM_ANCHOR_NOT_IN_JAVA, "walkvm_anchor_not_in_java") \ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -761,9 +761,9 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex | |
| } | ||
| } | ||
|
|
||
| // One shared-lock acquisition returns the stub view plus the | ||
| // precomputed aarch64 unwind info (null on x64): the signal | ||
| // handler still takes _stubs_lock exactly once. | ||
| // At most one bounded shared-lock attempt returns the stub view | ||
| // plus the precomputed aarch64 unwind info (null on x64). When | ||
| // the lookup fails, the nmethod's own start and name are used. | ||
| const void* start; | ||
| const char* name; | ||
| const StubUnwindInfo* stub_info = nullptr; | ||
|
|
@@ -773,9 +773,10 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex | |
| name = nm->name(); | ||
| } | ||
| #else | ||
| CodeBlob* stub = JitCodeCache::findRuntimeStub(walk_pc.raw()); | ||
| start = stub != NULL ? stub->_start : nm->code(); | ||
| name = stub != NULL ? stub->_name : nm->name(); | ||
| if (!JitCodeCache::findRuntimeStub(walk_pc.raw(), &start, &name)) { | ||
| start = nm->code(); | ||
| name = nm->name(); | ||
| } | ||
| #endif | ||
|
|
||
| if (details) { | ||
|
|
@@ -1170,10 +1171,7 @@ int HotspotSupport::getJavaTraceAsync(void *ucontext, ASGCT_CallFrame *frames, | |
| #ifdef __aarch64__ | ||
| if (JitCodeCache::findRuntimeStubInfo((const void *)frame.pc(), &stub_start, &stub_name, &stub_info)) { | ||
| #else | ||
| CodeBlob *stub = JitCodeCache::findRuntimeStub((const void *)frame.pc()); | ||
| if (stub != NULL) { | ||
| stub_start = stub->_start; | ||
| stub_name = stub->_name; | ||
| if (JitCodeCache::findRuntimeStub((const void *)frame.pc(), &stub_start, &stub_name)) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A failed lookup also changes how the frame is unwound When this returns false because the lock is busy, the code falls through to the So a busy lock can produce a wrong caller frame, not just a missing or less specific frame name. Inside the short registration window that is acceptable, but the reader-contention case in the other comment makes it happen more often. Fixing that comment is enough. It would still be worth saying in the PR description and the header comment that the fallback is a degraded unwind. |
||
| #endif | ||
| if (cstack != CSTACK_NO) { | ||
| max_depth -= makeFrame(trace.frames++, BCI_NATIVE_FRAME, stub_name); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Optional: up to you, not blocking.
A design point to consider. The bounded readers alone already break the deadlock, so
prepareAdd()/commitAdd()plus the second mutex are about keeping the exclusive window short. I think that's worth keeping: without it, a writer blocked in malloc holds the lock exclusively for an unbounded time, every stub lookup fails during that time, andruntimeStubsMemoryUsage()waits behind it.The rules, though, are enforced only by debug asserts: exactly one
commitAdd()perprepareAdd(), nothing changing the cache in between, the caller deletes the returned array after unlocking, and writers are serialized externally. To make misuse harder,PreparedAddcould be an RAII type that frees its name copy and grown array if it is never committed.