Repository navigation
[fix](arrow-flight) Release a finished Flight query's coordinator instead of holding it until wait_timeout - #67504
Conversation
|
Thank you for your contribution to Apache Doris. Please clearly describe your PR:
|
…instead of killing the session Follow-up to the first revision of apache#67504 after review: - Narrow the apache#64799 deferral gate: only a coordinator that still hands out splits to the BE (an external-table scan in batch mode, see the new ScanNode/Coordinator.hasBatchSplitSource) outlives GetFlightInfo. Every other Arrow Flight query closes its coordinator at the end of GetFlightInfo again, releasing the workload-group queue slot and the active_queries entry right away. Finalizing the FE side does not cancel BE execution, so DoGet is unaffected. - Replace arrow_flight_session_idle_timeout_second by arrow_flight_deferred_query_idle_timeout_second: the connection timeout checker now finalizes the deferred executors of a sleeping Flight session and leaves the session alive; wait_timeout still governs the session. A killed session would have made the client's next call fail with "UserSession expire after access". - Freeze the execution timeout when the executor is deferred, so a SET_VAR query_timeout hint (reverted at the end of execute()) still floors the bound. - Tests: FlightSqlDeferredQueryIdleTimeoutTest drives checkTimeout, ArrowFlightDeferralGateTest covers the predicates, StmtExecutorTest covers the frozen timeout; regression cases for the internal-table release (arrow_flight_sql_p0) and the idle reaper on a batch-mode Iceberg scan. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ApxvNkMK8TiNycv44P8Ezk
|
Thanks for the precise write-up, the problem is real and #67503 nails the mechanism. I reviewed the branch and pushed a revision on top of your commit (the PR allows maintainer edits); the description is updated to match. What changed and why:
Pick labels: #64799 is in 4.0.8 / 4.1.4, so this needs |
|
run buildall |
…rom wait_timeout A Flight SQL session idles under the same wait_timeout as a MySQL connection (28800s by default). Since the coordinator of a BE-served Flight query is kept alive across GetFlightInfo -> DoGet until the session's next query or its close, an abandoned session - a client that opens a session per query and never sends CloseSession - keeps that query's workload-group queue slot for the whole wait_timeout. Eight such sessions fill a max_concurrency=8 group and every later query in it fails with "query queue timeout". Add a mutable FE config, arrow_flight_session_idle_timeout_second (default 3600), applied by the existing connection timeout checker to ARROW_FLIGHT_SQL contexts only as min(wait_timeout, max(config, exec timeout)). The exec-timeout floor matters: a Flight session is COM_SLEEP while the client drains the result via DoGet and its idle clock runs from the query's start, so a bound below query_timeout would kill a long result stream before the query's own timeout could. 0 disables the bound. MySQL-protocol connections are unchanged. The kill log line now reports the effective idle timeout. Signed-off-by: Raghvendra Singh <raghav@cashify.in> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…instead of killing the session Follow-up to the first revision of apache#67504 after review: - Narrow the apache#64799 deferral gate: only a coordinator that still hands out splits to the BE (an external-table scan in batch mode, see the new ScanNode/Coordinator.hasBatchSplitSource) outlives GetFlightInfo. Every other Arrow Flight query closes its coordinator at the end of GetFlightInfo again, releasing the workload-group queue slot and the active_queries entry right away. Finalizing the FE side does not cancel BE execution, so DoGet is unaffected. - Replace arrow_flight_session_idle_timeout_second by arrow_flight_deferred_query_idle_timeout_second: the connection timeout checker now finalizes the deferred executors of a sleeping Flight session and leaves the session alive; wait_timeout still governs the session. A killed session would have made the client's next call fail with "UserSession expire after access". - Freeze the execution timeout when the executor is deferred, so a SET_VAR query_timeout hint (reverted at the end of execute()) still floors the bound. - Tests: FlightSqlDeferredQueryIdleTimeoutTest drives checkTimeout, ArrowFlightDeferralGateTest covers the predicates, StmtExecutorTest covers the frozen timeout; regression cases for the internal-table release (arrow_flight_sql_p0) and the idle reaper on a batch-mode Iceberg scan. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ApxvNkMK8TiNycv44P8Ezk
212a026 to
c662d85
Compare
|
Rebased onto current master ( |
|
run buildall |
TPC-H: Total hot run time: 16940 ms |
TPC-DS: Total hot run time: 83156 ms |
ClickBench: Total hot run time: 14.81 s |
FE UT Coverage ReportIncrement line coverage |
…elease path Follow-up to apache#67504 after review: the deferral gate this PR narrows moves exactly one case - an external-table scan that is NOT in batch mode - from "coordinator deferred past GetFlightInfo" to "coordinator closed at the end of GetFlightInfo". That case had no Arrow Flight coverage anywhere: the only external-catalog Flight suite forces batch mode on its session and asserts it, so all four of its data queries exercise the deferred side, and the new arrow_flight_sql_p0 case covers the eager side only for an internal table, where neither a split source nor a connector read session exists. Add the missing cell to test_iceberg_arrow_flight_split_source, reusing the catalog and Flight connection it already sets up: - A negative control mirroring the existing batch assertion: "(approximate)" is emitted only when isBatchMode(), so its absence proves the scan really is on the synchronous split path. Without it the block could silently run in batch mode and pass. enable_external_table_batch_mode=false is a reliable off switch here - IcebergScanPlanProvider.streamingSplitEstimate returns -1 when it is unset, and Iceberg does not override supportsBatchScan (SPI default false), so the partition-count flavor cannot route around it. - A full scan that must return every row: the FE has closed the coordinator by then, and the BE buffers the result independently of it. - A check that the release really was eager - the query is gone from active_queries right after the client has the rows. No polling is needed because finalizeQuery() runs inside GetFlightInfo. A distinct limit keeps the query text apart from the other scans and the LIKE pattern is built with CONCAT so the probe cannot match itself. Batch mode is restored afterwards, since the idle-reaper assertions below need a deferred coordinator to release. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGqBt7EdtV7rc7tD4nmEqx
|
run buildall |
FE Regression Coverage ReportIncrement line coverage |
TPC-H: Total hot run time: 16970 ms |
TPC-DS: Total hot run time: 82259 ms |
ClickBench: Total hot run time: 14.75 s |
Local pipeline review — ✅ PASSschema: doris-repo-review/v1
status: PASS
pr: apache/doris#67504
commit: 888313142ef112a5dd52d4e536259832b16e3852
base: b58b2c53ff56354c692c2dc7634d724d8780838f
reviewed_at: 2026-09-06T06:19+08:00
reviewer: morningman
model: claude-opus-5[1m]
effort: max
findings: {blocker: 0, major: 0, minor: 14, nit: 4}
rounds: 2
converged: trueNotes for maintainers
Reviewed locally with the |
…cuit on an Arrow Flight connection #67487 (#67557) Cherry-picked from #67487 ### Backport notes `LogicalResultSinkToShortCircuitPointQuery` and the `StmtExecutor` comment carry the upstream change; the rule keeps this branch's `private` signature for `scanMatchShortCircuitCondition`, since nothing on branch-4.1 calls it directly (upstream made it package-private for a different test, added by a PR that is not on this branch). `ShortCircuitPointQueryTest` does not carry the later master cases here, so it has no `rewrite(sql)` helper. The helper is added with the same body as upstream and the new case uses it; the existing case is untouched. The regression suite `arrow_flight_sql_p0/test_point_query_over_arrow_flight.groovy` applied cleanly. ### Merge order Touches the same `StmtExecutor` comment as #67558 (the branch-4.1 backport of #67504). Merging this one first keeps that one conflict-free; the other way round leaves a trivial comment conflict. There is no code dependency either way. ### Local verification `./build.sh --fe` on this branch: **BUILD SUCCESS**, no errors, checkstyle clean on every module (`fe-common` and `fe-core` included). Regression suites were not run locally. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01DzUYFcGHQH3bnLGCjpncVj
…tead of holding it until wait_timeout (apache#67504) Issue Number: close apache#67503 Related PR: apache#64799 (kept the coordinator alive across GetFlightInfo → DoGet so the BE can fetch external-table splits; this PR narrows that to the queries that actually need it and bounds how long an abandoned session can hold the rest) Problem Summary: Since apache#64799 every Arrow Flight SQL query whose results are produced on the BE kept its coordinator alive after `GetFlightInfo`, until the session ran its next query or was closed. The coordinator holds the query's workload-group queue slot and its `active_queries` registration. Most Flight clients open a session per query and never send `CloseSession` (the ADBC/JDBC drivers don't do it on connection close, as the comment in `DorisFlightSqlProducer.closeSession` notes), so each abandoned session pinned one slot for up to `wait_timeout` (8h by default). Observed on a 2-FE cluster with a `max_concurrency=8` workload group: eight finished Flight queries sat in `information_schema.active_queries` as RUNNING for over an hour, and every later query in the group failed with `query queue timeout, timeout: 60000 ms`. Full write-up in apache#67503. This PR fixes it in two steps: 1. **Only defer the coordinator when the BE still needs it.** The deferral added by apache#64799 is required only for an external-table scan in batch mode, where the BE lazily fetches splits during `DoGet` from the split source the coordinator holds. Every other query (internal tables, external tables in non-batch mode) now closes its coordinator at the end of `GetFlightInfo` again, releasing the queue slot and the `active_queries` entry right away. That is safe: the BE buffers its results independently of the coordinator, and finalizing the FE side does not cancel BE execution (`QeProcessorImpl.reportExecStatus` accepts reports for an unregistered query, and `Coordinator.close()` only releases the queue token and the split sources). New predicates `ScanNode.hasBatchSplitSource()` / `Coordinator.hasBatchSplitSource()` drive the gate in `StmtExecutor.executeAndSendResult`. 2. **Bound the remaining deferred queries without killing the session.** New mutable FE config `arrow_flight_deferred_query_idle_timeout_second` (default 3600). Once a Flight session has been sleeping for longer than this since its last query started, the connection timeout checker finalizes the session's deferred executors (releasing the slot and the registration) and leaves the session alive; `wait_timeout` still governs the session itself. The bound is floored at the execution timeout the deferred query actually ran with, captured when the executor is deferred (`SET_VAR` hint values are reverted at the end of `execute()`, so the session value cannot be read later). `0` disables the bound. Why not kill the session (the first revision of this PR): a killed Flight session's bearer token stays in the token cache marked as already used, so the client's next call on it fails with `UserSession expire after access` and has to re-handshake. With a 1h bound, every pooled or BI-tool Flight connection that idles for an hour would fail once on its next use. Reaping only the deferred query releases the leaked resources with no client-visible change. Arrow Flight SQL: a query no longer holds its workload-group queue slot and `active_queries` entry after `GetFlightInfo` unless it is an external-table scan in batch mode, the only case where the BE still fetches splits from the FE during `DoGet`. For that case a new FE config `arrow_flight_deferred_query_idle_timeout_second` (default 1h) releases the coordinator of an idle, never-closed session without killing the session. - Test <!-- At least one of them must be included. --> - [x] Regression test (`arrow_flight_sql_p0/test_arrow_flight_query_release`: a finished Flight query on an internal table no longer occupies a `max_concurrency=1` group and is gone from `active_queries`; `external_table_p0/iceberg/test_iceberg_arrow_flight_split_source`: the batch-mode scan stays registered after `DoGet` and is released by the idle reaper while the session survives) - [x] Unit Test (`FlightSqlDeferredQueryIdleTimeoutTest`: the reaper through `checkTimeout`, the exec-timeout floor, `0` disables, nothing deferred, MySQL untouched; `ArrowFlightDeferralGateTest`: the batch-split-source predicates; `StmtExecutorTest.testDeferForArrowFlightFreezesExecTimeoutInEffect`) - [x] Manual test (the first revision was verified on a test cluster with an 8s bound, see the PR history; the current revision is covered by the regression tests above) - [ ] No need to test or manual test. Explain why: - [ ] This is a refactor/code format and no logic has been changed. - [ ] Previous test can cover this change. - [ ] No code files have been changed. - [ ] Other reason <!-- Add your reason? --> - Behavior changed: - [ ] No. - [x] Yes. (1) A Flight query that is not an external-table batch-mode scan releases its coordinator, queue slot and `active_queries` entry at the end of `GetFlightInfo`, as it did before apache#64799. (2) The deferred coordinator of a batch-mode scan on an idle session is released after 1h by default instead of at `wait_timeout`; set `arrow_flight_deferred_query_idle_timeout_second=0` for the previous behavior. Sessions are never killed by this change. - Does this need documentation? - [ ] No. - [x] Yes. New FE config `arrow_flight_deferred_query_idle_timeout_second` — doris-website PR to follow once this is reviewed. - [ ] Confirm the release note - [ ] Confirm test cases - [ ] Confirm document - [ ] Add branch pick label <!-- apache#64799 is in 4.0.8 / 4.1.4 --> --------- Signed-off-by: Raghvendra Singh <raghav@cashify.in> Co-authored-by: Raghav <raghav@Raghavs-RMac.local> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: morningman <yunyou@selectdb.com> (cherry picked from commit 23b5ee5) Two adaptations for branch-4.1: - SplitAssignment is org.apache.doris.datasource.SplitAssignment here, not org.apache.doris.datasource.split.SplitAssignment, so ArrowFlightDeferralGateTest imports it from its package on this branch. - The StmtExecutor comment above the deferral gate is taken from upstream verbatim, so it already mentions apache#67368, whose branch-4.1 backport (apache#67557) has since landed. The rebase onto it resolved the one conflict in StmtExecutor, in that comment and the gate condition; the resolved block is identical to upstream master.
…rdinator instead of holding it until wait_timeout #67504 (#67558) Cherry-picked from #67504 ### Backport notes Two conflicts, both resolved without changing behaviour: - `Config`: branch-4.1 still has the bilingual `autobucket_partition_size_per_bucket_gb` description that master rewrote, so that entry is kept as it is here and the new `arrow_flight_deferred_query_idle_timeout_second` is inserted above it, verbatim. - `StmtExecutor`: the comment above the deferral gate is taken from upstream verbatim, so it already mentions #67368. That fix is being backported separately (see below); the gate itself does not depend on it. One adaptation: `SplitAssignment` is `org.apache.doris.datasource.SplitAssignment` on this branch, not `org.apache.doris.datasource.split.SplitAssignment`, so `ArrowFlightDeferralGateTest` imports it from its package here. Everything else -- `ScanNode.hasBatchSplitSource`, `Coordinator.hasBatchSplitSource`, the `ConnectContext` idle reaper, `FlightSqlConnectProcessor`, `StmtExecutorTest`, `FlightSqlDeferredQueryIdleTimeoutTest` and both regression suites -- applied cleanly. ### Merge order Touches the same `StmtExecutor` comment as #67557 (the branch-4.1 backport of #67487). Merging that one first keeps this one conflict-free. ### Local verification `./build.sh --fe` on this branch: **BUILD SUCCESS** (2:26 min), no errors, checkstyle clean on every module -- `fe-common` (the new `Config` entry) and `fe-core` (the five main-source changes plus `ArrowFlightDeferralGateTest`, `FlightSqlDeferredQueryIdleTimeoutTest` and the new `StmtExecutorTest` case) included. The regression suites were not run locally. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01DzUYFcGHQH3bnLGCjpncVj Co-authored-by: Raghvendra Singh <raghav@cashify.in>
…rdinator instead of holding it until wait_timeout #67504 (#67558) Cherry-picked from #67504 ### Backport notes Two conflicts, both resolved without changing behaviour: - `Config`: branch-4.1 still has the bilingual `autobucket_partition_size_per_bucket_gb` description that master rewrote, so that entry is kept as it is here and the new `arrow_flight_deferred_query_idle_timeout_second` is inserted above it, verbatim. - `StmtExecutor`: the comment above the deferral gate is taken from upstream verbatim, so it already mentions #67368. That fix is being backported separately (see below); the gate itself does not depend on it. One adaptation: `SplitAssignment` is `org.apache.doris.datasource.SplitAssignment` on this branch, not `org.apache.doris.datasource.split.SplitAssignment`, so `ArrowFlightDeferralGateTest` imports it from its package here. Everything else -- `ScanNode.hasBatchSplitSource`, `Coordinator.hasBatchSplitSource`, the `ConnectContext` idle reaper, `FlightSqlConnectProcessor`, `StmtExecutorTest`, `FlightSqlDeferredQueryIdleTimeoutTest` and both regression suites -- applied cleanly. ### Merge order Touches the same `StmtExecutor` comment as #67557 (the branch-4.1 backport of #67487). Merging that one first keeps this one conflict-free. ### Local verification `./build.sh --fe` on this branch: **BUILD SUCCESS** (2:26 min), no errors, checkstyle clean on every module -- `fe-common` (the new `Config` entry) and `fe-core` (the five main-source changes plus `ArrowFlightDeferralGateTest`, `FlightSqlDeferredQueryIdleTimeoutTest` and the new `StmtExecutorTest` case) included. The regression suites were not run locally. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01DzUYFcGHQH3bnLGCjpncVj Co-authored-by: Raghvendra Singh <raghav@cashify.in>
…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 <liwenqiang@selectdb.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…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 <liwenqiang@selectdb.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…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 <liwenqiang@selectdb.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What problem does this PR solve?
Issue Number: close #67503
Related PR: #64799 (kept the coordinator alive across GetFlightInfo → DoGet so the BE can fetch external-table splits; this PR narrows that to the queries that actually need it and bounds how long an abandoned session can hold the rest)
Problem Summary:
Since #64799 every Arrow Flight SQL query whose results are produced on the BE kept its coordinator alive after
GetFlightInfo, until the session ran its next query or was closed. The coordinator holds the query's workload-group queue slot and itsactive_queriesregistration. Most Flight clients open a session per query and never sendCloseSession(the ADBC/JDBC drivers don't do it on connection close, as the comment inDorisFlightSqlProducer.closeSessionnotes), so each abandoned session pinned one slot for up towait_timeout(8h by default).Observed on a 2-FE cluster with a
max_concurrency=8workload group: eight finished Flight queries sat ininformation_schema.active_queriesas RUNNING for over an hour, and every later query in the group failed withquery queue timeout, timeout: 60000 ms. Full write-up in #67503.This PR fixes it in two steps:
Only defer the coordinator when the BE still needs it. The deferral added by [fix](arrow-flight) Keep coordinator alive across GetFlightInfo/DoGet for external table scan #64799 is required only for an external-table scan in batch mode, where the BE lazily fetches splits during
DoGetfrom the split source the coordinator holds. Every other query (internal tables, external tables in non-batch mode) now closes its coordinator at the end ofGetFlightInfoagain, releasing the queue slot and theactive_queriesentry right away. That is safe: the BE buffers its results independently of the coordinator, and finalizing the FE side does not cancel BE execution (QeProcessorImpl.reportExecStatusaccepts reports for an unregistered query, andCoordinator.close()only releases the queue token and the split sources). New predicatesScanNode.hasBatchSplitSource()/Coordinator.hasBatchSplitSource()drive the gate inStmtExecutor.executeAndSendResult.Bound the remaining deferred queries without killing the session. New mutable FE config
arrow_flight_deferred_query_idle_timeout_second(default 3600). Once a Flight session has been sleeping for longer than this since its last query started, the connection timeout checker finalizes the session's deferred executors (releasing the slot and the registration) and leaves the session alive;wait_timeoutstill governs the session itself. The bound is floored at the execution timeout the deferred query actually ran with, captured when the executor is deferred (SET_VARhint values are reverted at the end ofexecute(), so the session value cannot be read later).0disables the bound.Why not kill the session (the first revision of this PR): a killed Flight session's bearer token stays in the token cache marked as already used, so the client's next call on it fails with
UserSession expire after accessand has to re-handshake. With a 1h bound, every pooled or BI-tool Flight connection that idles for an hour would fail once on its next use. Reaping only the deferred query releases the leaked resources with no client-visible change.Release note
Arrow Flight SQL: a query no longer holds its workload-group queue slot and
active_queriesentry afterGetFlightInfounless it is an external-table scan in batch mode, the only case where the BE still fetches splits from the FE duringDoGet. For that case a new FE configarrow_flight_deferred_query_idle_timeout_second(default 1h) releases the coordinator of an idle, never-closed session without killing the session.Check List (For Author)
Test
arrow_flight_sql_p0/test_arrow_flight_query_release: a finished Flight query on an internal table no longer occupies amax_concurrency=1group and is gone fromactive_queries;external_table_p0/iceberg/test_iceberg_arrow_flight_split_source: the batch-mode scan stays registered afterDoGetand is released by the idle reaper while the session survives)FlightSqlDeferredQueryIdleTimeoutTest: the reaper throughcheckTimeout, the exec-timeout floor,0disables, nothing deferred, MySQL untouched;ArrowFlightDeferralGateTest: the batch-split-source predicates;StmtExecutorTest.testDeferForArrowFlightFreezesExecTimeoutInEffect)Behavior changed:
active_queriesentry at the end ofGetFlightInfo, as it did before [fix](arrow-flight) Keep coordinator alive across GetFlightInfo/DoGet for external table scan #64799. (2) The deferred coordinator of a batch-mode scan on an idle session is released after 1h by default instead of atwait_timeout; setarrow_flight_deferred_query_idle_timeout_second=0for the previous behavior. Sessions are never killed by this change.Does this need documentation?
arrow_flight_deferred_query_idle_timeout_second— doris-website PR to follow once this is reviewed.Check List (For Reviewer who merge this PR)