Carry the partial_result override off ThreadContext so it survives the security handoff - #5758
Conversation
…e security handoff The per-request partial_result override was stored in Log4j ThreadContext (QueryContext.setPartialResultOverride), set on the transport thread and read on the worker/planning thread by CalciteLogicalIndexScan.tryPartialResultAggregate. The security plugin's transport interceptor does not preserve Log4j ThreadContext across that handoff, so on a security-enabled cluster the override is lost and isPartialResultEnabled silently falls back to the cluster setting. A request that sends partial_result=false to opt out therefore still gets a partial result (with a PARTIAL_RESULT warning) when the cluster setting is on -- the setting's own path is unaffected because it is read from osIndex.getSettings() on the worker thread. This is the same transport->worker handoff bug opensearch-project#5739 fixed for warningsSupported; opensearch-project#5739 moved warningsSupported onto the object graph but left the override on ThreadContext (reasoning the settings fallback made it resilient -- but the fallback masks the drop). Carry the override the same way warningsSupported is carried: PPLQueryRequest.partialResult -> AbstractPlan -> QueryPlan.execute() (worker thread) -> CalcitePlanContext (in its thread-local snapshot, reset per query). tryPartialResultAggregate now reads the override from CalcitePlanContext and falls back to the cluster setting. Removes the dead QueryContext override methods. Reproduced and verified with a new PartialResultSecurityIT case (perRequestPartialResultFalseOverridesClusterSettingUnderSecurity): fails before this change (complete expected, partial returned), passes after. Signed-off-by: Kai Huang <ahkcs@amazon.com>
PR Reviewer Guide 🔍Here are some key observations to aid the review process:
|
PR Code Suggestions ✨Explore these optional code suggestions:
|
RyanL1997
left a comment
There was a problem hiding this comment.
I'm just thinking of a mechanism to catch the changes like this which will potentially impact the security path. With the current CI setup I think there is still a gap for us to test with security enabled.
Related
warningsSupportedgate off Log4jThreadContextonto the object graphThis PR is a direct follow-up: the same transport→worker
ThreadContexthandoff bug, for the siblingpartial_resultoverride that #5743 left behind.Description
The per-request
partial_resultoverride (thepartial_resultboolean in the PPL request body, e.g. from the Dashboards toggle) was stored in Log4jThreadContextviaQueryContext.setPartialResultOverride. It is set on the transport thread (TransportPPLQueryAction) and read on the worker/planning thread byCalciteLogicalIndexScan.tryPartialResultAggregate→QueryContext.isPartialResultEnabled.The security plugin's transport interceptor does not preserve Log4j
ThreadContextacross the transport→worker handoff. So on a security-enabled cluster the override is dropped before the worker thread reads it, andisPartialResultEnabledsilently falls back to the cluster setting. A request that sendspartial_result: falseto opt out therefore still returns a partial result with aPARTIAL_RESULTwarning whenever the cluster settingplugins.query.partial_result.on_mapping_conflict.enabledis on.Only the override is affected — the cluster-setting path is read from
osIndex.getSettings()on the worker thread, so it works. The bug is invisible without the security plugin (a plain./gradlew runcluster preserves theThreadContext), which is why it wasn't caught earlier.This is the same transport→worker handoff bug fixed in #5743 (issue #5739) for
warningsSupported. That change movedwarningsSupportedonto the object graph but left thepartial_resultoverride onThreadContext, reasoning the settings fallback made it resilient — but the fallback masks the drop rather than surviving it.Fix
Carry the override the same way
warningsSupportedis carried, offThreadContextand onto the object graph:PPLQueryRequest.partialResult(set from the request on the transport thread) →AbstractPlan.partialResultOverride→QueryPlan.execute()(runs on the worker thread) →CalcitePlanContext.setPartialResultOverride(...). It is included inCalcitePlanContext's thread-local snapshot (so it survives the planner's own complex-worker handoff) and reset per query.tryPartialResultAggregatenow reads the override fromCalcitePlanContextand falls back to the cluster setting. The now-deadQueryContext.setPartialResultOverride/isPartialResultEnabled(and the transport-side clear) are removed.Testing
Added
PartialResultSecurityIT.perRequestPartialResultFalseOverridesClusterSettingUnderSecurity— with the cluster setting on, an authenticated user sendspartial_result: falseand must get the complete result (no warning). Run underintegTestWithSecurity:perRequestPartialResultFalseOverridesClusterSettingUnderSecurityexpected:<3> but was:<2>(override dropped → partial)partialResultWarningSurvivesSecurityHandoffcompleteResultCarriesNoWarningWithSecurityQueryPlanTestandPartialResultAggregatePushdownTestalso pass;:opensearch-sql-plugin:compileJava+:integ-test:compileTestJavaclean.Check List