Skip to content

Commit 5edac71

Browse files
authored
Add support for asynchronous invocation entry (#146)
- Add several `asyncEntry` method in `Sph` interface and `SphU` class. - Add a new `AsyncEntry` class for asynchronous entry representation. Some refactor for `CtEntry`. - Refactored `CtSph` and implement `asyncEntryInternal` for asynchronous entry. - Add `runOnContext` and `replaceContext` method in `ContextUtil` for context switching.
2 parents 5490549 + 2a3e335 commit 5edac71

File tree

13 files changed

+982
-109
lines changed

13 files changed

+982
-109
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright 1999-2018 Alibaba Group Holding Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.alibaba.csp.sentinel;
17+
18+
import com.alibaba.csp.sentinel.context.Context;
19+
import com.alibaba.csp.sentinel.slotchain.ProcessorSlot;
20+
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
21+
22+
/**
23+
* The entry for asynchronous resources.
24+
*
25+
* @author Eric Zhao
26+
* @since 0.2.0
27+
*/
28+
public class AsyncEntry extends CtEntry {
29+
30+
private Context asyncContext;
31+
32+
AsyncEntry(ResourceWrapper resourceWrapper, ProcessorSlot<Object> chain, Context context) {
33+
super(resourceWrapper, chain, context);
34+
}
35+
36+
/**
37+
* Remove current entry from local context, but does not exit.
38+
*/
39+
void cleanCurrentEntryInLocal() {
40+
Context originalContext = context;
41+
if (originalContext != null) {
42+
Entry curEntry = originalContext.getCurEntry();
43+
if (curEntry == this) {
44+
Entry parent = this.parent;
45+
originalContext.setCurEntry(parent);
46+
if (parent != null) {
47+
((CtEntry)parent).child = null;
48+
}
49+
} else {
50+
throw new IllegalStateException("Bad async context state");
51+
}
52+
}
53+
}
54+
55+
public Context getAsyncContext() {
56+
return asyncContext;
57+
}
58+
59+
/**
60+
* The async context should not be initialized until the node for current resource has been set to current entry.
61+
*/
62+
void initAsyncContext() {
63+
if (asyncContext == null) {
64+
this.asyncContext = Context.newAsyncContext(context.getEntranceNode(), context.getName())
65+
.setOrigin(context.getOrigin())
66+
.setCurEntry(this);
67+
} else {
68+
throw new IllegalStateException("Duplicate initialize of async context");
69+
}
70+
}
71+
72+
@Override
73+
protected void clearEntryContext() {
74+
super.clearEntryContext();
75+
this.asyncContext = null;
76+
}
77+
78+
@Override
79+
protected Entry trueExit(int count, Object... args) throws ErrorEntryFreeException {
80+
exitForContext(asyncContext, count, args);
81+
82+
return parent;
83+
}
84+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
* Copyright 1999-2018 Alibaba Group Holding Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.alibaba.csp.sentinel;
17+
18+
import com.alibaba.csp.sentinel.context.Context;
19+
import com.alibaba.csp.sentinel.context.ContextUtil;
20+
import com.alibaba.csp.sentinel.node.Node;
21+
import com.alibaba.csp.sentinel.slotchain.ProcessorSlot;
22+
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
23+
24+
/**
25+
* Linked entry within current context.
26+
*
27+
* @author jialiang.linjl
28+
* @author Eric Zhao
29+
*/
30+
class CtEntry extends Entry {
31+
32+
protected Entry parent = null;
33+
protected Entry child = null;
34+
35+
protected ProcessorSlot<Object> chain;
36+
protected Context context;
37+
38+
CtEntry(ResourceWrapper resourceWrapper, ProcessorSlot<Object> chain, Context context) {
39+
super(resourceWrapper);
40+
this.chain = chain;
41+
this.context = context;
42+
43+
setUpEntryFor(context);
44+
}
45+
46+
private void setUpEntryFor(Context context) {
47+
this.parent = context.getCurEntry();
48+
if (parent != null) {
49+
((CtEntry)parent).child = this;
50+
}
51+
context.setCurEntry(this);
52+
}
53+
54+
@Override
55+
public void exit(int count, Object... args) throws ErrorEntryFreeException {
56+
trueExit(count, args);
57+
}
58+
59+
protected void exitForContext(Context context, int count, Object... args) throws ErrorEntryFreeException {
60+
if (context != null) {
61+
if (context.getCurEntry() != this) {
62+
// Clean previous call stack.
63+
CtEntry e = (CtEntry)context.getCurEntry();
64+
while (e != null) {
65+
e.exit(count, args);
66+
e = (CtEntry)e.parent;
67+
}
68+
throw new ErrorEntryFreeException("The order of entry free is can't be paired with the order of entry");
69+
} else {
70+
if (chain != null) {
71+
chain.exit(context, resourceWrapper, count, args);
72+
}
73+
// Restore the call stack.
74+
context.setCurEntry(parent);
75+
if (parent != null) {
76+
((CtEntry)parent).child = null;
77+
}
78+
if (parent == null) {
79+
// Auto-created entry indicates immediate exit.
80+
ContextUtil.exit();
81+
}
82+
// Clean the reference of context in current entry to avoid duplicate exit.
83+
clearEntryContext();
84+
}
85+
}
86+
}
87+
88+
protected void clearEntryContext() {
89+
this.context = null;
90+
}
91+
92+
@Override
93+
protected Entry trueExit(int count, Object... args) throws ErrorEntryFreeException {
94+
exitForContext(context, count, args);
95+
96+
return parent;
97+
}
98+
99+
@Override
100+
public Node getLastNode() {
101+
return parent == null ? null : parent.getCurNode();
102+
}
103+
}

sentinel-core/src/main/java/com/alibaba/csp/sentinel/CtSph.java

+46-63
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import com.alibaba.csp.sentinel.context.Context;
2424
import com.alibaba.csp.sentinel.context.ContextUtil;
2525
import com.alibaba.csp.sentinel.context.NullContext;
26-
import com.alibaba.csp.sentinel.node.Node;
2726
import com.alibaba.csp.sentinel.slotchain.MethodResourceWrapper;
2827
import com.alibaba.csp.sentinel.slotchain.ProcessorSlot;
2928
import com.alibaba.csp.sentinel.slotchain.ProcessorSlotChain;
@@ -54,6 +53,46 @@ public class CtSph implements Sph {
5453

5554
private static final Object LOCK = new Object();
5655

56+
private AsyncEntry asyncEntryInternal(ResourceWrapper resourceWrapper, int count, Object... args) throws BlockException {
57+
Context context = ContextUtil.getContext();
58+
if (context instanceof NullContext) {
59+
// Init the entry only. No rule checking will occur.
60+
return new AsyncEntry(resourceWrapper, null, context);
61+
}
62+
if (context == null) {
63+
context = MyContextUtil.myEnter(Constants.CONTEXT_DEFAULT_NAME, "", resourceWrapper.getType());
64+
}
65+
66+
// Global switch is turned off, so no rule checking will be done.
67+
if (!Constants.ON) {
68+
return new AsyncEntry(resourceWrapper, null, context);
69+
}
70+
71+
ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);
72+
73+
// Means processor cache size exceeds {@link Constants.MAX_SLOT_CHAIN_SIZE}, so no rule checking will be done.
74+
if (chain == null) {
75+
return new AsyncEntry(resourceWrapper, null, context);
76+
}
77+
78+
AsyncEntry asyncEntry = new AsyncEntry(resourceWrapper, chain, context);
79+
try {
80+
chain.entry(context, resourceWrapper, null, count, args);
81+
// Initiate the async context.
82+
asyncEntry.initAsyncContext();
83+
} catch (BlockException e1) {
84+
asyncEntry.exit(count, args);
85+
throw e1;
86+
} catch (Throwable e1) {
87+
RecordLog.info("Sentinel unexpected exception", e1);
88+
} finally {
89+
// The asynchronous call may take time in background, and current context should not be hanged on it.
90+
// So we need to remove current async entry from current context.
91+
asyncEntry.cleanCurrentEntryInLocal();
92+
}
93+
return asyncEntry;
94+
}
95+
5796
/**
5897
* Do all {@link Rule}s checking about the resource.
5998
*
@@ -146,68 +185,6 @@ private ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper)
146185
return chain;
147186
}
148187

149-
private static class CtEntry extends Entry {
150-
151-
protected Entry parent = null;
152-
protected Entry child = null;
153-
private ProcessorSlot<Object> chain;
154-
private Context context;
155-
156-
CtEntry(ResourceWrapper resourceWrapper, ProcessorSlot<Object> chain, Context context) {
157-
super(resourceWrapper);
158-
this.chain = chain;
159-
this.context = context;
160-
parent = context.getCurEntry();
161-
if (parent != null) {
162-
((CtEntry)parent).child = this;
163-
}
164-
context.setCurEntry(this);
165-
}
166-
167-
@Override
168-
public void exit(int count, Object... args) throws ErrorEntryFreeException {
169-
trueExit(count, args);
170-
}
171-
172-
@Override
173-
protected Entry trueExit(int count, Object... args) throws ErrorEntryFreeException {
174-
if (context != null) {
175-
if (context.getCurEntry() != this) {
176-
// Clean previous call stack.
177-
CtEntry e = (CtEntry)context.getCurEntry();
178-
while (e != null) {
179-
e.exit(count, args);
180-
e = (CtEntry)e.parent;
181-
}
182-
throw new ErrorEntryFreeException(
183-
"The order of entry free is can't be paired with the order of entry");
184-
} else {
185-
if (chain != null) {
186-
chain.exit(context, resourceWrapper, count, args);
187-
}
188-
// Modify the call stack.
189-
context.setCurEntry(parent);
190-
if (parent != null) {
191-
((CtEntry)parent).child = null;
192-
}
193-
if (parent == null) {
194-
// Auto-created entry indicates immediate exit.
195-
ContextUtil.exit();
196-
}
197-
// Clean the reference of context in current entry to avoid duplicate exit.
198-
context = null;
199-
}
200-
}
201-
return parent;
202-
203-
}
204-
205-
@Override
206-
public Node getLastNode() {
207-
return parent == null ? null : parent.getCurNode();
208-
}
209-
}
210-
211188
/**
212189
* This class is used for skip context name checking.
213190
*/
@@ -276,4 +253,10 @@ public Entry entry(String name, EntryType type, int count, Object... args) throw
276253
StringResourceWrapper resource = new StringResourceWrapper(name, type);
277254
return entry(resource, count, args);
278255
}
256+
257+
@Override
258+
public AsyncEntry asyncEntry(String name, EntryType type, int count, Object... args) throws BlockException {
259+
StringResourceWrapper resource = new StringResourceWrapper(name, type);
260+
return asyncEntryInternal(resource, count, args);
261+
}
279262
}

sentinel-core/src/main/java/com/alibaba/csp/sentinel/Sph.java

+16-3
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
* @author qinan.qn
2828
* @author jialiang.linjl
2929
* @author leyou
30+
* @author Eric Zhao
3031
*/
3132
public interface Sph {
3233

@@ -135,11 +136,23 @@ public interface Sph {
135136
* @param type the resource is an inbound or an outbound method. This is used
136137
* to mark whether it can be blocked when the system is unstable
137138
* @param count the count that the resource requires
138-
* @param args the parameters of the method. It can also be counted by setting
139-
* hot parameter rule
140-
* @return entry get.
139+
* @param args the parameters of the method. It can also be counted by setting hot parameter rule
140+
* @return entry get
141141
* @throws BlockException if the block criteria is met
142142
*/
143143
Entry entry(String name, EntryType type, int count, Object... args) throws BlockException;
144144

145+
/**
146+
* Create a protected asynchronous resource.
147+
*
148+
* @param name the unique name for the protected resource
149+
* @param type the resource is an inbound or an outbound method. This is used
150+
* to mark whether it can be blocked when the system is unstable
151+
* @param count the count that the resource requires
152+
* @param args the parameters of the method. It can also be counted by setting hot parameter rule
153+
* @return created asynchronous entry
154+
* @throws BlockException if the block criteria is met
155+
* @since 0.2.0
156+
*/
157+
AsyncEntry asyncEntry(String name, EntryType type, int count, Object... args) throws BlockException;
145158
}

0 commit comments

Comments
 (0)