-
Notifications
You must be signed in to change notification settings - Fork 387
fix: park the native scan loop instead of busy-polling while waiting on native I/O #6219
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
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
013ac01
fix: park the native scan loop instead of busy-polling while waiting …
mixermt 25c70ae
fix: wake the scan streams on refill and park the loop without a timeout
mixermt c29893e
fix: bound the sleep-based loop test and correct the threading notes
mixermt 88cd407
fix: skip the park after a pull that made a JNI call
andygrove 398b3f9
fix: notice a wake-up with a flag of the loop's own, not the pull's r…
andygrove b83dce3
docs: say why next_batch runs on_pending inside block_in_place
andygrove File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
Oops, something went wrong.
Oops, something went wrong.
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.
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.
Skipping the park after a pull is correct for the nesting we know about. It depends on every call that can run another Comet plan on this thread reporting itself through
on_pending's return value, though. The closure inexecutePlanalso callsupdate_metrics_on_interval, which calls into the JVM and doesn't report it. That's safe today because a metrics update doesn't run a plan, but nothing in the code enforces it, and a future JNI call in the closure would bring the hang back without failing a test. The pull isn't the only way into the JVM from this thread either. The threading section ofdevelopment.mdnotes that memory pool operations callacquireMemory()over JNI on whatever thread the operator runs on, and on this path that's inside the stream poll, where no return value fromon_pendingcan report it. Spark can make other consumers in the task spill to satisfy that request. Every Cometspill()returns 0 today, so this doesn't nest a plan now, but the loop's safety still depends on that staying true.The lost wake comes from tokio keeping one wake token per thread.
CachedParkThread::block_onpolls and then parks on the thread-localCURRENT_PARKERwith no per-call state (park.rs), while theblock_in_placedocs presentHandle::block_oninsideblock_in_placeas supported. The stdWakedocs call out this case for theirblock_onexample: "production-grade implementations will also need to handle intermediate calls tothread::unparkas well as nested invocations."Could
next_batchtrack its own wake-ups instead? It can poll the stream with a waker that sets a flag owned by this call and then forwards to theblock_onwaker. After the pull, it returnsPendingonly if the flag is still unset. A nestedblock_oncan consume the thread's token but not the flag, so the loop no longer needs to know which calls can nest, andget_next_batchcan keep theResult<(), CometError>signature from #6092. The cost is oneArcpernext_batchcall, which is once per output batch rather than once per poll. Roughly:This also replaces
park_until_woken. I tried it in a local checkout of this branch (keeping theboolclosures and ignoring the value). The threenext_batchtests andrefill_wakes_the_pending_poll_and_eof_stays_bufferedpass. I also changed the nestedblock_ontest's closure to returnOk(false), which is a nested plan that doesn't get reported. The sketch still returns in 0.10 s. The head commit fails after 10.0 s with "the park after the pull lost the stream's wake-up". If you go this way, the nested test's closure could returnOk(()), so that it covers a nested plan without depending on how the pull reports it. The paragraph this PR adds todevelopment.mdwould then describe the flag instead of the rule about skipping the park after a JNI call.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.
Thanks, this is a better design, and I've switched to it in 398b3f9.
get_next_batchandpull_input_batchesreturn()again, the nestedblock_ontest's closure returnsOk(()), and thedevelopment.mdparagraph describes the flag.One change from your sketch: when the flag is set,
next_batchwakes theblock_onwaker and returnsPendinginstead of looping inside thepoll_fn, so each poll starts with a fresh coop budget. A stream that has spent its budget wakes itself and returnsPending, which sets the flag. The loop only gets past that becauseblock_in_placeleaves the budget unconstrained on a thread that isn't a tokio worker, since itsResetguard restores the budget only when there's a worker context. In a standalone tokio 1.53.1 program withblock_in_placetaken out, so the spent budget stayed spent, the loop re-polled more than a million times without progress, and the yielding version finished in 8 polls.On
acquireMemoryinside the poll: a nested plan can't run there, because tokio panics with "Cannot start a runtime from within a runtime" on ablock_onoutsideblock_in_place. A spill that ran a Comet plan would error out rather than hang, soon_pendingis the only place a nested plan can run today. If that changes, the flag also records a wake that arrives during the poll.I also moved the elapsed-time assertion into the shared test helper, because the timeout's timer polls the future again when it fires, and that can finish it. Without the check, a
WakeFlagthat didn't forward toblock_onpassed the native I/O test in 10 s.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.
I checked the new version locally. With the
flag.wokencheck forced to false,next_batch_polls_again_when_a_nested_block_on_took_the_wake_upfails after 10.0 s with "a wake was lost, and only the timeout's timer woke the task", and at the head commit it passes.That matches what I see. With
block_in_place(&mut on_pending)replaced by a plainon_pending(), the same test panics with "Cannot start a runtime from within a runtime", so a nested plan inside the poll errors out rather than hanging.