Skip to content

Fix slot chain links for shared slots - #3615

Open
mzl2233 wants to merge 1 commit into
alibaba:1.8from
mzl2233:fix-processor-slot-singleton
Open

Fix slot chain links for shared slots#3615
mzl2233 wants to merge 1 commit into
alibaba:1.8from
mzl2233:fix-processor-slot-singleton

Conversation

@mzl2233

@mzl2233 mzl2233 commented May 14, 2026

Copy link
Copy Markdown

This updates the default processor slot chain so a shared slot instance no longer has its next pointer 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 test could not be run in this environment because mvn is not installed.

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@LearningGp

Copy link
Copy Markdown
Collaborator
image

@oss-sentinel-ai

Copy link
Copy Markdown

CLA Not Signed

The Contributor License Agreement (CLA) check is currently pending on this PR (license/cla: Contributor License Agreement is not signed yet.). This PR cannot be merged until the CLA is signed.

@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 license/cla status will turn green.


Automated check by github-manager-bot

@oss-sentinel-ai oss-sentinel-ai left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 exercises entry()/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 invoking fireEntry() 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 use new 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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[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();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants