[sandbox] Fix AnalyticsQueryTask onCancel callback race - #22231
Conversation
PR Reviewer Guide 🔍(Review updated until commit 40da719)Here are some key observations to aid the review process:
|
PR Code Suggestions ✨Latest suggestions up to 38769dc
Previous suggestionsSuggestions up to commit 5faecbe
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #22231 +/- ##
============================================
- Coverage 73.43% 73.33% -0.11%
+ Complexity 75965 75858 -107
============================================
Files 6070 6070
Lines 344903 344903
Branches 49625 49625
============================================
- Hits 253285 252936 -349
- Misses 71493 71790 +297
- Partials 20125 20177 +52 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
5faecbe to
38769dc
Compare
|
Persistent review updated to latest commit 38769dc |
The framework registers AnalyticsQueryTask before doExecute forks to the search executor and installs the cancellation callback via QueryScheduler.setCancellationCallback. A cancel arriving in that window (server-side timeout, HTTP disconnect from RestCancellableNodeClient, parent-task cascade) fires onCancelled() with no callback installed and silently no-ops; the task is marked cancelled but the analytics query runs to completion. Re-check isCancelled() after the callback is installed and run it inline when a cancel was already observed. Switch consumption to getAndSet(null) so the install-side and onCancelled() paths cannot both fire it. Mirrors AnalyticsShardTask.setCancellationListener, which already has this guard. Signed-off-by: bowenlan-amzn <bowenlan23@gmail.com>
38769dc to
40da719
Compare
|
Persistent review updated to latest commit 40da719 |
…roject#22231) The framework registers AnalyticsQueryTask before doExecute forks to the search executor and installs the cancellation callback via QueryScheduler.setCancellationCallback. A cancel arriving in that window (server-side timeout, HTTP disconnect from RestCancellableNodeClient, parent-task cascade) fires onCancelled() with no callback installed and silently no-ops; the task is marked cancelled but the analytics query runs to completion. Re-check isCancelled() after the callback is installed and run it inline when a cancel was already observed. Switch consumption to getAndSet(null) so the install-side and onCancelled() paths cannot both fire it. Mirrors AnalyticsShardTask.setCancellationListener, which already has this guard. Signed-off-by: bowenlan-amzn <bowenlan23@gmail.com>
…roject#22231) The framework registers AnalyticsQueryTask before doExecute forks to the search executor and installs the cancellation callback via QueryScheduler.setCancellationCallback. A cancel arriving in that window (server-side timeout, HTTP disconnect from RestCancellableNodeClient, parent-task cascade) fires onCancelled() with no callback installed and silently no-ops; the task is marked cancelled but the analytics query runs to completion. Re-check isCancelled() after the callback is installed and run it inline when a cancel was already observed. Switch consumption to getAndSet(null) so the install-side and onCancelled() paths cannot both fire it. Mirrors AnalyticsShardTask.setCancellationListener, which already has this guard. Signed-off-by: bowenlan-amzn <bowenlan23@gmail.com>
Description
When the analytics-engine transport action runs on the coordinator, there's a time gap between when the task becomes available to cancel and when the cancel callback is wired up:
TransportAction.execute:101—taskManager.register(...)putsAnalyticsQueryTaskintocancellableTasks. From this moment on,_tasks/_cancelcan find and fire it.DefaultPlanExecutor.doExecute:414—searchExecutor.execute(...)queues the lambda containing planning + scheduler setup; the transport thread returns immediately.QueryScheduler.setCancellationCallback:139which callssetOnCancelCallback(...).The gap (1) → (3) is determined by SEARCH pool queue depth and Calcite planning latency (cold path with schema fetch). Sub-millisecond on a quiet cluster, noticeably reachable under load.
A cancel landing in that gap — server-side timeout, HTTP disconnect via
RestCancellableNodeClient(fires inline on the Netty event loop), or parent-task cascade — runsonCancelled()whileonCancelCallbackis still null. The current code returns silently; the task is marked cancelled but the analytics query keeps running to completion.This regresses the disconnect-cancel guarantee #22229 + opensearch-project/sql#5563 set up.
Fix
Mirror the pattern already used by
AnalyticsShardTask.setCancellationListeneron the data-node side: after the install-time CAS, re-checkisCancelled()and run the callback inline if it's already set. Switch consumption togetAndSet(null)so the install-side andonCancelled()paths can't both fire it.Test
testCallbackFiresImmediatelyIfAlreadyCancelledis the regression guard — empirically fails on the pre-fixAnalyticsQueryTask(callCount=0) and passes after this change. Other four tests confirm existing semantics unchanged: callback fires once on normal cancel, idempotent under repeated cancel, no-op when no callback installed, second-set throws.Check List
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.