fix(datafusion): hand off the worker before blocking in catalog callbacks - #891
Merged
JingsongLi merged 1 commit intoSep 21, 2026
Conversation
…acks A filtered information_schema query through the Python binding never returns. The plan has a RepartitionExec, so the metadata enumeration runs on a worker of the process runtime, and block_on_with_runtime blocks that worker with a spawned thread and join() in the middle of a poll. Two things are lost with the worker. Its LIFO slot, which Tokio does not steal from, holds the hyper connection task that was woken when the previous table() call dropped its response body. And when the worker was the last one parked on the I/O driver, nobody takes the driver over, so no I/O and no timers run. The helper thread waits for a request that is never written to the socket. Mark the threads of the process runtime and use block_in_place on them. It moves the LIFO slot to the run queue and hands the worker core to another thread before blocking. Every other caller keeps the thread-based path, because block_in_place panics inside a LocalSet and on a current-thread runtime, and Tokio has no way to ask whether it is allowed.
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.
Purpose
Linked issue: #872
A filtered
information_schemaquery through the Python binding never returns:The plan has a
RepartitionExec, so the metadata enumeration runs on a worker of the process runtime. The enumeration callsCatalogProvider::schema()on that worker.block_on_with_runtimethen blocks the worker with a spawned thread andjoin(), in the middle of a poll.Two things are lost with the worker:
table().awaitdropped its response body, and a push into an empty LIFO slot wakes no other worker.The helper thread waits for a request that is never written to the socket. gdb on the hung process shows
lifo_slot: Some(..)with the task inNOTIFIEDstate andhad_driver: Yeson the blocked worker.ssshows one established connection with empty queues.The number of workers does not matter. It hangs 10/10 on 2, 4 and 8 cores. With one core, a plan that spawns tasks hangs the same way, for example
UNION ALLover twoinformation_schemascans. Turning off the LIFO slot in the same binary makes it finish 10/10.In the Python binding no runtime is entered on the calling thread, so
runtime()is the process runtime. DataFusion and the helper thread use the same runtime, there is only one in this path.Brief change log
on_thread_start. On these threadsblock_on_with_runtimeusesblock_in_place. It moves the LIFO slot to the run queue and hands the worker core to another thread before blocking.block_in_placepanics inside aLocalSetand on a current-thread runtime, and Tokio has no API to ask whether it is allowed. This is the case raised in the review of fix(datafusion): preserve active runtime in blocking callbacks #873.rt-multi-threadTokio feature.Runtime::new()already needed it and only got it through feature unification.This does not close #872. A worker of a runtime owned by the caller still takes the thread-based path. #881 removes the blocking call from the discovery callbacks. This change fixes the bridge itself, so it also covers the callbacks that #881 leaves on it. The two only touch the same line in
Cargo.toml.Tests
runtime::tests::blocking_on_a_process_runtime_worker_keeps_its_queued_tasks_running: a task spawns a second task, which lands in the LIFO slot, then blocks on its result. It fails after 30s without the fix, on any number of cores.runtime::tests::callers_outside_the_process_runtime_keep_the_thread_based_path: aLocalSeton a multi-thread runtime (the root future and aspawn_localtask) and a current-thread runtime. With the approach of fix(datafusion): preserve active runtime in blocking callbacks #873 it panics withcan call blocking only when running on the multi-threaded runtime.tests/catalog_callbacks_on_runtime_workers.rs: a REST catalog queried from a plain thread, the way the Python binding does it, with the filtered query and aUNION ALLquery. Without the fix it fails on the filtered query with 8 cores and on theUNION ALLquery with 1 core.cargo fmt --all -- --checkcargo clippy --locked -p paimon-datafusion --all-targets -- -D warningscargo test --locked -p paimon-datafusion: 801 passed. 39 tests intable::testsandtests/read_tables.rsneed the shared test warehouse and fail locally withTableNotExist. The same 39 fail onmain.API and Format
No.
Documentation
No.