Conversation
utafrali
left a comment
There was a problem hiding this comment.
The fix correctly addresses the original test's flaw: running on a FastThreadLocalThread, proving instance identity with assertSame, covering all three create variants including the production read-path variant, and using a bounded warm-up that fails loudly rather than silently passing on fresh allocations. The production comment is now accurate about sequential-case-only guarantees. Two minor suggestions: join the thread after the future resolves, and prefer assertEquals over assertTrue(...compareTo(...) == 0) for actionable failure messages.
| }); | ||
| thread.start(); | ||
| // Rethrow assertion failures on the test thread. | ||
| completion.get(30, TimeUnit.SECONDS); |
There was a problem hiding this comment.
completion.get(30, TimeUnit.SECONDS) returns as soon as the future is resolved, but the thread is technically still alive until its run() returns. The gap is tiny, but adding thread.join() after completion.get(...) makes the lifecycle explicit and prevents any thread-leak warnings from strict test frameworks:
completion.get(30, TimeUnit.SECONDS);
thread.join();| // report the poisoned (-1, -1) position as its own. | ||
| EntryImpl second = secondEntry.get(); | ||
| assertSame(second, first, variant + ": expected the recycler to reuse the same instance"); | ||
| assertTrue(second.getPosition().compareTo(PositionFactory.create(expectedLedgerId, expectedEntryId)) == 0, |
There was a problem hiding this comment.
assertTrue(x.compareTo(...) == 0, msg) produces a failure message like expected true but got false. Swapping to assertEquals would print the actual and expected positions on failure, which is much more useful when debugging:
assertEquals(second.getPosition(), PositionFactory.create(expectedLedgerId, expectedEntryId),
variant + ": a recycled entry must not inherit the poisoned (-1, -1) position");The compareTo-based form is already used elsewhere in this file, so this isn't blocking, but it's worth changing in a test whose whole point is catching bad values.
… recycle Follow-up to apache#26707 addressing the post-merge review: the merged test ran on a plain test worker thread, which receives a no-op recycler handle on Netty 4.2 (pooling is FastThreadLocalThread-only), so every create() returned a fresh instance and the assertions held with or without the fix. - Run the scenario on a FastThreadLocalThread and prove instance identity with assertSame, guarded by a bounded warm-up that fails the test if the pool never hands the same instance back. - Cover create(LedgerEntry, int), the variant the managed-ledger read path actually uses, in addition to the byte[] and ByteBuf variants. - Reword the reset comment in EntryImpl.create(LedgerEntry, int): the reset closes the sequential poison-and-reuse case; it cannot protect against a getPosition() racing concurrently with create() (unsynchronized fields, no generation check) - post-release access remains a caller bug to fix at the call site. Red/green verified: removing the three position resets turns the test red (first failure on the create(LedgerEntry, int) variant); restoring them is green.
d7015a4 to
19d17ac
Compare
Motivation
The post-merge review of #26707 (thanks @void-ptr974) pointed out that the regression test added there does not actually exercise recycling: on Netty 4.2 the
Recycleronly pools instances forFastThreadLocalThreads — a plain thread receives a no-op handle, so everyget()allocates a fresh object. The merged test ran on an ordinary test worker thread and still passes with the threeentry.position = nullresets removed.The review also correctly noted that the comment above the reset overclaims: a
getPosition()concurrent withcreate()can still read the old ids and publish its stale value after the reset (unsynchronized fields, no generation check), so the reset is best-effort hardening for the sequential poison-and-reuse case — post-release access remains a caller bug to fix at the call site.Modifications
EntryImplTest.testRecycledObjectDoesNotInheritPoisonedPositionnow runs the poison-and-reuse scenario on aFastThreadLocalThreadand proves instance identity withassertSame, guarded by a bounded warm-up that fails the test if the per-thread pool never hands the same instance back (absorbsio.netty.recycler.ratiowarm-up behavior).create(...)variants, includingcreate(LedgerEntry, int)— the variant the managed-ledger read path actually uses.EntryImpl.create(LedgerEntry, int)to state the actual guarantee and its limits (sequential case only; concurrent post-release access is out of scope and must be fixed at the call site).No production behavior change — the three reset lines are untouched.
Verifying this change
./gradlew :managed-ledger:test --tests "org.apache.bookkeeper.mledger.impl.EntryImplTest"— 12/12 green.entry.position = nullresets temporarily removed, the new test fails (create(LedgerEntry, int): a recycled entry must not inherit the poisoned (-1, -1) position); restoring them is green.This change is a trivial rework: no production logic is modified beyond a comment.