Repository navigation
Conversation
PendingReadOp.initiate() starts a speculative read task for each entry of the read, but kept them all in the single speculativeTask field, so completing the read cancelled only the task of the last entry. After a read of N entries, N-1 tasks stayed scheduled on the client scheduler: each ran speculativeReadTimeout (2 seconds by default) later and submitted a task to the ledger's ordered thread, which found the entry complete. BookKeeper.close() also waited for them, since the scheduler runs its delayed tasks after shutdown(). And for a read-only ledger handle, initiate() runs on the caller's thread while the read completes on the ledger's thread, so even the last task was missed when the read completed before initiate() stored it. Each entry now keeps its own speculative read task. Completing a PendingReadOp cancels the tasks of all its entries, and a ListenerBasedPendingReadOp (used by ledger recovery) cancels the task of each entry it hands to the listener. initiate() cancels the task it just started when the entry, or the whole read, completed meanwhile.
There was a problem hiding this comment.
🟡 Changes recommended
The regression test can leak its locally created client's resources if an assertion fails or execution is interrupted; try-with-resources is needed to ensure cleanup.
1 open issue
What changed in this PR
This PR fixes speculative-read task cleanup for multi-entry reads, preventing leftover tasks from delaying BookKeeper client shutdown.
Changes:
- Track and cancel speculative tasks per entry, including tasks registered concurrently with completion.
- Cancel each recovery-read entry’s task before delivering it to the listener.
- Add a regression test that checks scheduler termination after a multi-entry read.
| File | Description |
|---|---|
bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestSpeculativeRead.java |
Adds a multi-entry task-cancellation regression test. |
bookkeeper-server/src/main/java/org/apache/bookkeeper/client/PendingReadOp.java |
Tracks per-entry tasks and handles completion races during registration. |
bookkeeper-server/src/main/java/org/apache/bookkeeper/client/ListenerBasedPendingReadOp.java |
Cancels tasks as entries are removed for listener delivery. |
Review effort: Balanced
testSpeculativeReadTasksCancelledOnCompletion closes its own client in try-with-resources, so a failed assertion doesn't leave the client's threads and connections open.
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.

Problem
PendingReadOp.initiate()starts a speculative read task for every entry of the read and assigns each one to thesingle
speculativeTaskfield, so completing the read cancels only the last one. A read of N entries leaves N-1 taskson the client scheduler:
speculativeReadTimeout(2 seconds by default) later and submits a task to the ledger's ordered thread,which finds the entry complete;
ScheduledThreadPoolExecutor, which runs its delayedtasks after
shutdown():BookKeeper.close()right after a multi-entry read takes up tospeculativeReadTimeout.For a read-only handle,
initiate()runs on the caller's thread and the read completes on the ledger's thread, so theread can complete before
initiate()stores even the last task.Example
Change
Each
SingleLedgerEntryRequestkeeps its own speculative read task (a volatile field).PendingReadOp.cancelSpeculativeTask(), whichsubmitCallback()calls when the read completes, cancels the tasks ofall the entries.
ListenerBasedPendingReadOp(used by ledger recovery) removes each entry fromseqwhen it hands it to thelistener, so it cancels that entry's task there.
initiate()cancels the task it just started if the entry, or the whole read, completed meanwhile:The callback sets the completion flag before it reads the task, and
initiate()stores the task before it readsthe flag, so at least one of them cancels it.
ReadOpBase.getSpeculativeTask()now returnsnullfor aPendingReadOp.BatchedReadOp, which has a single task,is unchanged. A cancelled task still sits in the scheduler queue until its delay (the scheduler doesn't remove
cancelled tasks), but it no longer runs and no longer holds
close().Testing
TestSpeculativeRead.testSpeculativeReadTasksCancelledOnCompletion: with a 60 s speculative read timeout, reads10 entries through a read-only handle, shuts the client scheduler down and checks that it terminates within
10 seconds. Fails on master (
Speculative read tasks of the completed read are still scheduled): the tasks thatweren't cancelled keep the scheduler running until they fire.
org.apache.bookkeeper.clientpackage passes,client.apiincluded (82 classes, 674 tests): among themthe speculative, sequence, parallel and batched read tests and the recovery tests (
LedgerRecoveryTest,ParallelLedgerRecoveryTest,BookieRecoveryTest,TestReadEntryListener).