Fix slot chain links for shared slots - #3615
Conversation
|
|
|
CLA Not Signed The Contributor License Agreement (CLA) check is currently pending on this PR ( @mzl2233 please sign the CLA via the CLA assistant badge in the comment above, or visit https://cla-assistant.io/alibaba/Sentinel. Once signed, the Automated check by github-manager-bot |
oss-sentinel-ai
left a comment
There was a problem hiding this comment.
Summary
This PR reworks DefaultProcessorSlotChain.addLast() to wrap each added slot in a per-chain anonymous node, aiming to stop a shared slot instance's next pointer from being overwritten when the same slot is added to multiple chains. The goal is reasonable, but the implementation breaks slot-chain traversal entirely. Chain progression in Sentinel is driven by each slot calling its own fireEntry()/fireExit() at the end of entry()/exit(), which walks the slot's own next pointer. After this change the chain only links wrapper nodes — the real slot's next is never set, and nothing invokes the wrapper's fireEntry() — so execution stops after the first slot. I verified this empirically against the branch: a 3-slot chain runs only the first slot with the patch (ENTRY_ORDER=[A]) versus [A, B, C] on the current 1.8 code, and exit unwinding breaks the same way. On the default chain this would silently skip FlowSlot, DefaultCircuitBreakerSlot, DegradeSlot, SystemSlot and AuthoritySlot for every request — disabling flow control and circuit breaking and corrupting entry/exit statistics. The added test also does not compile (ProcessorSlotChain is abstract, so new ProcessorSlotChain() fails testCompile). Requesting changes.
Findings
- [Critical] sentinel-core/src/main/java/com/alibaba/csp/sentinel/slotchain/DefaultProcessorSlotChain.java:58 — delegating to
protocolProcessor.transformEntry(...)stops traversal at the first slot (verified empirically); flow control / circuit breaking / degrade / system slots would be skipped for every request - [Warning] sentinel-core/src/main/java/com/alibaba/csp/sentinel/slotchain/DefaultProcessorSlotChain.java:66 —
addFirst(...)is not wrapped, so the same shared-slot corruption remains on that path - [Warning] sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/DefaultSlotChainBuilderTest.java:98 —
new ProcessorSlotChain()does not compile (abstract); the test also never exercisesentry()/exit()so it can't catch the traversal break
Suggestions
- The wrapper would need to control progression itself (call the slot's logic, then advance via the wrapper's own
fireEntry()), but that conflicts with real slots invokingfireEntry()internally — a workable fix is to give each chain its own slot instances (builder creates per-chain instances) or keep next-linkage chain-scoped without mutating shared slots. - Please make the regression test run
entry()/exit()end-to-end on a chain that shares a slot across two chains, asserting every slot executes in order and exits in reverse, and usenew DefaultProcessorSlotChain().
Automated review by github-manager-bot
| @Override | ||
| public void entry(Context context, ResourceWrapper resourceWrapper, Object t, int count, boolean prioritized, | ||
| Object... args) throws Throwable { | ||
| protocolProcessor.transformEntry(context, resourceWrapper, t, count, prioritized, args); |
There was a problem hiding this comment.
[Critical] This delegation breaks slot-chain traversal. In this codebase, chain progression is driven by each slot calling its own fireEntry()/fireExit() at the end of entry()/exit() (e.g. StatisticSlot.entry() → fireEntry(...)), which advances through the slot's own next pointer. With this wrapper design the chain only links wrapper nodes — the real slot's next is never set, and nothing ever invokes the wrapper's fireEntry() — so execution stops after the first slot. Verified empirically against this branch: building a DefaultProcessorSlotChain with three slots and calling entry() executes only the first slot (ENTRY_ORDER=[A] vs [A, B, C] on current 1.8; exit unwinding breaks the same way). Applied to the default chain, FlowSlot, DefaultCircuitBreakerSlot, DegradeSlot, SystemSlot and AuthoritySlot would be silently skipped for every request — disabling Sentinel's core protection and corrupting entry/exit pairing and statistics. Note the fix cannot simply call the downstream from the wrapper after the real slot's entry() returns: downstream execution must stay inside the real slot's fireEntry (e.g. StatisticSlot records pass/block based on exceptions thrown inside its own fireEntry try-block). Consider an approach where each chain owns its own next-links without mutating shared slots (e.g. the builder creates per-chain slot instances, or linkage is kept chain-scoped).
| protocolProcessor.exit(context, resourceWrapper, count, args); | ||
| } | ||
| }; | ||
| end.setNext(processor); |
There was a problem hiding this comment.
[Warning] addFirst(...) above is not wrapped — it still calls protocolProcessor.setNext(...) on the raw slot, so the same shared-slot link corruption the PR targets remains for any addFirst usage. If a wrapping approach is kept, both insertion paths need consistent treatment.
|
|
||
| @Test | ||
| public void testAddSingletonSlotInDifferentChains() { | ||
| ProcessorSlotChain chain = new ProcessorSlotChain(); |
There was a problem hiding this comment.
[Warning] This test does not compile: ProcessorSlotChain is abstract (new ProcessorSlotChain() → "ProcessorSlotChain is abstract; cannot be instantiated"), so mvn -pl sentinel-core testCompile fails before any test runs — use new DefaultProcessorSlotChain(). Beyond compilation: the assertions compare against raw slots (assertSame(firstNext, next.getNext())), but getNext() traversal now returns wrapper nodes, and the test never executes chain.entry()/exit(), so it could not detect the traversal break above. A useful regression test here should build a chain with shared slots and run entry/exit end-to-end, asserting every slot executes in order and exits in reverse.

This updates the default processor slot chain so a shared slot instance no longer has its
nextpointer overwritten when it is added to multiple chains. The chain now keeps its own link node for each added slot and delegates processing to the original slot, preserving each chain’s local successor. A regression test covers building two chains with the same slot instance followed by different downstream slots.Test:
mvn -pl sentinel-core -Dtest=DefaultSlotChainBuilderTest testcould not be run in this environment becausemvnis not installed.