Fix Reactor adapter async-context race in SentinelReactorSubscriber.currentContext - #3624
Fix Reactor adapter async-context race in SentinelReactorSubscriber.currentContext#3624wuwen5 wants to merge 1 commit into
SentinelReactorSubscriber.currentContext#3624Conversation
…currentContext` (#1) * fix(reactor): avoid async context race in currentContext and add regression test Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: wuwen5 <5037807+wuwen5@users.noreply.github.com>
d5bdc20 to
b6f58d3
Compare
|
@LearningGp done.
|
angus-guo
left a comment
There was a problem hiding this comment.
Excellent fix for a subtle race condition! ✅
Problem Analysis
The root cause is a check-then-act race in currentContext():
// Line 65: First read
com.alibaba.csp.sentinel.context.Context sentinelContext = currentEntry.getAsyncContext();
if (sentinelContext == null) {
return actual.currentContext();
}
// Line 70: Second read - race window here!
return actual.currentContext()
.put(SentinelReactorConstants.SENTINEL_CONTEXT_KEY, currentEntry.getAsyncContext());Between the two getAsyncContext() calls, a concurrent cancellation thread can:
- Call
tryCompleteEntry()→currentEntry.exit() - Which triggers
AsyncEntry.clearEntryContext()→asyncContext = null - The second
getAsyncContext()now returnsnull - Reactor's
Context.put(key, value)throws NPE whenvalue == null
Solution Quality
Core fix: One-line change using the already-captured sentinelContext variable
.put(SentinelReactorConstants.SENTINEL_CONTEXT_KEY, sentinelContext); // ✅ Use captured valueTest coverage: The regression test is excellent:
- Uses reflection to verify internal state before/after the race
- Deterministically triggers the race via a mock
CoreSubscriber.currentContext()that cancels mid-read - Asserts both the fix (no NPE) and the invariant (captured context propagates correctly)
Impact
- Severity: High — affects all reactive Sentinel users under concurrent load
- Scope: All versions 1.8.6+ (verified 1.8.7, 1.8.8, 1.8.9 all have the bug)
- Risk: Minimal — changes only the variable reference, no behavior change
Recommendation
Approve and merge immediately. This is a production-critical fix that's been waiting 3 months.
Fixes #3556
cc @sczyh30 @jasonjoo2010 for final approval
oss-sentinel-ai
left a comment
There was a problem hiding this comment.
Summary
LGTM — the fix correctly removes the check-then-act race in SentinelReactorSubscriber.currentContext() by reusing the already-captured non-null async context instead of re-reading currentEntry.getAsyncContext(), closing the window where a concurrent cancel/exit could pass null to Context.put. The added regression test deterministically drives the interleaving and verifies both context propagation and async-context cleanup. Entry/exit pairing and exception-tracing semantics are preserved, the change is Java 8 compatible, and no public API is affected.
Automated review by github-manager-bot

Describe what this PR does / why we need it
SentinelReactorSubscriber.currentContext()could throwNullPointerException: valueunder concurrent termination because it readcurrentEntry.getAsyncContext()twice. IfAsyncEntry.exit()cleared async context between those reads,Context.put(...)receivednull.Race fix in context propagation
currentContext()to use the already-captured localsentinelContextwhen putting Sentinel context into ReactorContext.currentEntry.getAsyncContext().Targeted regression coverage
SentinelReactorSubscriberTestin the reactor adapter test module.currentContext()captures a non-null async context, a concurrent cancel path exits/clears context, andcurrentContext()must still propagate the captured value (the pre-fix path would passnulltoContext.put).某Redis Reactive请求先发生异常
→ Reactor进入onError链路
→ SentinelReactorSubscriber.currentContext()
→ Context.put(null)
→ NullPointerException
→ 未捕获异常打穿Netty EventExecutor
→ lettuce-eventExecutorLoop-1-4 号线程死亡(threadStatus=2)
→ executor terminated
→ 后续Redis Reactive callback无法调度
→ Spring Session Reactive Pipeline部分卡死 (重启恢复)
Does this pull request fix one issue?
fixed #3556 #1907
This pull request was created from Copilot chat.