-
Notifications
You must be signed in to change notification settings - Fork 3.9k
[fix](arrow-flight) Keep coordinator alive across GetFlightInfo/DoGet for external table scan #64799
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[fix](arrow-flight) Keep coordinator alive across GetFlightInfo/DoGet for external table scan #64799
Changes from all commits
ae526d0
0dcc3e7
e57f3d6
d4a974d
826d25d
663491e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -199,6 +199,10 @@ public class StmtExecutor { | |
|
|
||
| @Setter | ||
| private volatile Coordinator coord = null; | ||
| // Arrow Flight SQL: when true, this query's coordinator is kept alive past GetFlightInfo and | ||
| // is finalized later by ConnectContext (see #62259), so the eager close in executeAndSendResult | ||
| // is skipped. | ||
| private volatile boolean deferredForArrowFlight = false; | ||
| private MasterOpExecutor masterOpExecutor = null; | ||
| private RedirectStatus redirectStatus = null; | ||
| private Planner planner; | ||
|
|
@@ -1035,6 +1039,23 @@ public void finalizeQuery() { | |
| QeProcessorImpl.INSTANCE.unregisterQuery(context.queryId()); | ||
| } | ||
|
|
||
| public boolean isDeferredForArrowFlight() { | ||
| return deferredForArrowFlight; | ||
| } | ||
|
|
||
| // Finalize an Arrow Flight query whose coordinator was kept alive across the | ||
| // GetFlightInfo -> DoGet phases: close the coordinator (releasing external-table batch | ||
| // SplitSources and the query queue slot) and then unregister the query. See #62259. | ||
| public void finalizeArrowFlightQuery() { | ||
| try { | ||
| if (coord != null) { | ||
| coord.close(); | ||
| } | ||
| } finally { | ||
| finalizeQuery(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P1] Unregister the deferred executor's captured query ID This calls |
||
| } | ||
| } | ||
|
|
||
| private void handleQueryWithRetry(TUniqueId queryId) throws Exception { | ||
| // queue query here | ||
| int retryTime = Config.max_query_retry_time; | ||
|
|
@@ -1470,6 +1491,22 @@ public void executeAndSendResult(boolean isOutfileQuery, boolean isSendFields, | |
| if (context.getConnectType().equals(ConnectType.ARROW_FLIGHT_SQL)) { | ||
| Preconditions.checkState(!context.isReturnResultFromLocal()); | ||
| profile.getSummaryProfile().setTempStartTime(); | ||
| // Defer closing the coordinator to ConnectContext (closed on the next query or | ||
| // connection teardown) instead of in the finally block below. This gate covers | ||
| // every Arrow Flight query whose results are produced on the BE (coordBase == | ||
| // coord) -- internal-table and external, batch or not. It is REQUIRED only for an | ||
| // external-table scan in batch mode, where the BE lazily fetches splits from the FE | ||
| // during the later DoGet phase, so closing the coordinator here would release its | ||
| // batch SplitSource too early and break DoGet. Other remote-result queries do not | ||
| // need deferral (the BE buffers their result independently) but are captured by the | ||
| // same gate; the trade-off is their coordinator, query queue slot and query | ||
| // registration stay held until the next query / teardown instead of being released | ||
| // at the end of GetFlightInfo. Point queries use a different coordBase (not | ||
| // deferred). See #62259. | ||
| if (coordBase == coord) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P1] Do not leave every BE-result query attached to the Flight session This gate also defers internal-table and non-batch queries even though only an external batch scan needs a live |
||
| deferredForArrowFlight = true; | ||
| context.addFlightSqlDeferredExecutor(this); | ||
| } | ||
| return; | ||
| } | ||
|
|
||
|
|
@@ -1579,7 +1616,12 @@ public void executeAndSendResult(boolean isOutfileQuery, boolean isSendFields, | |
| this.coord = null; | ||
| throw e; | ||
| } finally { | ||
| coordBase.close(); | ||
| // For deferred Arrow Flight queries the coordinator is closed later by ConnectContext | ||
| // (next query / connection teardown), so the BE can still fetch splits during DoGet. | ||
| // See #62259. | ||
| if (!deferredForArrowFlight) { | ||
| coordBase.close(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -189,6 +189,10 @@ private FlightInfo executeQueryStatement(String peerIdentity, ConnectContext con | |
| try { | ||
| Preconditions.checkState(null != connectContext); | ||
| Preconditions.checkState(!query.isEmpty()); | ||
| // Finalize the previous query's coordinator on this connection whose close was | ||
| // deferred (Arrow Flight keeps it alive across GetFlightInfo -> DoGet so the BE can | ||
| // fetch external-table splits during DoGet). By now the previous DoGet is done. #62259 | ||
| connectContext.closeFlightSqlDeferredExecutors(); | ||
| // After the previous query was executed, there was no getStreamStatement to take away the result. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The new cleanup assumes that starting the next statement means the previous
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I looked into this and don't think it's a regression this PR introduces, nor something that can be properly fixed within this PR's scope. Details: Not a new failure mode / not a regression. Before this PR, every external-table batch-split query over Arrow Flight already failed with Interleaving two queries on one session is not a supported usage. A bearer token maps to a single shared The suggested fix needs a signal the FE does not have. For an external-table scan the result Leaks are already bounded. The "or the session is closed" fallback is implemented: If first-class concurrent Flight clients are ever needed, the right follow-up is a BE -> FE completion signal plus session serialization; I can track that separately. |
||
| connectContext.getFlightSqlChannel().reset(); | ||
| connectContext.clearFlightSqlEndpointsLocations(); | ||
|
|
@@ -280,6 +284,14 @@ private FlightInfo executeQueryStatement(String peerIdentity, ConnectContext con | |
| } | ||
| } | ||
| } catch (Throwable e) { | ||
| // GetFlightInfo failed (e.g. the BE Arrow schema fetch above timed out or returned an | ||
| // error) after this query's coordinator may already have been deferred during planning. | ||
| // No FlightInfo is returned, so no DoGet will ever pull this query's results; finalize | ||
| // the deferred coordinator now (releasing its external-table batch SplitSource, query | ||
| // queue slot and query registration) instead of leaking it until the next query starts | ||
| // or the connection is torn down. The previous query's deferred coordinator was already | ||
| // finalized at the top of this method, so this only closes this failed query. See #62259. | ||
| connectContext.closeFlightSqlDeferredExecutors(); | ||
| String errMsg = "get flight info statement failed, " + e.getMessage() + ", " + Util.getRootCauseMessage(e) | ||
| + ", error code: " + connectContext.getState().getErrorCode() + ", error msg: " | ||
| + connectContext.getState().getErrorMessage(); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -196,11 +196,20 @@ public void fetchArrowFlightSchema(int timeoutMs) { | |
| @Override | ||
| public void close() throws Exception { | ||
| ctx.setCommand(MysqlCommand.COM_SLEEP); | ||
| // Executors whose results are pulled from the BE keep their coordinator alive past | ||
| // GetFlightInfo (registered as deferred executors on the ConnectContext) so the BE can | ||
| // still fetch external-table splits during DoGet. Do NOT finalize those here; they are | ||
| // finalized when the next query starts or the connection is torn down. Executors that are | ||
| // not deferred (local results, or a query that already failed) are finalized now. See #62259. | ||
| for (StmtExecutor asynExecutor : returnResultFromRemoteExecutor) { | ||
| asynExecutor.finalizeQuery(); | ||
| if (!asynExecutor.isDeferredForArrowFlight()) { | ||
| asynExecutor.finalizeQuery(); | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This guard also skips cleanup when
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Confirmed — this is a real (bounded) leak. If the Arrow schema fetch in Fixed in 41a79a6 by finalizing the deferred coordinator on the Added |
||
| } | ||
| returnResultFromRemoteExecutor.clear(); | ||
| executor.finalizeQuery(); | ||
| if (executor != null && !executor.isDeferredForArrowFlight()) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P1] Keep deferred queries reachable by cancellation This branch keeps q1 active for the later |
||
| executor.finalizeQuery(); | ||
| } | ||
| ctx.clear(); | ||
| ConnectContext.remove(); | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P1] Make terminal teardown atomic with deferred registration
Token eviction or
CloseSessioncan callunregisterConnection()on another thread while this query is still insidecoordBase.exec(). If teardown drains the currently empty list and removes the context/token mapping first, the query then returns, adds itself here, andFlightSqlConnectProcessor.close()skips it because it is deferred. No later request or timeout can retrieve that removed context, so its Qe registration/instance accounting, queue token, coordinator, and batch SplitSources remain indefinitely. The monitor prevents list corruption but does not close this drain-then-add window. Please mark terminal teardown under the same synchronization and make a late add finalize/reject the executor, with a latch test for this ordering.