Signed-off-by: Eric Zhao <sczyh16@gmail.com>master
@@ -0,0 +1,84 @@ | |||
/* | |||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
* See the License for the specific language governing permissions and | |||
* limitations under the License. | |||
*/ | |||
package com.alibaba.csp.sentinel; | |||
import com.alibaba.csp.sentinel.context.Context; | |||
import com.alibaba.csp.sentinel.slotchain.ProcessorSlot; | |||
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; | |||
/** | |||
* The entry for asynchronous resources. | |||
* | |||
* @author Eric Zhao | |||
* @since 0.2.0 | |||
*/ | |||
public class AsyncEntry extends CtEntry { | |||
private Context asyncContext; | |||
AsyncEntry(ResourceWrapper resourceWrapper, ProcessorSlot<Object> chain, Context context) { | |||
super(resourceWrapper, chain, context); | |||
} | |||
/** | |||
* Remove current entry from local context, but does not exit. | |||
*/ | |||
void cleanCurrentEntryInLocal() { | |||
Context originalContext = context; | |||
if (originalContext != null) { | |||
Entry curEntry = originalContext.getCurEntry(); | |||
if (curEntry == this) { | |||
Entry parent = this.parent; | |||
originalContext.setCurEntry(parent); | |||
if (parent != null) { | |||
((CtEntry)parent).child = null; | |||
} | |||
} else { | |||
throw new IllegalStateException("Bad async context state"); | |||
} | |||
} | |||
} | |||
public Context getAsyncContext() { | |||
return asyncContext; | |||
} | |||
/** | |||
* The async context should not be initialized until the node for current resource has been set to current entry. | |||
*/ | |||
void initAsyncContext() { | |||
if (asyncContext == null) { | |||
this.asyncContext = Context.newAsyncContext(context.getEntranceNode(), context.getName()) | |||
.setOrigin(context.getOrigin()) | |||
.setCurEntry(this); | |||
} else { | |||
throw new IllegalStateException("Duplicate initialize of async context"); | |||
} | |||
} | |||
@Override | |||
protected void clearEntryContext() { | |||
super.clearEntryContext(); | |||
this.asyncContext = null; | |||
} | |||
@Override | |||
protected Entry trueExit(int count, Object... args) throws ErrorEntryFreeException { | |||
exitForContext(asyncContext, count, args); | |||
return parent; | |||
} | |||
} |
@@ -0,0 +1,103 @@ | |||
/* | |||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
* See the License for the specific language governing permissions and | |||
* limitations under the License. | |||
*/ | |||
package com.alibaba.csp.sentinel; | |||
import com.alibaba.csp.sentinel.context.Context; | |||
import com.alibaba.csp.sentinel.context.ContextUtil; | |||
import com.alibaba.csp.sentinel.node.Node; | |||
import com.alibaba.csp.sentinel.slotchain.ProcessorSlot; | |||
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; | |||
/** | |||
* Linked entry within current context. | |||
* | |||
* @author jialiang.linjl | |||
* @author Eric Zhao | |||
*/ | |||
class CtEntry extends Entry { | |||
protected Entry parent = null; | |||
protected Entry child = null; | |||
protected ProcessorSlot<Object> chain; | |||
protected Context context; | |||
CtEntry(ResourceWrapper resourceWrapper, ProcessorSlot<Object> chain, Context context) { | |||
super(resourceWrapper); | |||
this.chain = chain; | |||
this.context = context; | |||
setUpEntryFor(context); | |||
} | |||
private void setUpEntryFor(Context context) { | |||
this.parent = context.getCurEntry(); | |||
if (parent != null) { | |||
((CtEntry)parent).child = this; | |||
} | |||
context.setCurEntry(this); | |||
} | |||
@Override | |||
public void exit(int count, Object... args) throws ErrorEntryFreeException { | |||
trueExit(count, args); | |||
} | |||
protected void exitForContext(Context context, int count, Object... args) throws ErrorEntryFreeException { | |||
if (context != null) { | |||
if (context.getCurEntry() != this) { | |||
// Clean previous call stack. | |||
CtEntry e = (CtEntry)context.getCurEntry(); | |||
while (e != null) { | |||
e.exit(count, args); | |||
e = (CtEntry)e.parent; | |||
} | |||
throw new ErrorEntryFreeException("The order of entry free is can't be paired with the order of entry"); | |||
} else { | |||
if (chain != null) { | |||
chain.exit(context, resourceWrapper, count, args); | |||
} | |||
// Restore the call stack. | |||
context.setCurEntry(parent); | |||
if (parent != null) { | |||
((CtEntry)parent).child = null; | |||
} | |||
if (parent == null) { | |||
// Auto-created entry indicates immediate exit. | |||
ContextUtil.exit(); | |||
} | |||
// Clean the reference of context in current entry to avoid duplicate exit. | |||
clearEntryContext(); | |||
} | |||
} | |||
} | |||
protected void clearEntryContext() { | |||
this.context = null; | |||
} | |||
@Override | |||
protected Entry trueExit(int count, Object... args) throws ErrorEntryFreeException { | |||
exitForContext(context, count, args); | |||
return parent; | |||
} | |||
@Override | |||
public Node getLastNode() { | |||
return parent == null ? null : parent.getCurNode(); | |||
} | |||
} |
@@ -23,7 +23,6 @@ import com.alibaba.csp.sentinel.log.RecordLog; | |||
import com.alibaba.csp.sentinel.context.Context; | |||
import com.alibaba.csp.sentinel.context.ContextUtil; | |||
import com.alibaba.csp.sentinel.context.NullContext; | |||
import com.alibaba.csp.sentinel.node.Node; | |||
import com.alibaba.csp.sentinel.slotchain.MethodResourceWrapper; | |||
import com.alibaba.csp.sentinel.slotchain.ProcessorSlot; | |||
import com.alibaba.csp.sentinel.slotchain.ProcessorSlotChain; | |||
@@ -53,6 +52,46 @@ public class CtSph implements Sph { | |||
private static final Object LOCK = new Object(); | |||
private AsyncEntry asyncEntryInternal(ResourceWrapper resourceWrapper, int count, Object... args) throws BlockException { | |||
Context context = ContextUtil.getContext(); | |||
if (context instanceof NullContext) { | |||
// Init the entry only. No rule checking will occur. | |||
return new AsyncEntry(resourceWrapper, null, context); | |||
} | |||
if (context == null) { | |||
context = MyContextUtil.myEnter(Constants.CONTEXT_DEFAULT_NAME, "", resourceWrapper.getType()); | |||
} | |||
// Global switch is turned off, so no rule checking will be done. | |||
if (!Constants.ON) { | |||
return new AsyncEntry(resourceWrapper, null, context); | |||
} | |||
ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper); | |||
// Means processor cache size exceeds {@link Constants.MAX_SLOT_CHAIN_SIZE}, so no rule checking will be done. | |||
if (chain == null) { | |||
return new AsyncEntry(resourceWrapper, null, context); | |||
} | |||
AsyncEntry asyncEntry = new AsyncEntry(resourceWrapper, chain, context); | |||
try { | |||
chain.entry(context, resourceWrapper, null, count, args); | |||
// Initiate the async context. | |||
asyncEntry.initAsyncContext(); | |||
} catch (BlockException e1) { | |||
asyncEntry.exit(count, args); | |||
throw e1; | |||
} catch (Throwable e1) { | |||
RecordLog.info("Sentinel unexpected exception", e1); | |||
} finally { | |||
// The asynchronous call may take time in background, and current context should not be hanged on it. | |||
// So we need to remove current async entry from current context. | |||
asyncEntry.cleanCurrentEntryInLocal(); | |||
} | |||
return asyncEntry; | |||
} | |||
/** | |||
* Do all {@link Rule}s checking about the resource. | |||
* | |||
@@ -145,68 +184,6 @@ public class CtSph implements Sph { | |||
return chain; | |||
} | |||
private static class CtEntry extends Entry { | |||
protected Entry parent = null; | |||
protected Entry child = null; | |||
private ProcessorSlot<Object> chain; | |||
private Context context; | |||
CtEntry(ResourceWrapper resourceWrapper, ProcessorSlot<Object> chain, Context context) { | |||
super(resourceWrapper); | |||
this.chain = chain; | |||
this.context = context; | |||
parent = context.getCurEntry(); | |||
if (parent != null) { | |||
((CtEntry)parent).child = this; | |||
} | |||
context.setCurEntry(this); | |||
} | |||
@Override | |||
public void exit(int count, Object... args) throws ErrorEntryFreeException { | |||
trueExit(count, args); | |||
} | |||
@Override | |||
protected Entry trueExit(int count, Object... args) throws ErrorEntryFreeException { | |||
if (context != null) { | |||
if (context.getCurEntry() != this) { | |||
// Clean previous call stack. | |||
CtEntry e = (CtEntry)context.getCurEntry(); | |||
while (e != null) { | |||
e.exit(count, args); | |||
e = (CtEntry)e.parent; | |||
} | |||
throw new ErrorEntryFreeException( | |||
"The order of entry free is can't be paired with the order of entry"); | |||
} else { | |||
if (chain != null) { | |||
chain.exit(context, resourceWrapper, count, args); | |||
} | |||
// Modify the call stack. | |||
context.setCurEntry(parent); | |||
if (parent != null) { | |||
((CtEntry)parent).child = null; | |||
} | |||
if (parent == null) { | |||
// Auto-created entry indicates immediate exit. | |||
ContextUtil.exit(); | |||
} | |||
// Clean the reference of context in current entry to avoid duplicate exit. | |||
context = null; | |||
} | |||
} | |||
return parent; | |||
} | |||
@Override | |||
public Node getLastNode() { | |||
return parent == null ? null : parent.getCurNode(); | |||
} | |||
} | |||
/** | |||
* This class is used for skip context name checking. | |||
*/ | |||
@@ -275,4 +252,10 @@ public class CtSph implements Sph { | |||
StringResourceWrapper resource = new StringResourceWrapper(name, type); | |||
return entry(resource, count, args); | |||
} | |||
@Override | |||
public AsyncEntry asyncEntry(String name, EntryType type, int count, Object... args) throws BlockException { | |||
StringResourceWrapper resource = new StringResourceWrapper(name, type); | |||
return asyncEntryInternal(resource, count, args); | |||
} | |||
} |
@@ -27,6 +27,7 @@ import com.alibaba.csp.sentinel.slots.block.BlockException; | |||
* @author qinan.qn | |||
* @author jialiang.linjl | |||
* @author leyou | |||
* @author Eric Zhao | |||
*/ | |||
public interface Sph { | |||
@@ -135,11 +136,23 @@ public interface Sph { | |||
* @param type the resource is an inbound or an outbound method. This is used | |||
* to mark whether it can be blocked when the system is unstable | |||
* @param count the count that the resource requires | |||
* @param args the parameters of the method. It can also be counted by setting | |||
* hot parameter rule | |||
* @return entry get. | |||
* @param args the parameters of the method. It can also be counted by setting hot parameter rule | |||
* @return entry get | |||
* @throws BlockException if the block criteria is met | |||
*/ | |||
Entry entry(String name, EntryType type, int count, Object... args) throws BlockException; | |||
/** | |||
* Create a protected asynchronous resource. | |||
* | |||
* @param name the unique name for the protected resource | |||
* @param type the resource is an inbound or an outbound method. This is used | |||
* to mark whether it can be blocked when the system is unstable | |||
* @param count the count that the resource requires | |||
* @param args the parameters of the method. It can also be counted by setting hot parameter rule | |||
* @return created asynchronous entry | |||
* @throws BlockException if the block criteria is met | |||
* @since 0.2.0 | |||
*/ | |||
AsyncEntry asyncEntry(String name, EntryType type, int count, Object... args) throws BlockException; | |||
} |
@@ -69,6 +69,7 @@ import com.alibaba.csp.sentinel.slots.system.SystemRuleManager; | |||
* </p> | |||
* | |||
* @author jialiang.linjl | |||
* @author Eric Zhao | |||
* @see SphO | |||
*/ | |||
public class SphU { | |||
@@ -200,4 +201,45 @@ public class SphU { | |||
public static Entry entry(String name, EntryType type, int count, Object... args) throws BlockException { | |||
return Env.sph.entry(name, type, count, args); | |||
} | |||
/** | |||
* Checking all rules about the asynchronous resource. | |||
* | |||
* @param name the unique name of the protected resource | |||
* @throws BlockException if the block criteria is met, eg. when any rule's threshold is exceeded | |||
* @since 0.2.0 | |||
*/ | |||
public static AsyncEntry asyncEntry(String name) throws BlockException { | |||
return Env.sph.asyncEntry(name, EntryType.OUT, 1, OBJECTS0); | |||
} | |||
/** | |||
* Checking all {@link Rule}s about the asynchronous resource. | |||
* | |||
* @param name the unique name for the protected resource | |||
* @param type the resource is an inbound or an outbound method. This is used | |||
* to mark whether it can be blocked when the system is unstable, | |||
* only inbound traffic could be blocked by {@link SystemRule} | |||
* @throws BlockException if the block criteria is met, eg. when any rule's threshold is exceeded | |||
* @since 0.2.0 | |||
*/ | |||
public static AsyncEntry asyncEntry(String name, EntryType type) throws BlockException { | |||
return Env.sph.asyncEntry(name, type, 1, OBJECTS0); | |||
} | |||
/** | |||
* Checking all {@link Rule}s about the asynchronous resource. | |||
* | |||
* @param name the unique name for the protected resource | |||
* @param type the resource is an inbound or an outbound method. This is used | |||
* to mark whether it can be blocked when the system is unstable, | |||
* only inbound traffic could be blocked by {@link SystemRule} | |||
* @param count tokens required | |||
* @param args extra parameters | |||
* @throws BlockException if the block criteria is met, eg. when any rule's threshold is exceeded | |||
* @since 0.2.0 | |||
*/ | |||
public static AsyncEntry asyncEntry(String name, EntryType type, int count, Object... args) throws BlockException { | |||
return Env.sph.asyncEntry(name, type, count, args); | |||
} | |||
} |
@@ -32,8 +32,9 @@ import com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot; | |||
* <li>the current {@link Entry}: the current invocation point.</li> | |||
* <li>the current {@link Node}: the statistics related to the | |||
* {@link Entry}.</li> | |||
* <li>the origin:The origin is useful when we want to control different | |||
* invoker/consumer separately. Usually the origin could be the Service Consumer's app name. </li> | |||
* <li>the origin: The origin is useful when we want to control different | |||
* invoker/consumer separately. Usually the origin could be the Service Consumer's app name | |||
* or origin IP. </li> | |||
* </ul> | |||
* <p> | |||
* Each {@link SphU}#entry() or {@link SphO}#entry() should be in a {@link Context}, | |||
@@ -58,7 +59,7 @@ public class Context { | |||
/** | |||
* Context name. | |||
*/ | |||
private String name; | |||
private final String name; | |||
/** | |||
* The entrance node of current invocation tree. | |||
@@ -71,14 +72,36 @@ public class Context { | |||
private Entry curEntry; | |||
/** | |||
* the origin of this context, usually the origin is the Service Consumer's app name. | |||
* The origin of this context (usually indicate different invokers, e.g. service consumer name or origin IP). | |||
*/ | |||
private String origin = ""; | |||
private final boolean async; | |||
/** | |||
* Create a new async context. | |||
* | |||
* @param entranceNode entrance node of the context | |||
* @param name context name | |||
* @return the new created context | |||
* @since 0.2.0 | |||
*/ | |||
public static Context newAsyncContext(DefaultNode entranceNode, String name) { | |||
return new Context(name, entranceNode, true); | |||
} | |||
public Context(DefaultNode entranceNode, String name) { | |||
super(); | |||
this(name, entranceNode, false); | |||
} | |||
public Context(String name, DefaultNode entranceNode, boolean async) { | |||
this.name = name; | |||
this.entranceNode = entranceNode; | |||
this.async = async; | |||
} | |||
public boolean isAsync() { | |||
return async; | |||
} | |||
public String getName() { | |||
@@ -89,24 +112,27 @@ public class Context { | |||
return curEntry.getCurNode(); | |||
} | |||
public void setCurNode(Node node) { | |||
public Context setCurNode(Node node) { | |||
this.curEntry.setCurNode(node); | |||
return this; | |||
} | |||
public Entry getCurEntry() { | |||
return curEntry; | |||
} | |||
public void setCurEntry(Entry curEntry) { | |||
public Context setCurEntry(Entry curEntry) { | |||
this.curEntry = curEntry; | |||
return this; | |||
} | |||
public String getOrigin() { | |||
return origin; | |||
} | |||
public void setOrigin(String origin) { | |||
public Context setOrigin(String origin) { | |||
this.origin = origin; | |||
return this; | |||
} | |||
public double getOriginTotalQps() { | |||
@@ -175,4 +175,45 @@ public class ContextUtil { | |||
public static Context getContext() { | |||
return contextHolder.get(); | |||
} | |||
/** | |||
* <p> | |||
* Replace current context with the provided context. | |||
* This is mainly designed for context switching (e.g. in asynchronous invocation). | |||
* </p> | |||
* <p> | |||
* Note: When switching context manually, remember to restore the original context. | |||
* For common scenarios, you can use {@link #runOnContext(Context, Runnable)}. | |||
* </p> | |||
* | |||
* @param newContext new context to set | |||
* @return old context | |||
* @since 0.2.0 | |||
*/ | |||
private static Context replaceContext(Context newContext) { | |||
Context backupContext = contextHolder.get(); | |||
if (newContext == null) { | |||
contextHolder.remove(); | |||
} else { | |||
contextHolder.set(newContext); | |||
} | |||
return backupContext; | |||
} | |||
/** | |||
* Execute the code within provided context. | |||
* This is mainly designed for context switching (e.g. in asynchronous invocation). | |||
* | |||
* @param context the context | |||
* @param f lambda to run within the context | |||
* @since 0.2.0 | |||
*/ | |||
public static void runOnContext(Context context, Runnable f) { | |||
Context curContext = replaceContext(context); | |||
try { | |||
f.run(); | |||
} finally { | |||
replaceContext(curContext); | |||
} | |||
} | |||
} |