Repository navigation
[fix](arrow-flight) Cancel queries when result streams close early - #68630
Conversation
|
run buildall |
|
/review |
|
run buildall |
|
/review |
|
Codex automated review failed and did not complete. Error: Review recovery stopped: No final review submission was declared Please inspect the workflow logs and rerun the review after the underlying issue is resolved. |
There was a problem hiding this comment.
Complete static review of PR #68630 at head 2e0bb21. Four new findings are inline: one P1 and three P2. There were no existing inline threads or previously reported P0/P1 findings. The review converged in three rounds, including full coverage and separate risk scans; every candidate was verified, deduplicated, accepted, or dismissed.
Critical checkpoints:
- Goal and proof: early Flight stream close/cancel reaches a single local or current-version remote result buffer, and explicit EOS avoids confusing an empty wait with completion. The new BE tests and Groovy regression cover several close, cancellation, error, and normal-EOF paths, but the four inline cases prevent the broader cleanup goal from holding. No builds or tests were run in this review, as required by the review prompt; author and CI claims were not treated as independent validation.
- Scope: changes are focused on Flight readers, result buffers, the internal fetch RPC, and tests. No unnecessary source changes were identified.
- Concurrency: traced Flight DoGet, pipeline producers, the buffer-map and buffer locks, parked brpc fetch callbacks, and the bounded Flight work pool. Query cancellation runs outside the map and buffer locks; no separate lock-order deadlock or pending-fetch completion error was substantiated. The rejected cleanup RPC in M2 remains a concurrency/load failure.
- Lifecycle and static initialization: checked reader Close/destruction, callback ownership, and weak QueryContext ownership. The Flight call context remains alive through stream destruction; no new cross-translation-unit static initializer was added. M1 and M4 identify incomplete buffer and cross-BE cleanup after local query lifecycle changes.
- Configuration: no configuration item was added. The existing timeout cleanup can leave the resources in M1, M3, and M4 until timeout.
- Compatibility and transmitted fields: the optional BE internal cancel flag defaults false for ordinary fetches, but a new proxy talking to an older result BE loses cancellation during rolling upgrade (M3). No new FE-to-BE session variable was added.
- Parallel paths and conditions: checked local and remote readers, parallel query-ID buffers and nonparallel instance-ID buffers, success/error closes, and explicit EOF. M1 affects sibling instance buffers; M4 affects finished and active endpoints across BEs; M2 misses the application error returned on work-pool rejection.
- Tests and expected results: the new tests cover single-buffer local and remote flows. The regression uses numbers(), which schedules one task, so it does not exercise multi-endpoint cleanup; overload and mixed-version cases are also untested. No generated .out result was changed. Test results were not independently checked by execution.
- Observability: transport failures are logged and Flight work-pool metrics exist, but M2's successful-transport application failure is not reported by the proxy. Existing logs provide query and buffer IDs on the other paths.
- Transactions, persistence, and data writes: not applicable; this PR changes result delivery and cancellation, not stored data or EditLog behavior.
- Performance and other risks: the 20 ms local poll repeats dependency checks on empty waits; this was reviewed and did not justify a standalone finding. No additional substantiated issue remained after the final sweep.
User focus: no additional focus points were supplied. Review execution is complete; the P1 finding requests changes.
| cancel(reason); | ||
| // The result buffer can be keyed by a fragment instance id rather than the query id. | ||
| // Keep only a weak query reference, and cancel outside the buffer/map locks. | ||
| if (auto query_ctx = _query_ctx.lock()) { |
There was a problem hiding this comment.
[P1] Send stream abort to the query coordinator after a local fragment finishes. In parallel result mode, BE A can finish its fragment while BE B is still producing, leaving A's unread Flight buffer registered. A's fragment is then removed and the buffer holds only a weak QueryContext reference. If the client reads one batch from A and closes before EOF, _query_ctx.lock() can fail here; even a surviving context has no active local fragment to report a new error to FE. BE B therefore receives no cancellation and can remain active or backpressured until timeout. Propagate the abort through a query-level route that remains usable after the selected result fragment has finished, and cover a multi-BE query with one early-finishing endpoint.
There was a problem hiding this comment.
Fixed in 59cbfb7. Stream aborts now reach the owning FE through a result-ID route and fan out the existing query cancellation RPC to every participating BE. The route retains only IDs and addresses, so it survives both local fragment completion and normal FE coordinator unregistration without holding the coordinator or its queue slot. Added BE coverage for an expired local context and a finished endpoint, FE coverage for multiple backend addresses without a live coordinator, and a distributed regression that aborts only one endpoint. The updated cluster regression is pending CI.
| if (!buffer) { | ||
| return; | ||
| } | ||
| _buffer_map.erase(it); |
There was a problem hiding this comment.
[P2] Cancel sibling Flight buffers when aborting a query. In nonparallel result-sink mode, each instance has its own buffer ID and Nereids can advertise multiple endpoints on one BE. Closing one stream erases only its buffer here, then cancels the shared QueryContext. The other sinks close with an error, but ResultBlockBuffer::close leaves their queued Blocks intact and ResultSinkLocalState::close returns before scheduling deferred cleanup. Those buffers retain unread result memory until the original execution timeout. Cancel all Flight buffers for the query (or otherwise promptly clear and remove sibling buffers) when one endpoint aborts.
There was a problem hiding this comment.
Fixed in 59cbfb7. ResultBufferMgr now indexes all Flight buffers by their actual query ID and removes/clears every sibling on query abort, outside the map lock and independently of QueryContext lifetime. The existing cancel_plan_fragment handler also invokes this cleanup on each updated BE. Added a test that expires the context before closing one endpoint and verifies that a sibling buffer and its queued Blocks are removed. LIMIT_REACH and FINISHED preserve unread results, with dedicated coverage.
| closure->response_.get(), closure.get()); | ||
| closure.release(); | ||
| callback->join(); | ||
| if (callback->cntl_->Failed()) { |
There was a problem hiding this comment.
[P2] Handle a rejected remote cancellation. fetch_arrow_data puts this cancel request into the bounded Arrow Flight work pool. If the queue is full, offer_failed returns a protobuf CANCELLED status while the brpc controller itself succeeds. This code checks only cntl_->Failed(), so the reader remains closed even though the result BE never cancelled its buffer or query; a backpressured producer can stay alive until timeout. Check the response status and provide a cleanup path that still succeeds when this pool rejects work.
There was a problem hiding this comment.
Fixed in 59cbfb7. Removed cancellation from fetch_arrow_data entirely. The FE route uses cancel_plan_fragment, which bypasses the Arrow Flight work pool. Both transport and application statuses are checked; failed routes remain available for a bounded retry. Added BE tests with the Arrow pool rejecting work and an FE application error, plus FE coverage for a rejected backend cancellation followed by a successful retry.
|
|
||
| message PFetchArrowDataRequest { | ||
| optional PUniqueId finst_id = 1; | ||
| optional bool cancel = 2 [default = false]; |
There was a problem hiding this comment.
[P2] Preserve cancellation when the result BE is older. A new proxy BE can forward a Flight stream to an older result BE during a rolling upgrade. That BE does not know this newly added optional field, so it handles the cancel=true request as another ordinary fetch; with no ready batch it parks the RPC, and the proxy times out after one second while the producer remains blocked. Please use a compatible cancellation route or gate the behavior on result-BE capability so closing the stream does not leave the old BE's query running until timeout.
There was a problem hiding this comment.
Fixed in 59cbfb7 by removing the new fetch cancel field. With the serving FE and Flight-facing BE upgraded, cancellation now reaches older result BEs through their existing cancel_plan_fragment RPC, using the actual query ID from FE routing metadata. Added a legacy-handler compatibility test. Older result BEs still retain their historical unread-buffer reclamation policy; immediate sibling-buffer removal requires upgrading those BEs, as documented in the PR description.
|
run buildall |
|
/review |
There was a problem hiding this comment.
Complete static review of PR 68630 at head 59cbfb7f7c24696ecb5e61a8cf365d539be6e7e4 (base 76d173e17b94e1019fa3c7742a3c05b62a2373a6). Four new P2 findings are inline. All 18 changed paths were swept after three convergence rounds; the final round's normal BE, normal FE/test, and risk-focused reviews found no new valuable issue. No suspicious point remains without a disposition.
The change addresses the main goal for a registered route: unfinished Flight streams can cancel all participating BEs, clear sibling buffers on updated BEs, and preserve normal EOF and unread results after LIMIT_REACH/FINISHED. The four findings cover a route removed during session reuse, rejection of the replacement cancellation RPC, slow discovery of the route-owning FE, and the new BE test fixture's metric-hook lifetime. The previous P1 inline comment (4131669262) is fixed in its original finished-fragment scenario by the FE result-ID route; the route-lifetime finding here is a distinct new failure. The prior P2 threads on sibling buffers, Arrow read-pool rejection, and older result-BE protocol have corresponding fixes on this head. The documented older-BE unread-buffer cleanup limitation remains; none of those prior threads is duplicated here. No existing P0/P1 inline finding was independently confirmed still applicable.
Critical checkpoints: Goal and tests: new BE/FE tests and a distributed regression exercise normal completion, direct/proxy cancellation, expired contexts, and older result BEs; the active-ticket-across-next-statement case is missing (first finding). This review did not run builds or tests because the review prompt prohibits them; author validation and pending CI are not independent runtime proof. Scope and clarity: the change is focused on Flight cancellation, but session route ownership and FE discovery introduce the identified lifecycle and latency gaps. Concurrency and lifecycle: gRPC reads, brpc callbacks, BE work pools, FE routing, and the buffer manager can overlap. Map and query-index updates share the map lock; buffer/query cancellation runs after releasing it, with shared ownership for copied buffers. No additional lock-order, callback-lifetime, EOF race, or cross-translation-unit static-initialization defect was substantiated. Route lifetime, light-pool admission, and the test's global metric hook are the exceptions reported inline. Configuration and compatibility: no new dynamic configuration or storage format; the additive FE Thrift method uses the existing BE cancellation RPC, including older result BEs when the serving FE and Flight-facing BE are upgraded. Parallel and conditional paths: classic/Nereids, direct/proxy BE, and parallel/nonparallel endpoints were traced; backend snapshots cover dispatched tasks, and the LIMIT_REACH/FINISHED guards preserve valid unread results. Observability and performance: cancellation errors log result/FE identifiers; sequential FE probing can delay abort despite the nominal per-client timeout (third finding). Transactions, writes, visibility, MoW, persistence, and FE-to-BE session-variable propagation: none is changed by this PR. Test artifacts: no .out results were changed; the new regression's query-ID diagnostics and assertions were inspected statically. User focus file states no additional focus.
|
run buildall |
|
/review |
There was a problem hiding this comment.
Complete static review of apache/doris PR #68630 at head 08a7fb2 (base 76d173e). All 18 authoritative changed files and their upstream/downstream cancellation paths were reviewed. The three normal reviews and separate cross-service risk review converged after one round with NO_NEW_VALUABLE_FINDINGS. There are no new nonduplicate inline findings to add.
Existing review context: Four current P2 threads still apply and are not reposted: session reset removes routes for earlier valid BE tickets (4132182055); BE light-pool rejection can outlast the reader's bounded cancellation attempts (4132182068); synchronous FE discovery can delay cancellation (4132182080); and the BE test fixture can leave a global metrics hook pointing to a destroyed manager (4132182087). The earlier P1 on abort after local fragment completion (4131669262) is resolved on this head by the result-ID FE route and query-wide BE fanout, so no existing P0/P1 blocking comment ID is confirmed. Earlier sibling-buffer, Arrow fetch-pool, and older-result-BE threads are likewise addressed in this head, subject to the documented older-BE historical buffer policy.
Critical checkpoints:
- Goal and proof: Closing a Flight stream before EOF cancels its local buffer, finds the owning FE by result ID, and fans out the existing query-cancellation RPC to the dispatched BEs. BE buffer indexing clears sibling Flight buffers even after a local QueryContext expires. New BE and FE unit tests and a distributed regression cover key paths; they were reviewed statically, not run here.
- Scope and clarity: The 18 changed files cover the necessary BE reader/buffer/RPC, FE route/coordinator, Thrift, and tests. No unrelated source change or new configuration was identified.
- Concurrency and performance: Reader state uses atomics, pending BRPC reads use per-RPC callback state and join before release, the BE buffer map and query index share one lock, and query-wide cancellation releases that lock before touching buffers or QueryContext. FE routes use a concurrent cache; channel registration/reset are synchronized. No additional lock-order defect was substantiated. FE discovery latency and bounded retry under light-pool pressure remain the existing P2 concerns above.
- Lifecycle and compatibility: A weak local QueryContext does not retain a finished fragment; FE routing retains only IDs and addresses until cancellation, reset, or timeout. Early reset is the existing P2. The Thrift method is appended, and fanout uses the pre-existing cancel_plan_fragment RPC understood by older result BEs. Updated BEs clear sibling buffers; older BEs retain their previous unread-buffer reclamation behavior. No cross-translation-unit static initialization dependency was added. The test metrics hook lifecycle is the existing P2.
- Parallel and conditional paths: Legacy and Nereids coordinators supply dispatched BE addresses; parallel tickets carry the query ID, while nonparallel tickets carry instance IDs. Local and proxy readers, normal EOF versus abort, LIMIT_REACH/FINISHED versus error, and queued versus in-flight fetches were checked. The cancellation status checks both transport and application responses.
- Tests, observability, and other invariants: BE tests cover local/remote early close, pending fetch, sibling cleanup, expired context, normal EOF, retry, and legacy BE behavior. FE tests cover multi-BE fanout, application error retry, expiry, and reset; the regression targets one aborted endpoint. No build, test, or runtime validation was performed, as the review instructions prohibit it; test results and CI claims are not independent proof here. Existing logs identify failed FE/BE cancellation and query IDs. The change does not add persistence, transactions, data writes, storage-format changes, or FE-to-BE session variables; the data-visibility, version, delete-bitmap, and allocation checkpoints do not gain a new path in this PR. No additional correctness, performance, or coverage issue was substantiated.
User focus: No additional focus was provided. Review status: COMPLETE static review, with the four existing P2 threads above still open.
Cloud UT Coverage ReportIncrement line coverage Increment coverage report
|
BE UT Coverage ReportIncrement line coverage Increment coverage report
|
BE Regression && UT Coverage ReportIncrement line coverage Increment coverage report
|
|
PR approved by anyone and no changes requested. |
|
PR approved by at least one committer and no changes requested. |
…68630) ## What problem does this PR solve? Cancelling or closing an unfinished Arrow Flight stream stops the client RPC but can leave result producers blocked until timeout. Propagate stream aborts to the whole query and promptly release its Flight buffers, while preserving normal EOF and valid unread results after LIMIT_REACH or FINISHED. Local reads use bounded buffer waits with explicit EOF; the reader owns cancellation checks. Remote reads interrupt outstanding BRPC waits. A BE reports an aborted result ID to the owning FE, which sends the existing cancel_plan_fragment RPC to every participating BE. Lightweight result-ID routing metadata survives local fragment and coordinator completion without retaining the coordinator or its query queue slot; session reset and execution-timeout expiry remove those routes. RPC transport and application errors are checked, with bounded retries. Cancellation bypasses the Arrow read pool. On updated BEs, a query-to-buffer index removes all sibling Flight buffers, even after their local QueryContext expires. Query cancellation occurs outside buffer-map locks. A fetch racing with buffer removal completes its RPC with an error. ## Validation - 18 BE tests passed in 10 consecutive incremental ASAN runs (180 executions), including real Flight, Thrift and BRPC transport paths, expired contexts, sibling cleanup, rejected Arrow work, application-error retry, a legacy cancellation handler, and successful fragment-stop preservation. - Four FE tests passed for cancellation across backend addresses after coordinator completion, application-error retry, expiry and session cleanup. Changed FE sources and generated Thrift code compiled against cached dependencies. - FE Checkstyle, clang-format 16 and git diff whitespace checks passed. The Groovy regression compiled against the regression framework. - The cluster regression uses a distributed table, aborts only one endpoint, checks that the query's pipelines disappear from every BE within 20 seconds, then checks connection reuse and normal completion. Full execution on rebuilt FE/BE binaries remains pending CI; local validation used focused incremental builds. ## Upgrade behavior Upgrade the serving FE and Flight-facing BE to enable the new cancellation route. Result BEs can still be older: the FE uses their existing query cancellation RPC rather than an unknown fetch flag. Older result BEs retain their historical unread-buffer reclamation behavior until upgraded; immediate sibling-buffer reclamation requires the updated BE.
…stream closes early ### What problem does this PR solve? Issue Number: None Related PR: apache#68630, apache#67644 Problem Summary: Port the BE-local half of branch-4.x apache#68630 to master. An Arrow Flight DoGet stream that the client cancels or closes before EOF left its query running. The local reader waited on an empty result buffer without looking at the RPC's cancellation state, and closing the stream did not cancel anything: the abandoned buffer kept its unread blocks until ResultBufferMgr dropped it, at execution_timeout + 5 s or result_buffer_cancelled_interval_time (300 s) after its fragments finished, and when the result did not fit it back-pressured the result sink until the query timed out. A remote reader blocked in a BRPC join that nothing could interrupt, an aborting cancel_plan_fragment left the query's unread Flight buffers in place until that delayed cleanup, and fetch_arrow_data for a buffer that was already gone never ran its done closure, so the caller waited for the BRPC timeout. The reader now checks ServerCallContext::is_cancelled between bounded 20 ms buffer waits and while a BRPC fetch is pending (cancelling it with brpc::StartCancel), and closes itself on Close(), on a read error and in its destructor. Closing a local reader before EOF cancels the query on this BE: ResultBufferMgr keeps a query -> Flight buffers index, removes every Flight buffer of the query and cancels its QueryContext outside the buffer-map lock, even after that context has expired. cancel_plan_fragment with an abort reason (not LIMIT_REACH or FINISHED) also drops the query's Flight buffers, and fetch_arrow_data completes the RPC with an error when the buffer is gone (the same line as apache#67644). The FE route that reaches the query's other BEs (cancelFlightQuery and the BE broadcast) is left to a follow-up PR. Differences from branch-4.x: the two index/map consistency checks are DORIS_CHECK, and the ArrowFlightResultBlockBuffer constructor moved to the .cpp so that varrow_flight_result_writer.h, which 13 TUs include, does not include runtime/query_context.h; only the .cpp needs its definition. ### Release note Arrow Flight SQL: cancelling or closing a result stream before its end now cancels the query on the BE that holds the stream's results and releases its unread results right away, instead of keeping them for minutes. ### Check List (For Author) - Test: Unit Test / Manual test - BE UT: ArrowFlightCancelTest (9, new), ArrowFlightRemoteCancelTest (3, new), ResultBufferMgrTest (6), ArrowResultBlockBufferTest (4) - Manual test on a local single-BE cluster: branch-4.x's test_flight_cancel_cleanup fails before and passes after; arrow_flight_sql_p0 and external_table_p0/remote_doris pass - Behavior changed: Yes. A Flight result stream closed before EOF on the BE that holds its results cancels its query on that BE, and a later DoGet with the same ticket fails instead of resuming. - Does this need documentation: No Co-authored-by: Gabriel <[email protected]> Co-Authored-By: Claude Opus 5.5 <[email protected]>
…s next request ### What problem does this PR solve? Issue Number: None Related PR: apache#68630, apache#69945 Problem Summary: The FE dropped a Flight query's cancellation route when the session's next request began and when the session was torn down (FlightSqlChannel.reset). The client reads the results from the BEs, so it can still hold a stream of the query open at that point, for example through a second statement on the same connection, and close that stream early afterwards. The BE's cancelFlightQuery then found no route, and the query's other BEs kept their pipelines and result buffers until the query timeout. branch-4.x (apache#68630) drops the route at the same points. A route now belongs to no session. It goes when its cancellation succeeds, when its query's execution timeout plus 5 s has passed, and, oldest first and as a whole, when the registry would hold more than 100,000 result ids, a few hundred bytes each. Without the session as a bound, that cap is what keeps the registry from growing with the query rate times the query timeout; a query whose route was pushed out ends at its timeout, as every Flight query did before this PR. The registry is an insertion-ordered map under a lock, with the RPCs sent outside it, instead of a Caffeine cache, whose size eviction may reject a new entry instead of evicting the oldest one. StmtExecutor registers a query through FlightSqlQueryCancellation.register(ConnectContext, Coordinator), and FlightSqlChannel is back to its master form. ### Release note None ### Check List (For Author) - Test: Unit Test / Regression test - FE UT: FlightSqlQueryCancellationTest (5): a route survives the session's next request and its teardown and still cancels; the bound drops whole routes, oldest first; an expired route leaves at the next registration. With the 16 other Flight session and coordinator test classes. - Regression: test_flight_cancel_cleanup gains a mode that runs the session's next request before it aborts the stream. - Behavior changed: Yes. A stream aborted after its session moved on to another request, or was closed, still cancels its query on every BE. - Does this need documentation: No Co-Authored-By: Claude Opus 5.5 <[email protected]>
…stream closes early (#69945) ### What problem does this PR solve? Issue Number: None Related PR: #68630, #67644 Problem Summary: #### In short This ports the BE half of branch-4.x's #68630 to master. When an Arrow Flight SQL client cancels or closes a result stream before its end, master keeps the query running: the BE thread serving the stream waits on the result buffer without ever checking whether the client is still there, and the unread result blocks stay in memory, holding back the query, until the query times out or the delayed buffer cleanup runs minutes later. With this PR the BE notices the cancelled stream within 20 ms, and when the stream is served by the BE that holds its result (the usual case), that BE cancels the query and frees all of the query's Flight result buffers there. The FE route that also reaches the query's other BEs (`cancelFlightQuery`, which changes `FrontendService.thrift`) comes in a follow-up PR. #### Background - A Flight SQL query leaves its result on the BEs. `GetFlightInfo` returns one endpoint per result buffer, and the client reads each endpoint with a `DoGet` stream. Since #67504 the FE closes the coordinator as soon as it has returned the `FlightInfo`, so it no longer cancels anything on behalf of an open stream. - `DoGet` is served by `ArrowFlightBatchLocalReader` when the result buffer is on the same BE, or by `ArrowFlightBatchRemoteReader`, which pulls the blocks from the result BE with the `fetch_arrow_data` BRPC. - The result buffer (`ArrowFlightResultBlockBuffer`) is keyed by the query id when parallel result sink is on (the default) and by the fragment instance id when it is off. When it is full, the result sink stops and the query waits for the reader. - `ResultBufferMgr` drops a buffer that nobody removed at `execution_timeout + 5 s`, or `result_buffer_cancelled_interval_time` (300 s by default) after the fragments writing it have finished, whichever comes first. Finishing with an error does not clear the unread blocks. `cancel_plan_fragment` is the BRPC the FE uses to cancel a query on a BE. #### The problem, and what it cost - **The local reader never looks at the client.** `get_arrow_batch` loops on 20 ms waits until a block arrives, the buffer is closed or it fails. It has no access to `ServerCallContext::is_cancelled()`, so a `DoGet` cancelled while the buffer is empty keeps a Flight server thread waiting until the query produces a block or ends. - **Closing a stream cancels nothing.** The readers do not override `RecordBatchReader::Close()`, and their destructor only logs. A client that stops reading (a `LIMIT` reached on the client side, a cancelled cursor, a dropped connection) leaves the query running and its unread blocks in the buffer. When the result does not fit in the buffer, the full buffer back-pressures the result sink and the fragments, with their memory, wait until the query times out; the unread blocks go only when the buffer is dropped. - **The remote reader cannot be interrupted.** It waits for `fetch_arrow_data` with `brpc::Join`, which returns only when the result BE answers or `arrow_flight_reader_brpc_controller_timeout_ms` (300 s by default) passes. - **An FE cancel leaves the results behind.** `cancel_plan_fragment` cancels the fragments but not the query's Flight buffers, so their unread blocks stay until the delayed cleanup 300 s later. - **A fetch for a removed buffer never completes.** When `fetch_arrow_data` cannot find the buffer, it logs and returns without running `done`, so the caller waits for its BRPC timeout instead of getting an error. #67644 fixes the same line. #### How this PR fixes it - **Bounded waits, cancellation checks in the reader.** `get_arrow_batch(result, eos)` waits at most 20 ms and then returns OK with no block and `eos = false`; `eos` now marks the real end. The local reader retries in a loop and checks `is_cancelled()` before each wait. `DoGetStatement` passes `context.is_cancelled()` to both readers. - **The reader closes itself.** `ArrowFlightBatchReaderBase` now implements `ReadNext` (which calls each reader's `ReadNextImpl`) and `Close()`. A stream counts as aborted when it is cancelled, closed before EOF, fails while reading, or is destroyed before EOF; a stream that reached EOF is never treated as aborted. - **Aborting a local stream cancels the query on this BE.** `ResultBufferMgr` keeps an index from query id to the query's Flight buffers, maintained under the buffer-map lock together with the map. `cancel_arrow_flight_buffers` removes every Flight buffer of the query from the map, then, outside the lock, fails each buffer (which wakes its waiting fetches and releases the back-pressure) and cancels the `QueryContext` it holds a weak reference to. Sibling buffers of the same query are cleared even after their `QueryContext` has expired. - **Remote waits can be interrupted.** The remote reader waits for its BRPC with a callback that checks `is_cancelled()` every 20 ms and calls `brpc::StartCancel` when the stream is gone. - **`cancel_plan_fragment` drops the Flight buffers of an aborted query.** It calls `cancel_arrow_flight_buffers` unless the reason is `LIMIT_REACH` or `FINISHED`, which can leave valid unread results that the client still has to read. - **`fetch_arrow_data` answers when the buffer is gone.** It completes the RPC with the lookup error. The line is identical to the one #67644 adds, so the two PRs merge in either order. Differences from branch-4.x: the two consistency checks between the index and the buffer map are `DORIS_CHECK` (an `if` and a `DCHECK` in 4.x), a null check on the reader's memory tracker that both constructors make unnecessary is not carried over, and the `ArrowFlightResultBlockBuffer` constructor moved to the .cpp so that `varrow_flight_result_writer.h`, which 13 TUs include, does not include `runtime/query_context.h`; only the .cpp needs its definition. The new lines also take a few clang-tidy fixes (the destructor calls `ArrowFlightBatchReaderBase::Close()` explicitly instead of a virtual call, `std::scoped_lock`), and the test starts its BRPC server inside `ScopedLeakCheckDisabler`, like the other BE tests that start one, because brpc registers process-lifetime built-in services on its first start. Not in this PR: the FE route. A remote reader's abort, and an abort on one result BE, still do not reach the query's other BEs; their fragments and buffers wait for the timeout as before. The follow-up adds `FrontendService.cancelFlightQuery` (same signature as branch-4.x), the BE broadcast to the FEs and the FE routing table, together with the FE unit test and the `test_flight_cancel_cleanup` regression test. | Situation | Before | After | |---|---|---| | Client cancels `DoGet` while the result buffer is empty (local reader) | Server thread waits until the query produces data or ends | Returns within 20 ms; the query is cancelled on this BE | | Client closes a local stream before EOF | Query keeps running, back-pressured until it times out if the result does not fit; unread blocks kept for minutes | Query cancelled on this BE; all of its Flight buffers there freed at once | | Client cancels while a remote fetch is pending | Waits for the result BE, up to 300 s | BRPC cancelled within 20 ms | | FE cancels the query (`cancel_plan_fragment`, abort reason) | Unread blocks kept until the delayed cleanup, 300 s later | Flight buffers freed at once; `LIMIT_REACH` / `FINISHED` still keep them | | `fetch_arrow_data` for a removed buffer | RPC never completes; caller waits for its timeout | Completes at once with an error | | Stream read to EOF, then closed | Nothing cancelled | Nothing cancelled | | New `DoGet` with the ticket of a stream aborted on its result BE | Reads the remaining blocks, or EOS | Fails: the buffer is gone | #### Results - `ArrowFlightCancelTest` (9 tests, new): early close and destruction free a back-pressured buffer and cancel the query; sibling buffers are freed after their `QueryContext` expired; EOF followed by close is not a cancellation; cancellation interrupts an empty-buffer wait; empty waits return for retry and do not end the stream; a conversion error cancels the query; a real Flight server sees a client-side `Cancel()` through `ServerCallContext` and cleans up. - `ArrowFlightRemoteCancelTest` (3 tests, new, against an in-process BRPC `PInternalService`): cancellation interrupts a pending `fetch_arrow_data`; an aborting `cancel_plan_fragment` frees the buffer and a following fetch completes with an error inside its 1 s timeout; `LIMIT_REACH` / `FINISHED` keep the unread results. - On a local single-BE cluster, branch-4.x's regression `test_flight_cancel_cleanup` (it aborts one endpoint after the first batch, by closing it and by an explicit cancel, and requires the query's pipelines to disappear within 20 s) fails on master with `Cancelled Flight query retained pipelines: [...: 1 pipeline fragment contexts in query ...]` and passes with this PR. The suite comes with the follow-up, because on a multi-BE cluster it also needs the FE route. The 20 `arrow_flight_sql_p0` suites and the 14 `external_table_p0/remote_doris` suites, which read results over Arrow Flight, pass before and after. - The 12 new tests plus `ResultBufferMgrTest` (6) and `ArrowResultBlockBufferTest` (4) pass 10 times in a row in one ASAN process. Ten single-point mutations of the change (dropping the buffer cleanup in `cancel_plan_fragment`, the `on_failure` call, the local cancel in `close()`, the cancellation check in the local read loop or in the BRPC wait, the sibling loop, the EOF flag, the `QueryContext` cancel, the cancel in the destructor, the `eos = false` on an empty wait) each turn their target tests red.
…t stream closes early ### What problem does this PR solve? Issue Number: None Related PR: apache#68630, apache#69945 Problem Summary: Port the FE route of branch-4.x apache#68630 to master, on top of apache#69945 (its BE-local half). With apache#69945 alone, a DoGet stream closed before EOF cancels its query only on the BE that holds the stream's results. A query whose results are spread over several BEs (parallel result sink, one endpoint per BE) kept running on the other BEs: master closes the coordinator when GetFlightInfo returns (apache#67504), so nothing sends them a cancel, and a single-fragment query has no exchange that would carry the cancellation between BEs. Their pipelines ran until the query finished or timed out. A remote reader (a DoGet served by a BE other than the result BE) could not cancel the result BE's query at all. The FE now keeps a route for each Flight query that leaves its results on the BEs: from each result id (the fragment instance id of an endpoint) to the query id and the BRPC addresses of all its BEs (FlightSqlQueryCancellation, registered by StmtExecutor before the coordinator is closed or deferred). The route belongs to the session's FlightSqlChannel and is dropped when the session's next request begins (FlightProtocolAdapter.beginRequest resets the channel), when the session is torn down (releaseSession closes the channel), when a cancellation succeeds, and at the latest execution_timeout + 5 s after registration. The new FrontendService RPC cancelFlightQuery(result_id) sends the existing cancel_plan_fragment, which older BEs understand and which does not use the Flight read pool, to every BE of the query and waits at most 1 s for all of them; when any BE fails, the route stays so that the caller can retry. A reader closed before EOF now asks the running FEs in turn: an FE without the route answers NOT_FOUND and the BE moves on to the next one, a failed attempt is retried once, and the transport is reopened after a thrift error. This runs on the DoGet thread, so with an unreachable FE one early close can take a few seconds (two attempts with a 2 s RPC timeout per FE). Compatibility: cancelFlightQuery has the same name and signature as on branch-4.1 and branch-4.2. An FE without it fails the call; the BE logs a warning for that FE and only the local cleanup of apache#69945 takes effect. A result BE without apache#69945 cancels the query but keeps its result buffer until its usual timeout. Differences from branch-4.x: the route is registered in master's `!isReturnResultFromLocal()` branch (apache#67900), before the `mustOutliveDispatch()` deferral, so deferred queries are routed as well; FlightProtocolAdapter needs no change because the route stays in FlightSqlChannel; the BE reader includes runtime/frontend_info.h itself (exec_env.h no longer includes it, apache#66400); the regression suite keeps its table at the end (AGENTS.md). ### Release note Arrow Flight SQL: cancelling or closing a result stream before its end now cancels the query on every BE that runs it, not only on the BE that serves the stream. The FE and the BEs must both be upgraded. ### Check List (For Author) - Test: Unit Test / Regression test - FE UT: FlightSqlQueryCancellationTest (5, new), StmtExecutorTest and 15 other Flight session / coordinator test classes - BE UT: ArrowFlightCancelTest (9), ArrowFlightRemoteCancelTest (9, 6 new), ResultBufferMgrTest (6), ArrowResultBlockBufferTest (4) - Regression: arrow_flight_sql_p0/test_flight_cancel_cleanup (new) and the rest of arrow_flight_sql_p0 on a local single-BE cluster; the cross-BE path is covered by the BE UT's in-process FE and remote BE - Behavior changed: Yes. A Flight result stream closed before EOF cancels its query on every BE of the query. - Does this need documentation: No Co-authored-by: Gabriel <[email protected]> Co-Authored-By: Claude Opus 5.5 <[email protected]>
…s next request ### What problem does this PR solve? Issue Number: None Related PR: apache#68630, apache#69945 Problem Summary: The FE dropped a Flight query's cancellation route when the session's next request began and when the session was torn down (FlightSqlChannel.reset). The client reads the results from the BEs, so it can still hold a stream of the query open at that point, for example through a second statement on the same connection, and close that stream early afterwards. The BE's cancelFlightQuery then found no route, and the query's other BEs kept their pipelines and result buffers until the query timeout. branch-4.x (apache#68630) drops the route at the same points. A route now belongs to no session. It goes when its cancellation succeeds, when its query's execution timeout plus 5 s has passed, and, oldest first and as a whole, when the registry would hold more than 100,000 result ids, a few hundred bytes each. Without the session as a bound, that cap is what keeps the registry from growing with the query rate times the query timeout; a query whose route was pushed out ends at its timeout, as every Flight query did before this PR. The registry is an insertion-ordered map under a lock, with the RPCs sent outside it, instead of a Caffeine cache, whose size eviction may reject a new entry instead of evicting the oldest one. StmtExecutor registers a query through FlightSqlQueryCancellation.register(ConnectContext, Coordinator), and FlightSqlChannel is back to its master form. ### Release note None ### Check List (For Author) - Test: Unit Test / Regression test - FE UT: FlightSqlQueryCancellationTest (5): a route survives the session's next request and its teardown and still cancels; the bound drops whole routes, oldest first; an expired route leaves at the next registration. With the 16 other Flight session and coordinator test classes. - Regression: test_flight_cancel_cleanup gains a mode that runs the session's next request before it aborts the stream. - Behavior changed: Yes. A stream aborted after its session moved on to another request, or was closed, still cancels its query on every BE. - Does this need documentation: No Co-Authored-By: Claude Opus 5.5 <[email protected]>
…t stream closes early ### What problem does this PR solve? Issue Number: None Related PR: apache#68630, apache#69945 Problem Summary: Port the FE route of branch-4.x apache#68630 to master, on top of apache#69945 (its BE-local half). With apache#69945 alone, a DoGet stream closed before EOF cancels its query only on the BE that holds the stream's results. A query whose results are spread over several BEs (parallel result sink, one endpoint per BE) kept running on the other BEs: master closes the coordinator when GetFlightInfo returns (apache#67504), so nothing sends them a cancel, and a single-fragment query has no exchange that would carry the cancellation between BEs. Their pipelines ran until the query finished or timed out. A remote reader (a DoGet served by a BE other than the result BE) could not cancel the result BE's query at all. The FE now keeps a route for each Flight query that leaves its results on the BEs: from each result id (the fragment instance id of an endpoint) to the query id and the BRPC addresses of all its BEs (FlightSqlQueryCancellation, registered by StmtExecutor before the coordinator is closed or deferred). The route belongs to the session's FlightSqlChannel and is dropped when the session's next request begins (FlightProtocolAdapter.beginRequest resets the channel), when the session is torn down (releaseSession closes the channel), when a cancellation succeeds, and at the latest execution_timeout + 5 s after registration. The new FrontendService RPC cancelFlightQuery(result_id) sends the existing cancel_plan_fragment, which older BEs understand and which does not use the Flight read pool, to every BE of the query and waits at most 1 s for all of them; when any BE fails, the route stays so that the caller can retry. A reader closed before EOF now asks the running FEs in turn: an FE without the route answers NOT_FOUND and the BE moves on to the next one, a failed attempt is retried once, and the transport is reopened after a thrift error. This runs on the DoGet thread, so with an unreachable FE one early close can take a few seconds (two attempts with a 2 s RPC timeout per FE). Compatibility: cancelFlightQuery has the same name and signature as on branch-4.1 and branch-4.2. An FE without it fails the call; the BE logs a warning for that FE and only the local cleanup of apache#69945 takes effect. A result BE without apache#69945 cancels the query but keeps its result buffer until its usual timeout. Differences from branch-4.x: the route is registered in master's `!isReturnResultFromLocal()` branch (apache#67900), before the `mustOutliveDispatch()` deferral, so deferred queries are routed as well; FlightProtocolAdapter needs no change because the route stays in FlightSqlChannel; the BE reader includes runtime/frontend_info.h itself (exec_env.h no longer includes it, apache#66400); the regression suite keeps its table at the end (AGENTS.md). ### Release note Arrow Flight SQL: cancelling or closing a result stream before its end now cancels the query on every BE that runs it, not only on the BE that serves the stream. The FE and the BEs must both be upgraded. ### Check List (For Author) - Test: Unit Test / Regression test - FE UT: FlightSqlQueryCancellationTest (5, new), StmtExecutorTest and 15 other Flight session / coordinator test classes - BE UT: ArrowFlightCancelTest (9), ArrowFlightRemoteCancelTest (9, 6 new), ResultBufferMgrTest (6), ArrowResultBlockBufferTest (4) - Regression: arrow_flight_sql_p0/test_flight_cancel_cleanup (new) and the rest of arrow_flight_sql_p0 on a local single-BE cluster; the cross-BE path is covered by the BE UT's in-process FE and remote BE - Behavior changed: Yes. A Flight result stream closed before EOF cancels its query on every BE of the query. - Does this need documentation: No Co-authored-by: Gabriel <[email protected]> Co-Authored-By: Claude Opus 5.5 <[email protected]>
…s next request ### What problem does this PR solve? Issue Number: None Related PR: apache#68630, apache#69945 Problem Summary: The FE dropped a Flight query's cancellation route when the session's next request began and when the session was torn down (FlightSqlChannel.reset). The client reads the results from the BEs, so it can still hold a stream of the query open at that point, for example through a second statement on the same connection, and close that stream early afterwards. The BE's cancelFlightQuery then found no route, and the query's other BEs kept their pipelines and result buffers until the query timeout. branch-4.x (apache#68630) drops the route at the same points. A route now belongs to no session. It goes when its cancellation succeeds, when its query's execution timeout plus 5 s has passed, and, oldest first and as a whole, when the registry would hold more than 100,000 result ids, a few hundred bytes each. Without the session as a bound, that cap is what keeps the registry from growing with the query rate times the query timeout; a query whose route was pushed out ends at its timeout, as every Flight query did before this PR. The registry is an insertion-ordered map under a lock, with the RPCs sent outside it, instead of a Caffeine cache, whose size eviction may reject a new entry instead of evicting the oldest one. StmtExecutor registers a query through FlightSqlQueryCancellation.register(ConnectContext, Coordinator), and FlightSqlChannel is back to its master form. ### Release note None ### Check List (For Author) - Test: Unit Test / Regression test - FE UT: FlightSqlQueryCancellationTest (5): a route survives the session's next request and its teardown and still cancels; the bound drops whole routes, oldest first; an expired route leaves at the next registration. With the 16 other Flight session and coordinator test classes. - Regression: test_flight_cancel_cleanup gains a mode that runs the session's next request before it aborts the stream. - Behavior changed: Yes. A stream aborted after its session moved on to another request, or was closed, still cancels its query on every BE. - Does this need documentation: No Co-Authored-By: Claude Opus 5.5 <[email protected]>
…t stream closes early ### What problem does this PR solve? Issue Number: None Related PR: apache#68630, apache#69945 Problem Summary: Port the FE route of branch-4.x apache#68630 to master, on top of apache#69945 (its BE-local half). With apache#69945 alone, a DoGet stream closed before EOF cancels its query only on the BE that holds the stream's results. A query whose results are spread over several BEs (parallel result sink, one endpoint per BE) kept running on the other BEs: master closes the coordinator when GetFlightInfo returns (apache#67504), so nothing sends them a cancel, and a single-fragment query has no exchange that would carry the cancellation between BEs. Their pipelines ran until the query finished or timed out. A remote reader (a DoGet served by a BE other than the result BE) could not cancel the result BE's query at all. The FE now keeps a route for each Flight query that leaves its results on the BEs: from each result id (the fragment instance id of an endpoint) to the query id and the BRPC addresses of all its BEs (FlightSqlQueryCancellation, registered by StmtExecutor before the coordinator is closed or deferred). The route belongs to the session's FlightSqlChannel and is dropped when the session's next request begins (FlightProtocolAdapter.beginRequest resets the channel), when the session is torn down (releaseSession closes the channel), when a cancellation succeeds, and at the latest execution_timeout + 5 s after registration. The new FrontendService RPC cancelFlightQuery(result_id) sends the existing cancel_plan_fragment, which older BEs understand and which does not use the Flight read pool, to every BE of the query and waits at most 1 s for all of them; when any BE fails, the route stays so that the caller can retry. A reader closed before EOF now asks the running FEs in turn: an FE without the route answers NOT_FOUND and the BE moves on to the next one, a failed attempt is retried once, and the transport is reopened after a thrift error. This runs on the DoGet thread, so with an unreachable FE one early close can take a few seconds (two attempts with a 2 s RPC timeout per FE). Compatibility: cancelFlightQuery has the same name and signature as on branch-4.1 and branch-4.2. An FE without it fails the call; the BE logs a warning for that FE and only the local cleanup of apache#69945 takes effect. A result BE without apache#69945 cancels the query but keeps its result buffer until its usual timeout. Differences from branch-4.x: the route is registered in master's `!isReturnResultFromLocal()` branch (apache#67900), before the `mustOutliveDispatch()` deferral, so deferred queries are routed as well; FlightProtocolAdapter needs no change because the route stays in FlightSqlChannel; the BE reader includes runtime/frontend_info.h itself (exec_env.h no longer includes it, apache#66400); the regression suite keeps its table at the end (AGENTS.md). ### Release note Arrow Flight SQL: cancelling or closing a result stream before its end now cancels the query on every BE that runs it, not only on the BE that serves the stream. The FE and the BEs must both be upgraded. ### Check List (For Author) - Test: Unit Test / Regression test - FE UT: FlightSqlQueryCancellationTest (5, new), StmtExecutorTest and 15 other Flight session / coordinator test classes - BE UT: ArrowFlightCancelTest (9), ArrowFlightRemoteCancelTest (9, 6 new), ResultBufferMgrTest (6), ArrowResultBlockBufferTest (4) - Regression: arrow_flight_sql_p0/test_flight_cancel_cleanup (new) and the rest of arrow_flight_sql_p0 on a local single-BE cluster; the cross-BE path is covered by the BE UT's in-process FE and remote BE - Behavior changed: Yes. A Flight result stream closed before EOF cancels its query on every BE of the query. - Does this need documentation: No Co-authored-by: Gabriel <[email protected]> Co-Authored-By: Claude Opus 5.5 <[email protected]>
…s next request ### What problem does this PR solve? Issue Number: None Related PR: apache#68630, apache#69945 Problem Summary: The FE dropped a Flight query's cancellation route when the session's next request began and when the session was torn down (FlightSqlChannel.reset). The client reads the results from the BEs, so it can still hold a stream of the query open at that point, for example through a second statement on the same connection, and close that stream early afterwards. The BE's cancelFlightQuery then found no route, and the query's other BEs kept their pipelines and result buffers until the query timeout. branch-4.x (apache#68630) drops the route at the same points. A route now belongs to no session. It goes when its cancellation succeeds, when its query's execution timeout plus 5 s has passed, and, oldest first and as a whole, when the registry would hold more than 100,000 result ids, a few hundred bytes each. Without the session as a bound, that cap is what keeps the registry from growing with the query rate times the query timeout; a query whose route was pushed out ends at its timeout, as every Flight query did before this PR. The registry is an insertion-ordered map under a lock, with the RPCs sent outside it, instead of a Caffeine cache, whose size eviction may reject a new entry instead of evicting the oldest one. StmtExecutor registers a query through FlightSqlQueryCancellation.register(ConnectContext, Coordinator), and FlightSqlChannel is back to its master form. ### Release note None ### Check List (For Author) - Test: Unit Test / Regression test - FE UT: FlightSqlQueryCancellationTest (5): a route survives the session's next request and its teardown and still cancels; the bound drops whole routes, oldest first; an expired route leaves at the next registration. With the 16 other Flight session and coordinator test classes. - Regression: test_flight_cancel_cleanup gains a mode that runs the session's next request before it aborts the stream. - Behavior changed: Yes. A stream aborted after its session moved on to another request, or was closed, still cancels its query on every BE. - Does this need documentation: No Co-Authored-By: Claude Opus 5.5 <[email protected]>
What problem does this PR solve?
Cancelling or closing an unfinished Arrow Flight stream stops the client RPC but can leave result producers blocked until timeout. Propagate stream aborts to the whole query and promptly release its Flight buffers, while preserving normal EOF and valid unread results after LIMIT_REACH or FINISHED.
Local reads use bounded buffer waits with explicit EOF; the reader owns cancellation checks. Remote reads interrupt outstanding BRPC waits. A BE reports an aborted result ID to the owning FE, which sends the existing cancel_plan_fragment RPC to every participating BE. Lightweight result-ID routing metadata survives local fragment and coordinator completion without retaining the coordinator or its query queue slot; session reset and execution-timeout expiry remove those routes. RPC transport and application errors are checked, with bounded retries. Cancellation bypasses the Arrow read pool.
On updated BEs, a query-to-buffer index removes all sibling Flight buffers, even after their local QueryContext expires. Query cancellation occurs outside buffer-map locks. A fetch racing with buffer removal completes its RPC with an error.
Validation
Upgrade behavior
Upgrade the serving FE and Flight-facing BE to enable the new cancellation route. Result BEs can still be older: the FE uses their existing query cancellation RPC rather than an unknown fetch flag. Older result BEs retain their historical unread-buffer reclamation behavior until upgraded; immediate sibling-buffer reclamation requires the updated BE.