Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package com.alibaba.csp.sentinel.slotchain;

import com.alibaba.csp.sentinel.context.Context;
import com.alibaba.csp.sentinel.slots.block.flow.PrototypeSlotWrapper;

/**
* @author qinan.qn
Expand Down Expand Up @@ -50,8 +51,9 @@ public void addFirst(AbstractLinkedProcessorSlot<?> protocolProcessor) {

@Override
public void addLast(AbstractLinkedProcessorSlot<?> protocolProcessor) {
end.setNext(protocolProcessor);
end = protocolProcessor;
PrototypeSlotWrapper processor = new PrototypeSlotWrapper(protocolProcessor);
end.setNext(processor);
end = processor;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.alibaba.csp.sentinel.slots.block.flow;

import com.alibaba.csp.sentinel.context.Context;
import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot;
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;

/**
* @author joyce
* @date 2026/5/14
* @since 1.8.5
**/
public class PrototypeSlotWrapper extends AbstractLinkedProcessorSlot<Object> {

private final AbstractLinkedProcessorSlot delegate;

public PrototypeSlotWrapper(AbstractLinkedProcessorSlot delegate) {
this.delegate = delegate;
}

@Override
public void fireEntry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args) throws Throwable {
this.delegate.fireEntry(context, resourceWrapper, obj, count, prioritized, args);
}

@Override
public void fireExit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
this.delegate.fireExit(context, resourceWrapper, count, args);
}

@Override
public void entry(Context context, ResourceWrapper resourceWrapper, Object param, int count, boolean prioritized, Object... args) throws Throwable {
delegate.entry(context, resourceWrapper, param, count, prioritized, args);
}

@Override
public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
delegate.exit(context, resourceWrapper, count, args);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.alibaba.csp.sentinel.slots;

import com.alibaba.csp.sentinel.context.Context;
import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot;
import com.alibaba.csp.sentinel.slotchain.DefaultProcessorSlotChain;
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mockito;
import java.lang.reflect.Field;

/**
* @author joyce
* @date 2026/5/14
*/
public class ProcessorSlotChainTest {

@Test
public void testBeforeFix_nextOfSingletonIsShared() throws Exception {
DefaultSlot singleton = new DefaultSlot();
DefaultProcessorSlotChain chain1 = createLegacyChain();
DefaultProcessorSlotChain chain2 = createLegacyChain();

DefaultSlot prototypeA1 = new DefaultSlot();
chain1.addLast(singleton);
chain1.addLast(prototypeA1);

DefaultSlot prototypeA2 = new DefaultSlot();
chain2.addLast(singleton);
chain2.addLast(prototypeA2);

Assert.assertEquals(chain1.getNext().getNext(), chain2.getNext().getNext());
}
@Test
public void testAfterFix_eachChainHasItsOwnWrapper() {
DefaultSlot singleton = new DefaultSlot();
DefaultProcessorSlotChain chain1 = new DefaultProcessorSlotChain();
DefaultProcessorSlotChain chain2 = new DefaultProcessorSlotChain();

chain1.addLast(singleton);
chain1.addLast(new DefaultSlot());

chain2.addLast(singleton);
chain2.addLast(new DefaultSlot());
Assert.assertNotEquals(chain1.getNext().getNext(), chain2.getNext().getNext());
}

private DefaultProcessorSlotChain createLegacyChain() throws Exception {
DefaultProcessorSlotChain chain = Mockito.spy(new DefaultProcessorSlotChain());
Field endField = DefaultProcessorSlotChain.class.getDeclaredField("end");
endField.setAccessible(true);

Mockito.doAnswer(invocation -> {
AbstractLinkedProcessorSlot<?> slot = invocation.getArgument(0);
AbstractLinkedProcessorSlot<?> end =
(AbstractLinkedProcessorSlot<?>) endField.get(chain);
end.setNext(slot);
endField.set(chain, slot);
return null;
}).when(chain).addLast(Mockito.any(AbstractLinkedProcessorSlot.class));

return chain;
}

private static class DefaultSlot extends AbstractLinkedProcessorSlot<Object> {

@Override
public void entry(Context context, ResourceWrapper resourceWrapper, Object t, int count,
boolean prioritized, Object... args) throws Throwable {
}

@Override
public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
}
}
}
Loading