Repository navigation
test: analyse factory-registered detector states mid-record path (#1215) - #1224
Merged
Merged
Conversation
analysisSurvivesAStateRegisteredButNotYetWritten builds a fresh state through newState, so it cannot reach the 18 per-instance detectors that register only through stateFor's factory overload: their state is built by a lambda at the record site. A detector among them whose analyze() throws on a published-but-unwritten state (the #1212 bug class) would pass every deterministic test and fail only on a rare race, when ifIssue then drops its whole report. AbstractInstanceDetector.register now calls a private static volatile test hook, set through a package-private setter, after computeIfAbsent publishes a state. Unset, it costs one volatile read on the registration path only, never on stateFor's get fast path; RunnerAllocationBudgetTest still passes. The new test installs the hook to run analyze() at that moment, drives every recording method of each per-instance detector once with a fresh first argument (plus hand drivers for ScopeResultEscapeDetector and VirtualThreadPoolingDetector, whose registration needs an opened scope or a real ThreadPoolExecutor), and fails on any throw. It reaches all 18 factory-only detectors (floor 15) and 68 of 75 per-instance ones. It found no live bug. Verified it detects one: with ThisEscapeDetector's empty-escapes guard replaced by escapes.iterator().next(), only the new test went red, naming "ThisEscapeDetector: inside recordConstructorEscape: java.util.NoSuchElementException"; the two existing tests stayed green. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
All 18 are reached today, so a floor of 15 would let three go quietly uncovered. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
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.
Closes #1215 (first bullet; the second is out of scope, see below).
Why
analysisSurvivesAStateRegisteredButNotYetWrittenbuilds a fresh state throughnewStateand runsanalyze()on it. That catches, every run instead of once in many, ananalyze()that throws on a state published but not yet written to. That's the #1212 bug class, whereifIssuethen drops the detector's whole report.It could not reach detectors that register only through
stateFor's factory overload. Their state is built by a lambda at the record site, so the test cannot make one without running the record path.What
AbstractInstanceDetector.register. A private static volatileConsumer, set through a package-private setter, runs right aftercomputeIfAbsentpublishes a state. When unset, it costs one volatile read, on the registration path only, never onstateFor'sgetfast path.RunnerAllocationBudgetTeststill passes.analysisSurvivesARecordPathBetweenRegistrationAndItsFirstWrite.analyze()at that moment and calls every recording method of each per-instance detector once, each with a fresh first argument so that every call registers a new state.ScopeResultEscapeDetectorneeds a scope opened first, andVirtualThreadPoolingDetectorneeds a realThreadPoolExecutor.AbstractInstanceDetectorTest: the hook fires once a first sighting is published, not on a lookup, and not once it's removed.docs/architecture/adding-a-detector.md:analyze()must survive a half-written state, and this test checks it.It found no live bug.
How it was verified
ThisEscapeDetector's empty-escapes guard replaced byescapes.iterator().next(), only the new test went red, namingThisEscapeDetector: inside recordConstructorEscape: java.util.NoSuchElementException. The two existing tests stayed green. The change was reverted afterwards.DetectorAnalysisUnderConcurrentWritesTest3/3,AbstractInstanceDetectorTest10/10 andRunnerAllocationBudgetTest2/2 (-P e2e) all pass.installwith PMD, checkstyle and errorprone passes.Not done
AbstractInstanceDetector(the issue's second bullet). Each keeps its state its own way, so there is no common seam to hook.Checklist
VIBETAGS-START/VIBETAGS-ENDmarkersCo-Authored-Bytrailer🤖 Generated with Claude Code