Skip to content

[fix](be) Return missing-buffer errors for MySQL and Arrow result fetches - #67644

Open
Mryange wants to merge 2 commits into
apache:masterfrom
Mryange:fix-result-buffer-fetch-error
Open

Mryange wants to merge 2 commits into
apache:masterfrom
Mryange:fix-result-buffer-fetch-error

Conversation

@Mryange

@Mryange Mryange commented Sep 8, 2026 •

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Issue Number: N/A

Problem Summary: MySQL/JDBC and remote Arrow Flight/ADBC result fetches could wait for a timeout when the BE result buffer was missing or had been removed after cancellation.

Root cause: Both fetch_data and fetch_arrow_data logged failed buffer lookups and returned without completing the RPC callback. The result context destructors do not run the callback.

Both paths now call on_failure to return the lookup error and complete the RPC. The shared lookup error uses protocol-neutral wording and retains the query/instance ID. Four service-entry unit tests cover nonexistent and cancelled/removed buffers on both paths, checking the error response and exactly one completion callback.

Release note

Return missing-result-buffer errors promptly for MySQL/JDBC and remote Arrow Flight/ADBC result fetches instead of waiting for a timeout.

Check List (For Author)

  • Test

    • Regression test
    • Unit Test: Four cases added in InternalServiceFetchTest; not compiled or run locally yet.
    • Manual test
    • Validation: clang-format 16, build hygiene, and git diff --check passed. CI validation remains pending; existing BE UT, NonConcurrent Regression, and coverage failures have not been diagnosed because TeamCity logs require authentication.
  • Behavior changed:

    • No.
    • Yes. Missing-buffer fetches return errors promptly; the error message is protocol-neutral. No RPC schema changes.
  • Does this need documentation?

    • No.
    • Yes.

Check List (For Reviewer who merge this PR)

  • Confirm the release note
  • Confirm test cases
  • Confirm document
  • Add branch pick label

@hello-stephen

Copy link
Copy Markdown
Contributor

Thank you for your contribution to Apache Doris.
Don't know what should be done next? See How to process your PR.

Please clearly describe your PR:

  1. What problem was fixed (it's best to include specific error reporting information). How it was fixed.
  2. Which behaviors were modified. What was the previous behavior, what is it now, why was it modified, and what possible impacts might there be.
  3. What features were added. Why was this function added?
  4. Which code was refactored and why was this part of the code refactored?
  5. Which functions were optimized and what is the difference before and after the optimization?

@Mryange

Mryange commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

run buildall

1 similar comment
@Mryange

Mryange commented Sep 10, 2026

Copy link
Copy Markdown
Contributor Author

run buildall

@hello-stephen

Copy link
Copy Markdown
Contributor

BE Regression && UT Coverage Report

Increment line coverage 0.00% (0/1) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 75.42% (34202/45350)
Line Coverage 60.08% (381580/635101)
Region Coverage 56.21% (319124/567771)
Branch Coverage 56.73% (144822/255289)

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-H: Total hot run time: 17142 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
Tpch sf100 test result on commit fbb6e1138d7c8d476f70daeb7c1127abd30a4e2e, data reload: false

------ Round 1 ----------------------------------
============================================
q1	17591	3080	3077	3077
q2	2111	266	227	227
q3	10222	886	522	522
q4	4667	262	205	205
q5	7661	574	395	395
q6	139	118	97	97
q7	522	510	399	399
q8	9239	911	968	911
q9	3462	2433	2421	2421
q10	6528	862	732	732
q11	400	196	178	178
q12	613	268	210	210
q13	18124	1552	1213	1213
q14	167	153	139	139
q15	q16	434	408	378	378
q17	1354	877	789	789
q18	3095	2336	2310	2310
q19	1271	868	800	800
q20	386	276	210	210
q21	5658	1697	1779	1697
q22	325	266	232	232
Total cold run time: 93969 ms
Total hot run time: 17142 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	3438	3367	3339	3339
q2	506	397	361	361
q3	2275	2358	2163	2163
q4	1211	1182	901	901
q5	2233	2197	2201	2197
q6	178	128	88	88
q7	1086	936	894	894
q8	1600	1398	1410	1398
q9	3166	3171	3160	3160
q10	1896	1862	1641	1641
q11	356	269	254	254
q12	463	437	340	340
q13	1535	1541	1174	1174
q14	167	174	162	162
q15	q16	395	403	374	374
q17	3650	3456	3337	3337
q18	4930	4552	5091	4552
q19	958	865	889	865
q20	1042	969	826	826
q21	3907	3186	3310	3186
q22	403	339	308	308
Total cold run time: 35395 ms
Total hot run time: 31520 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 83250 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
TPC-DS sf100 test result on commit fbb6e1138d7c8d476f70daeb7c1127abd30a4e2e, data reload: false

query5	4250	425	345	345
query6	392	138	123	123
query7	4936	433	239	239
query8	286	129	124	124
query9	8699	2925	2920	2920
query10	378	217	181	181
query11	5386	1048	930	930
query12	129	73	71	71
query13	1189	445	307	307
query14	6190	2222	2112	2112
query14_1	2031	2019	1997	1997
query15	182	123	114	114
query16	916	377	365	365
query17	804	461	370	370
query18	2340	338	237	237
query19	163	139	108	108
query20	85	73	73	73
query21	205	103	87	87
query22	5494	5551	5553	5551
query23	6918	6624	6123	6123
query23_1	6397	6029	6111	6029
query24	7396	1110	785	785
query24_1	789	795	797	795
query25	427	306	265	265
query26	1226	239	134	134
query27	2778	423	259	259
query28	4687	1510	1514	1510
query29	918	443	362	362
query30	251	159	136	136
query31	831	413	339	339
query32	130	77	81	77
query33	470	222	175	175
query34	981	845	499	499
query35	408	401	358	358
query36	573	565	565	565
query37	119	81	80	80
query38	1003	860	834	834
query39	511	518	471	471
query39_1	463	458	485	458
query40	202	91	79	79
query41	60	57	58	57
query42	75	76	74	74
query43	243	242	211	211
query44	997	532	533	532
query45	116	108	102	102
query46	753	809	535	535
query47	771	761	723	723
query48	295	311	238	238
query49	551	248	223	223
query50	716	264	190	190
query51	8174	8162	8240	8162
query52	70	75	60	60
query53	207	227	143	143
query54	214	164	164	164
query55	80	59	55	55
query56	192	207	172	172
query57	721	691	668	668
query58	197	166	157	157
query59	1205	1237	1108	1108
query60	247	180	168	168
query61	124	128	123	123
query62	390	225	176	176
query63	184	140	144	140
query64	2839	688	565	565
query65	1669	1639	1630	1630
query66	1775	266	196	196
query67	9832	10040	10278	10040
query68	2751	1137	649	649
query69	340	227	186	186
query70	683	629	608	608
query71	242	182	165	165
query72	2294	1664	1563	1563
query73	644	570	334	334
query74	1562	1237	1160	1160
query75	1181	1108	955	955
query76	2290	734	516	516
query77	259	251	221	221
query78	4062	3751	3258	3258
query79	2515	876	593	593
query80	1577	321	260	260
query81	484	164	135	135
query82	603	139	99	99
query83	272	199	196	196
query84	287	112	88	88
query85	767	329	283	283
query86	378	197	167	167
query87	1026	981	914	914
query88	2770	2105	2111	2105
query89	280	197	185	185
query90	1998	127	130	127
query91	127	115	92	92
query92	80	70	71	70
query93	1434	1032	715	715
query94	621	248	200	200
query95	509	320	225	225
query96	813	577	274	274
query97	1096	1034	1021	1021
query98	164	133	140	133
query99	424	345	309	309
Total cold run time: 178161 ms
Total hot run time: 83250 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
ClickBench: Total hot run time: 14.82 s
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/clickbench-tools
ClickBench test result on commit fbb6e1138d7c8d476f70daeb7c1127abd30a4e2e, data reload: false

query1	0.01	0.01	0.01
query2	0.07	0.04	0.04
query3	0.25	0.12	0.11
query4	1.60	0.10	0.10
query5	0.17	0.17	0.16
query6	1.27	0.70	0.67
query7	0.03	0.01	0.01
query8	0.04	0.04	0.03
query9	0.29	0.22	0.22
query10	0.37	0.36	0.35
query11	0.16	0.12	0.12
query12	0.15	0.12	0.12
query13	0.31	0.32	0.32
query14	0.46	0.45	0.46
query15	0.37	0.35	0.35
query16	0.22	0.24	0.23
query17	0.69	0.75	0.74
query18	0.18	0.17	0.16
query19	1.18	1.22	1.13
query20	0.02	0.01	0.01
query21	15.47	0.18	0.11
query22	5.06	0.04	0.05
query23	16.16	0.26	0.10
query24	3.07	0.31	0.26
query25	0.10	0.04	0.03
query26	0.74	0.17	0.11
query27	0.04	0.03	0.02
query28	3.63	0.57	0.28
query29	12.47	3.21	2.59
query30	0.27	0.11	0.13
query31	2.75	0.39	0.17
query32	3.53	0.31	0.22
query33	1.52	1.38	1.56
query34	15.32	2.28	1.81
query35	1.78	1.74	1.74
query36	0.45	0.29	0.28
query37	0.06	0.04	0.04
query38	0.04	0.03	0.03
query39	0.03	0.03	0.03
query40	0.12	0.08	0.08
query41	0.07	0.02	0.02
query42	0.03	0.02	0.02
query43	0.03	0.02	0.03
Total cold run time: 90.58 s
Total hot run time: 14.82 s

@HappenLee

Copy link
Copy Markdown
Contributor

Reviewed head fbb6e11. The added ctx->on_failure(st) is correct for MySQL/JDBC: it serializes the failure status and runs the RPC completion callback before returning. A few follow-ups would improve this fix:

  1. Please cover the Arrow Flight / ADBC result-fetch path too. In PInternalService::fetch_arrow_data() (be/src/service/internal_service.cpp, lines 702–705), a failed find_buffer() still only logs and returns. If the result buffer has been removed before a remote fetch, GetArrowResultBatchCtx's default destructor does not run done, so ArrowFlightBatchRemoteReader::_fetch_data() waits for the RPC timeout rather than receiving the lookup error. Please also call ctx->on_failure(st) in that branch. This is an existing parallel-path omission, not a regression introduced by the new line.

  2. Please add focused tests for missing/removed buffers. Existing GetResultBatchCtxTest tests on_failure() directly, but does not prove that either service entry point invokes it when lookup fails. Exercise fetch_data and fetch_arrow_data with a nonexistent or cancelled buffer, and assert a non-OK response and exactly one completion callback. The current incremental coverage report is 0/1 for this change.

  3. Consider making the lookup error protocol-neutral. ResultBufferMgr::find_buffer() currently returns no arrow schema for this query, maybe query has been canceled, which this fix also exposes to MySQL/JDBC clients. A result-buffer-unavailable message retaining the query/instance ID would be clearer; the original cancellation reason is not retained by this lookup.

Before merging, please also investigate/re-run the failed BE UT, NonConcurrent Regression, and coverage checks. I could not inspect the TeamCity failure details because the API returned 401, so I am not attributing those failures to this change. This review traced the code paths; no build or runtime test was run locally.

### What problem does this PR solve?

Problem Summary: A remote Arrow result fetch returned without completing its RPC when the result buffer had been removed, causing the reader to wait for an RPC timeout. Report lookup failures through the completion callback, use a protocol-neutral lookup error retaining the query or instance ID, and add service-entry tests for missing and cancelled buffers on both MySQL and Arrow paths.

### Release note

Remote Arrow result fetches return missing-buffer errors promptly instead of waiting for RPC timeout.

### Check List (For Author)

- Test: Added four BE unit tests; not compiled or run pending source review. clang-format 16, build hygiene, and git diff --check passed.
- Behavior changed: Yes. Arrow missing-buffer fetches complete with an error; lookup errors use protocol-neutral wording.
- Does this need documentation: No
@Mryange Mryange changed the title [fix](be) Return fetch errors when result buffer is unavailable [fix](be) Return missing-buffer errors for MySQL and Arrow result fetches Oct 8, 2026
@Mryange

Mryange commented Oct 8, 2026

Copy link
Copy Markdown
Contributor Author

run buildall

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-H: Total hot run time: 29228 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
Tpch sf100 test result on commit 18c691537ad4ea49c687f0fa681608c197698528, data reload: false

------ Round 1 ----------------------------------
============================================
q1	17593	5766	5608	5608
q2	2115	315	266	266
q3	10245	1406	847	847
q4	4694	471	348	348
q5	7447	799	538	538
q6	183	188	156	156
q7	781	830	598	598
q8	9260	1331	1179	1179
q9	5696	4483	4442	4442
q10	6815	1278	1014	1014
q11	446	261	230	230
q12	633	410	283	283
q13	18062	3068	2386	2386
q14	275	271	236	236
q15	q16	732	732	662	662
q17	1187	733	566	566
q18	7162	6262	6174	6174
q19	1162	949	608	608
q20	382	337	227	227
q21	5623	2570	2613	2570
q22	392	344	290	290
Total cold run time: 100885 ms
Total hot run time: 29228 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	6695	6664	6749	6664
q2	740	640	589	589
q3	5429	5821	5237	5237
q4	2032	2153	1622	1622
q5	5775	5712	5662	5662
q6	243	195	156	156
q7	2272	2033	1786	1786
q8	3128	2878	2713	2713
q9	8224	8254	8186	8186
q10	3685	3597	3244	3244
q11	616	415	370	370
q12	675	738	491	491
q13	2751	3097	2388	2388
q14	287	288	264	264
q15	q16	661	670	618	618
q17	7835	6995	6883	6883
q18	13104	12240	13094	12240
q19	914	806	795	795
q20	2167	2187	1936	1936
q21	5898	4750	4853	4750
q22	508	451	407	407
Total cold run time: 73639 ms
Total hot run time: 67001 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 152141 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
TPC-DS sf100 test result on commit 18c691537ad4ea49c687f0fa681608c197698528, data reload: false

query5	4322	586	442	442
query6	415	205	187	187
query7	4805	438	228	228
query8	321	188	169	169
query9	8801	3886	3877	3877
query10	470	309	258	258
query11	5996	3410	3186	3186
query12	152	89	83	83
query13	1250	439	324	324
query14	6526	4836	4472	4472
query14_1	4268	4253	4273	4253
query15	210	208	183	183
query16	950	440	410	410
query17	863	641	554	554
query18	2420	421	316	316
query19	194	163	132	132
query20	80	81	79	79
query21	221	134	114	114
query22	13040	13081	12981	12981
query23	13152	12460	12223	12223
query23_1	12177	12334	12298	12298
query24	7122	827	457	457
query24_1	469	472	467	467
query25	535	436	359	359
query26	1248	258	140	140
query27	2768	441	269	269
query28	4632	1896	1937	1896
query29	1551	597	451	451
query30	301	224	196	196
query31	893	772	655	655
query32	144	108	104	104
query33	525	308	251	251
query34	995	842	497	497
query35	761	757	643	643
query36	846	818	726	726
query37	128	107	103	103
query38	1775	1746	1685	1685
query39	711	717	699	699
query39_1	662	678	666	666
query40	224	123	98	98
query41	112	63	62	62
query42	93	83	82	82
query43	361	370	333	333
query44	1296	683	688	683
query45	179	180	158	158
query46	830	971	551	551
query47	2964	3011	2961	2961
query48	295	308	213	213
query49	560	395	285	285
query50	673	267	226	226
query51	10479	10557	10157	10157
query52	76	82	69	69
query53	182	213	147	147
query54	249	192	179	179
query55	71	67	63	63
query56	212	211	212	211
query57	1638	1609	1574	1574
query58	272	262	246	246
query59	2364	2379	2140	2140
query60	278	239	250	239
query61	151	154	140	140
query62	393	340	287	287
query63	189	171	166	166
query64	2762	966	782	782
query65	3422	3391	3393	3391
query66	1755	409	311	311
query67	20188	20100	20043	20043
query68	3201	985	611	611
query69	392	280	247	247
query70	911	863	822	822
query71	284	244	211	211
query72	2770	2538	2254	2254
query73	515	523	295	295
query74	4584	4471	4299	4299
query75	2334	2282	1953	1953
query76	2374	995	625	625
query77	367	381	307	307
query78	8984	8934	8487	8487
query79	957	833	515	515
query80	527	420	352	352
query81	526	320	279	279
query82	574	135	111	111
query83	297	199	177	177
query84	308	127	100	100
query85	785	442	377	377
query86	325	240	231	231
query87	2010	1953	1824	1824
query88	3573	2683	2685	2683
query89	339	302	259	259
query90	1904	185	186	185
query91	223	150	128	128
query92	97	89	91	89
query93	991	968	565	565
query94	442	292	291	291
query95	595	348	401	348
query96	662	514	230	230
query97	2450	2413	2288	2288
query98	158	148	142	142
query99	779	786	664	664
Total cold run time: 233870 ms
Total hot run time: 152141 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
ClickBench: Total hot run time: 24.34 s
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/clickbench-tools
ClickBench test result on commit 18c691537ad4ea49c687f0fa681608c197698528, data reload: false

query1	0.00	0.00	0.00
query2	0.10	0.05	0.04
query3	0.26	0.12	0.11
query4	1.60	0.12	0.11
query5	0.24	0.26	0.22
query6	1.20	0.68	0.68
query7	0.03	0.00	0.00
query8	0.06	0.04	0.04
query9	0.41	0.35	0.35
query10	0.60	0.63	0.57
query11	0.20	0.13	0.14
query12	0.18	0.14	0.14
query13	0.49	0.50	0.48
query14	1.00	0.96	0.97
query15	0.62	0.60	0.62
query16	0.35	0.35	0.35
query17	1.15	1.12	1.13
query18	0.23	0.21	0.21
query19	2.05	1.96	1.99
query20	0.02	0.01	0.01
query21	15.46	0.27	0.15
query22	4.42	0.06	0.06
query23	16.26	0.31	0.12
query24	3.21	0.58	0.40
query25	0.10	0.07	0.05
query26	1.13	0.25	0.15
query27	0.06	0.04	0.03
query28	2.04	0.63	0.41
query29	12.48	4.11	3.27
query30	0.30	0.15	0.14
query31	2.77	0.58	0.35
query32	3.22	0.60	0.52
query33	3.13	3.14	3.24
query34	15.49	3.93	3.35
query35	3.32	3.33	3.35
query36	0.54	0.43	0.46
query37	0.09	0.07	0.06
query38	0.05	0.04	0.04
query39	0.04	0.03	0.03
query40	0.18	0.14	0.14
query41	0.08	0.03	0.03
query42	0.03	0.03	0.03
query43	0.05	0.03	0.04
Total cold run time: 95.24 s
Total hot run time: 24.34 s

@hello-stephen

Copy link
Copy Markdown
Contributor

BE UT Coverage Report

Increment line coverage 100.00% (2/2) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 65.11% (30788/47288)
Line Coverage 49.88% (323488/648476)
Region Coverage 45.37% (261491/576382)
Branch Coverage 47.05% (122743/260882)

@hello-stephen

Copy link
Copy Markdown
Contributor

BE Regression && UT Coverage Report

Increment line coverage 100.00% (2/2) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 76.69% (35100/45766)
Line Coverage 61.89% (396782/641123)
Region Coverage 58.33% (334805/573995)
Branch Coverage 59.21% (154000/260079)

@Mryange

Mryange commented Oct 9, 2026

Copy link
Copy Markdown
Contributor Author

/review

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Complete static review of PR #67644 at 18c6915. No new inline findings and no existing P0/P1 inline blockers. The required normal and risk-focused review passes converged in one round; all three initial risk questions were resolved.

Critical checkpoints:

  • Goal and scope: The change completes missing-buffer MySQL and remote Arrow fetch RPCs with a non-OK status and an ID-bearing error. It is focused on the two RPC branches and their shared lookup wording. Four new BE service tests cover absent and canceled buffers for both protocols; they were inspected, not executed.
  • Concurrency: The result-manager map lock protects lookup/removal, a shared pointer preserves a found buffer, and the buffer lock handles a concurrent cancel. Missing lookups do not enqueue a context. Both failure callbacks serialize the error and invoke the completion closure once. No new lock-order or heavy-work-under-lock issue was substantiated.
  • Lifecycle: The Arrow test joins its worker before inspecting or destroying RPC arguments. Service teardown releases its bthread keys and joins pools; fixture teardown restores saved ExecEnv pointers and configuration. The apparent private/protected test access is allowed by the BE unit-test target's -fno-access-control flag.
  • Configuration, compatibility, and parallel paths: No new config, FE-BE field, function symbol, or storage format was introduced. MySQL and remote Arrow clients check the returned status before packet fields; the local Arrow and schema lookup paths already propagate lookup errors. No caller depends on the former Arrow-specific error text.
  • Conditions, tests, and results: A null result of find_buffer is the genuine absent or wrong-type case. The four tests check non-OK status and exactly one callback; be/test/CMakeLists.txt includes the test file. No result files changed. Builds and tests were prohibited by the review prompt, so runtime validation remains unverified.
  • Observability and performance: Existing warning logs and the returned error retain the query/instance ID. The normal fetch path adds no new work beyond the unchanged lookup. No new metric is required for this error path.
  • Transactions, persistence, data writes, failover, and FE-BE variable propagation: None are changed by this PR. No other substantiated correctness, memory, or coverage issue remains.

User focus: No additional review focus was provided.

morningman added a commit to morningman/doris that referenced this pull request Nov 2, 2026
…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]>
@morningman

Copy link
Copy Markdown
Contributor

Hi @Mryange, a heads-up about an overlap: #69945 (the BE half of branch-4.x #68630, which cancels an Arrow Flight query when its result stream is closed early) adds the same ctx->on_failure(st); after the "Result buffer not found" log in PInternalService::fetch_arrow_data. The hunk is identical to the one in this PR, so the two merge cleanly in either order and nothing needs to change here. #69945 does not touch fetch_data (the MySQL path) or the find_buffer message, which stay with this PR.

morningman added a commit that referenced this pull request Nov 4, 2026
…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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants