Repository: alibaba/sentinel-golang
Pull request #149 (2020-05-14 14:16:56)

Fix data race of Token result Pool
### Describe what this PR does / why we need it
```
==================
WARNING: DATA RACE
Read at 0x00c0000a0a00 by goroutine 11:
  github.com/alibaba/sentinel-golang/core/base.(*TokenResult).IsBlocked()
      /Users/sczyh30/temp/to-review/sentinel-go-ximu/core/base/result.go:58 +0x351
  github.com/alibaba/sentinel-golang/core/stat.(*StatisticSlot).OnCompleted()
      /Users/sczyh30/temp/to-review/sentinel-go-ximu/core/stat/stat_slot.go:32 +0x40
  github.com/alibaba/sentinel-golang/core/base.(*SlotChain).exit()
      /Users/sczyh30/temp/to-review/sentinel-go-ximu/core/base/slot_chain.go:181 +0x9f
  github.com/alibaba/sentinel-golang/core/base.(*SentinelEntry).Exit.func1()
      /Users/sczyh30/temp/to-review/sentinel-go-ximu/core/base/entry.go:64 +0xa3
  sync.(*Once).doSlow()
      /usr/local/go/src/sync/once.go:66 +0x103
  sync.(*Once).Do()
      /usr/local/go/src/sync/once.go:57 +0x68
  github.com/alibaba/sentinel-golang/core/base.(*SentinelEntry).Exit()
      /Users/sczyh30/temp/to-review/sentinel-go-ximu/core/base/entry.go:62 +0x144
  main.main.func2()
      /Users/sczyh30/temp/to-review/sentinel-go-ximu/api/test/cb.go:67 +0x136

Previous write at 0x00c0000a0a00 by goroutine 10:
  github.com/alibaba/sentinel-golang/core/base.RefurbishTokenResult()
      /Users/sczyh30/temp/to-review/sentinel-go-ximu/core/base/result.go:118 +0x40a
  github.com/alibaba/sentinel-golang/core/base.(*SlotChain).Entry()
      /Users/sczyh30/temp/to-review/sentinel-go-ximu/core/base/slot_chain.go:145 +0x401
  github.com/alibaba/sentinel-golang/api.entry()
      /Users/sczyh30/temp/to-review/sentinel-go-ximu/api/api.go:114 +0x4c1
  github.com/alibaba/sentinel-golang/api.Entry()
      /Users/sczyh30/temp/to-review/sentinel-go-ximu/api/api.go:86 +0x19a
  main.main.func1()
      /Users/sczyh30/temp/to-review/sentinel-go-ximu/api/test/cb.go:47 +0x58

Goroutine 11 (running) created at:
  main.main()
      /Users/sczyh30/temp/to-review/sentinel-go-ximu/api/test/cb.go:58 +0x439

Goroutine 10 (running) created at:
  main.main()
      /Users/sczyh30/temp/to-review/sentinel-go-ximu/api/test/cb.go:45 +0x421
==================
```

Root cause is here:
Refer to: [https://github.com/alibaba/sentinel-golang/commit/975debd7075d071473fcbcd17c3dbc3bd201a2da#diff-604f4192deea5adf9c159e5f88f64630R93
](https://github.com/alibaba/sentinel-golang/commit/975debd7075d071473fcbcd17c3dbc3bd201a2da#diff-604f4192deea5adf9c159e5f88f64630R93
)
```

defer func() {
	if r != nil {
		base.RefurbishTokenResult(r)
	}
}()
```
r is the same Pointer with context.Output.LastResult. Sentinel refurbish the r. But When user call Exit() by manually, will call context.Output.LastResult(the refurbished TokenResult might be got by other goroutine), it causes DATA RACE.


### Does this pull request fix one issue?


### Describe how you did it


### Describe how to verify it


### Special notes for reviews


Patch:
diff --git a/api/api.go b/api/api.go
index 19b6edc..ecaade3 100644
--- a/api/api.go
+++ b/api/api.go
@@ -87,13 +87,6 @@ func Entry(resource string, opts ...EntryOption) (*base.SentinelEntry, *base.Blo
 }
 
 func entry(resource string, options *EntryOptions) (*base.SentinelEntry, *base.BlockError) {
-	var r *base.TokenResult
-	defer func() {
-		if r != nil {
-			base.RefurbishTokenResult(r)
-		}
-	}()
-
 	rw := base.NewResourceWrapper(resource, options.resourceType, options.entryType)
 	sc := options.slotChain
 
@@ -109,16 +102,20 @@ func entry(resource string, options *EntryOptions) (*base.SentinelEntry, *base.B
 		Args:         options.args,
 		Attachments:  options.attachments,
 	}
+	ctx.Data = make(map[interface{}]interface{})
 
 	e := base.NewSentinelEntry(ctx, rw, sc)
-	r = sc.Entry(ctx)
+	r := sc.Entry(ctx)
 	if r == nil {
 		// This indicates internal error in some slots, so just pass
 		return e, nil
 	}
 	if r.Status() == base.ResultStatusBlocked {
+		// r will be put to Pool in calling Exit()
+		// must finish the lifecycle of r.
+		blockErr := base.NewBlockErrorFromDeepCopy(r.BlockError())
 		e.Exit()
-		return nil, r.BlockError()
+		return nil, blockErr
 	}
 
 	return e, nil
diff --git a/core/base/block_error.go b/core/base/block_error.go
index 51d7f37..b61dc13 100644
--- a/core/base/block_error.go
+++ b/core/base/block_error.go
@@ -25,6 +25,15 @@ func (e *BlockError) TriggeredValue() interface{} {
 	return e.snapshotValue
 }
 
+func NewBlockErrorFromDeepCopy(from *BlockError) *BlockError {
+	return &BlockError{
+		blockType:     from.blockType,
+		blockMsg:      from.blockMsg,
+		rule:          from.rule,
+		snapshotValue: from.snapshotValue,
+	}
+}
+
 func NewBlockError(blockType BlockType, blockMsg string) *BlockError {
 	return &BlockError{blockType: blockType, blockMsg: blockMsg}
 }
diff --git a/core/base/context.go b/core/base/context.go
index 73588cd..8fafb79 100644
--- a/core/base/context.go
+++ b/core/base/context.go
@@ -7,8 +7,11 @@ type EntryContext struct {
 	Resource *ResourceWrapper
 	StatNode StatNode
 
-	Input  *SentinelInput
-	Output *SentinelOutput
+	Input *SentinelInput
+	// the result of rule slots check
+	RuleCheckResult *TokenResult
+	// reserve for storing some intermediate data from the Entry execution process
+	Data map[interface{}]interface{}
 }
 
 func (ctx *EntryContext) StartTime() uint64 {
@@ -16,42 +19,32 @@ func (ctx *EntryContext) StartTime() uint64 {
 }
 
 func (ctx *EntryContext) IsBlocked() bool {
-	if ctx.Output.LastResult == nil {
+	if ctx.RuleCheckResult == nil {
 		return false
 	}
-	return ctx.Output.LastResult.IsBlocked()
+	return ctx.RuleCheckResult.IsBlocked()
 }
 
 func NewEmptyEntryContext() *EntryContext {
-	ctx := &EntryContext{
-		Input:  nil,
-		Output: newEmptyOutput(),
-	}
-	return ctx
+	return &EntryContext{}
 }
 
+// The input data of sentinel
 type SentinelInput struct {
 	AcquireCount uint32
 	Flag         int32
 	Args         []interface{}
-
 	// store some values in this context when calling context in slot.
 	Attachments map[interface{}]interface{}
 }
 
 func newEmptyInput() *SentinelInput {
-	return &SentinelInput{}
-}
-
-type SentinelOutput struct {
-	LastResult *TokenResult
-
-	// store output data.
-	Attachments map[interface{}]interface{}
-}
-
-func newEmptyOutput() *SentinelOutput {
-	return &SentinelOutput{}
+	return &SentinelInput{
+		AcquireCount: 1,
+		Flag:         0,
+		Args:         make([]interface{}, 0, 0),
+		Attachments:  make(map[interface{}]interface{}),
+	}
 }
 
 // Reset init EntryContext,
@@ -61,5 +54,10 @@ func (ctx *EntryContext) Reset() {
 	ctx.Resource = nil
 	ctx.StatNode = nil
 	ctx.Input = nil
-	ctx.Output = newEmptyOutput()
+	if ctx.RuleCheckResult == nil {
+		ctx.RuleCheckResult = NewTokenResultPass()
+	} else {
+		ctx.RuleCheckResult.ResetToPass()
+	}
+	ctx.Data = nil
 }
diff --git a/core/base/result.go b/core/base/result.go
index 9925a8c..85ad0e4 100644
--- a/core/base/result.go
+++ b/core/base/result.go
@@ -2,10 +2,9 @@ package base
 
 import (
 	"fmt"
-	"sync"
 )
 
-type BlockType int32
+type BlockType uint8
 
 const (
 	BlockTypeUnknown BlockType = iota
@@ -14,12 +13,6 @@ const (
 	BlockTypeSystemFlow
 )
 
-var resultPool = sync.Pool{
-	New: func() interface{} {
-		return NewTokenResultEmpty()
-	},
-}
-
 func (t BlockType) String() string {
 	switch t {
 	case BlockTypeUnknown:
@@ -35,7 +28,7 @@ func (t BlockType) String() string {
 	}
 }
 
-type TokenResultStatus int32
+type TokenResultStatus uint8
 
 const (
 	ResultStatusPass TokenResultStatus = iota
@@ -43,6 +36,19 @@ const (
 	ResultStatusShouldWait
 )
 
+func (s TokenResultStatus) String() string {
+	switch s {
+	case ResultStatusPass:
+		return "ResultStatusPass"
+	case ResultStatusBlocked:
+		return "ResultStatusBlocked"
+	case ResultStatusShouldWait:
+		return "ResultStatusShouldWait"
+	default:
+		return "Undefined"
+	}
+}
+
 type TokenResult struct {
 	status TokenResultStatus
 
@@ -50,6 +56,62 @@ type TokenResult struct {
 	waitMs   uint64
 }
 
+func (r *TokenResult) DeepCopyFrom(newResult *TokenResult) {
+	r.status = newResult.status
+	r.waitMs = newResult.waitMs
+	if r.blockErr == nil {
+		r.blockErr = &BlockError{
+			blockType:     newResult.blockErr.blockType,
+			blockMsg:      newResult.blockErr.blockMsg,
+			rule:          newResult.blockErr.rule,
+			snapshotValue: newResult.blockErr.snapshotValue,
+		}
+	} else {
+		r.blockErr.blockType = newResult.blockErr.blockType
+		r.blockErr.blockMsg = newResult.blockErr.blockMsg
+		r.blockErr.rule = newResult.blockErr.rule
+		r.blockErr.snapshotValue = newResult.blockErr.snapshotValue
+	}
+}
+
+func (r *TokenResult) ResetToPass() {
+	r.status = ResultStatusPass
+	r.blockErr = nil
+	r.waitMs = 0
+}
+
+func (r *TokenResult) ResetToBlockedFrom(blockType BlockType, blockMsg string) {
+	r.status = ResultStatusBlocked
+	if r.blockErr == nil {
+		r.blockErr = &BlockError{
+			blockType: blockType,
+			blockMsg:  blockMsg,
+		}
+	} else {
+		r.blockErr.blockType = blockType
+		r.blockErr.blockMsg = blockMsg
+	}
+	r.waitMs = 0
+}
+
+func (r *TokenResult) ResetToBlockedWithCauseFrom(blockType BlockType, blockMsg string, rule SentinelRule, snapshot interface{}) {
+	r.status = ResultStatusBlocked
+	if r.blockErr == nil {
+		r.blockErr = &BlockError{
+			blockType:     blockType,
+			blockMsg:      blockMsg,
+			rule:          rule,
+			snapshotValue: snapshot,
+		}
+	} else {
+		r.blockErr.blockType = blockType
+		r.blockErr.blockMsg = blockMsg
+		r.blockErr.rule = rule
+		r.blockErr.snapshotValue = snapshot
+	}
+	r.waitMs = 0
+}
+
 func (r *TokenResult) IsPass() bool {
 	return r.status == ResultStatusPass
 }
@@ -77,10 +139,10 @@ func (r *TokenResult) String() string {
 	} else {
 		blockMsg = r.blockErr.Error()
 	}
-	return fmt.Sprintf("TokenResult{status=%d, blockErr=%s, waitMs=%d}", r.status, blockMsg, r.waitMs)
+	return fmt.Sprintf("TokenResult{status=%+v, blockErr=%s, waitMs=%d}", r.status, blockMsg, r.waitMs)
 }
 
-func NewTokenResultEmpty() *TokenResult {
+func NewTokenResultPass() *TokenResult {
 	return &TokenResult{
 		status:   ResultStatusPass,
 		blockErr: nil,
@@ -88,37 +150,34 @@ func NewTokenResultEmpty() *TokenResult {
 	}
 }
 
-func NewTokenResultPass() *TokenResult {
-	return resultPool.Get().(*TokenResult)
-}
-
 func NewTokenResultBlocked(blockType BlockType, blockMsg string) *TokenResult {
-	result := resultPool.Get().(*TokenResult)
-	result.status = ResultStatusBlocked
-	result.blockErr = NewBlockError(blockType, blockMsg)
-	return result
+	return &TokenResult{
+		status: ResultStatusBlocked,
+		blockErr: &BlockError{
+			blockType: blockType,
+			blockMsg:  blockMsg,
+		},
+		waitMs: 0,
+	}
 }
 
 func NewTokenResultBlockedWithCause(blockType BlockType, blockMsg string, rule SentinelRule, snapshot interface{}) *TokenResult {
-	result := resultPool.Get().(*TokenResult)
-	result.status = ResultStatusBlocked
-	result.blockErr = NewBlockErrorWithCause(blockType, blockMsg, rule, snapshot)
-	return result
+	return &TokenResult{
+		status: ResultStatusBlocked,
+		blockErr: &BlockError{
+			blockType:     blockType,
+			blockMsg:      blockMsg,
+			rule:          rule,
+			snapshotValue: snapshot,
+		},
+		waitMs: 0,
+	}
 }
 
 func NewTokenResultShouldWait(waitMs uint64) *TokenResult {
-	result := resultPool.Get().(*TokenResult)
-	result.status = ResultStatusShouldWait
-	result.waitMs = waitMs
-	return result
-}
-
-func RefurbishTokenResult(result *TokenResult) {
-	if result != nil {
-		result.status = ResultStatusPass
-		result.blockErr = nil
-		result.waitMs = 0
-
-		resultPool.Put(result)
+	return &TokenResult{
+		status:   ResultStatusShouldWait,
+		blockErr: nil,
+		waitMs:   waitMs,
 	}
 }
diff --git a/core/base/slot_chain.go b/core/base/slot_chain.go
index 7995390..83d5b65 100644
--- a/core/base/slot_chain.go
+++ b/core/base/slot_chain.go
@@ -53,7 +53,7 @@ type SlotChain struct {
 	ruleChecks []RuleCheckSlot
 	stats      []StatSlot
 	// EntryContext Pool, used for reuse EntryContext object
-	pool sync.Pool
+	ctxPool sync.Pool
 }
 
 func NewSlotChain() *SlotChain {
@@ -61,17 +61,19 @@ func NewSlotChain() *SlotChain {
 		statPres:   make([]StatPrepareSlot, 0, 5),
 		ruleChecks: make([]RuleCheckSlot, 0, 5),
 		stats:      make([]StatSlot, 0, 5),
-		pool: sync.Pool{
+		ctxPool: sync.Pool{
 			New: func() interface{} {
-				return NewEmptyEntryContext()
+				ctx := NewEmptyEntryContext()
+				ctx.RuleCheckResult = NewTokenResultPass()
+				return ctx
 			},
 		},
 	}
 }
 
-// Get a EntryContext from EntryContext pool, if pool doesn't have enough EntryContext then new one.
+// Get a EntryContext from EntryContext ctxPool, if ctxPool doesn't have enough EntryContext then new one.
 func (sc *SlotChain) GetPooledContext() *EntryContext {
-	ctx := sc.pool.Get().(*EntryContext)
+	ctx := sc.ctxPool.Get().(*EntryContext)
 	ctx.startTime = util.CurrentTimeMillis()
 	return ctx
 }
@@ -79,7 +81,7 @@ func (sc *SlotChain) GetPooledContext() *EntryContext {
 func (sc *SlotChain) RefurbishContext(c *EntryContext) {
 	if c != nil {
 		c.Reset()
-		sc.pool.Put(c)
+		sc.ctxPool.Put(c)
 	}
 }
 
@@ -139,27 +141,27 @@ func (sc *SlotChain) Entry(ctx *EntryContext) *TokenResult {
 	var ruleCheckRet *TokenResult
 	if len(rcs) > 0 {
 		for _, s := range rcs {
-			if ruleCheckRet != nil {
-				RefurbishTokenResult(ruleCheckRet)
-			}
-
 			sr := s.Check(ctx)
-			ruleCheckRet = sr
+			if sr == nil {
+				// nil equals to check pass
+				continue
+			}
 			// check slot result
-			if sr.status == ResultStatusBlocked {
+			if sr.IsBlocked() {
+				ruleCheckRet = sr
 				break
 			}
-
-			// This slot passed, continue.
 		}
 	}
 	if ruleCheckRet == nil {
-		ruleCheckRet = NewTokenResultPass()
+		ctx.RuleCheckResult.ResetToPass()
+	} else {
+		ctx.RuleCheckResult = ruleCheckRet
 	}
-	ctx.Output.LastResult = ruleCheckRet
 
 	// execute statistic slot
 	ss := sc.stats
+	ruleCheckRet = ctx.RuleCheckResult
 	if len(ss) > 0 {
 		for _, s := range ss {
 			// indicate the result of rule based checking slot.
diff --git a/core/circuit_breaker/circuit_breaker.go b/core/circuit_breaker/circuit_breaker.go
index 1aa9025..d5b9448 100644
--- a/core/circuit_breaker/circuit_breaker.go
+++ b/core/circuit_breaker/circuit_breaker.go
@@ -11,7 +11,7 @@ import (
 
 type CircuitBreaker interface {
 	getRule() Rule
-	Check(ctx *base.EntryContext) *base.TokenResult
+	TryPass(ctx *base.EntryContext) bool
 }
 
 // average rt circuit breaker will cut resource if the rt of resource exceed the threshold of rule.
@@ -49,14 +49,14 @@ func (b averageRtCircuitBreaker) getRule() Rule {
 	return b.rule
 }
 
-func (b *averageRtCircuitBreaker) Check(_ *base.EntryContext) *base.TokenResult {
+func (b *averageRtCircuitBreaker) TryPass(_ *base.EntryContext) bool {
 	// currently, the breaker is before auto recover, direct return blocked .
 	if b.cut.Get() {
-		return base.NewTokenResultBlocked(base.BlockTypeCircuitBreaking, "CircuitBreaking")
+		return false
 	}
 	rule := b.rule
 	if rule == nil {
-		return base.NewTokenResultPass()
+		return true
 	}
 
 	// TODO need to optimize here.
@@ -65,7 +65,7 @@ func (b *averageRtCircuitBreaker) Check(_ *base.EntryContext) *base.TokenResult
 		resNode := stat.GetResourceNode(rule.Resource)
 		if resNode == nil {
 			logger.Errorf("Resource(%s)'s stat node is nil.", rule.Resource)
-			return base.NewTokenResultPass()
+			return true
 		}
 		b.metric = resNode.GetOrCreateSlidingWindowMetric(rule.SampleCount, rule.IntervalInMs)
 		logger.Errorf("Delayed to initialize the metric of averageRtCircuitBreaker.")
@@ -74,10 +74,10 @@ func (b *averageRtCircuitBreaker) Check(_ *base.EntryContext) *base.TokenResult
 	avgRt := b.metric.AvgRT()
 	if avgRt < rule.Threshold {
 		atomic.StoreInt64(&b.passCount, 0)
-		return base.NewTokenResultPass()
+		return true
 	}
 	if util.IncrementAndGetInt64(&b.passCount) < rule.RtSlowRequestAmount {
-		return base.NewTokenResultPass()
+		return true
 	}
 	// trigger circuit breaker
 	if b.cut.CompareAndSet(false, true) {
@@ -88,7 +88,7 @@ func (b *averageRtCircuitBreaker) Check(_ *base.EntryContext) *base.TokenResult
 			b.cut.Set(false)
 		}, logger)
 	}
-	return base.NewTokenResultBlocked(base.BlockTypeCircuitBreaking, "CircuitBreaking")
+	return false
 }
 
 // error ratio circuit breaker will cut resource if the error ratio of resource exceed the threshold of rule.
@@ -125,14 +125,14 @@ func (b *errorRatioCircuitBreaker) getRule() Rule {
 	return b.rule
 }
 
-func (b *errorRatioCircuitBreaker) Check(_ *base.EntryContext) *base.TokenResult {
+func (b *errorRatioCircuitBreaker) TryPass(_ *base.EntryContext) bool {
 	if b.cut.Get() {
-		return base.NewTokenResultBlocked(base.BlockTypeCircuitBreaking, "CircuitBreaking")
+		return false
 	}
 
 	rule := b.rule
 	if rule == nil {
-		return base.NewTokenResultPass()
+		return true
 	}
 
 	// TODO need to optimize here.
@@ -141,7 +141,7 @@ func (b *errorRatioCircuitBreaker) Check(_ *base.EntryContext) *base.TokenResult
 		resNode := stat.GetResourceNode(rule.Resource)
 		if resNode == nil {
 			logger.Errorf("Resource(%s)'s stat node is nil.", rule.Resource)
-			return base.NewTokenResultPass()
+			return true
 		}
 		b.metric = resNode.GetOrCreateSlidingWindowMetric(rule.SampleCount, rule.IntervalInMs)
 		logger.Errorf("Delayed to initialize the metric of errorRatioCircuitBreaker.")
@@ -156,19 +156,19 @@ func (b *errorRatioCircuitBreaker) Check(_ *base.EntryContext) *base.TokenResult
 
 	// If total amount is less than minRequestAmount, the request will pass.
 	if total < float64(rule.MinRequestAmount) {
-		return base.NewTokenResultPass()
+		return true
 	}
 
 	// "success" (aka. completed count) = error count + non-error count (realComplete)
 	realComplete := complete - err
 	// error count
 	if realComplete <= 0 && err < float64(rule.MinRequestAmount) {
-		return base.NewTokenResultPass()
+		return true
 	}
 
 	// err/complete is error ratio of the biz
 	if err/complete < rule.Threshold {
-		return base.NewTokenResultPass()
+		return true
 	}
 
 	if b.cut.CompareAndSet(false, true) {
@@ -178,7 +178,7 @@ func (b *errorRatioCircuitBreaker) Check(_ *base.EntryContext) *base.TokenResult
 			b.cut.Set(false)
 		}, logger)
 	}
-	return base.NewTokenResultBlocked(base.BlockTypeCircuitBreaking, "CircuitBreaking")
+	return false
 }
 
 // error count circuit breaker will cut resource if the error count of resource exceed the threshold of rule.
@@ -215,14 +215,14 @@ func (b *errorCountCircuitBreaker) getRule() Rule {
 	return b.rule
 }
 
-func (b *errorCountCircuitBreaker) Check(_ *base.EntryContext) *base.TokenResult {
+func (b *errorCountCircuitBreaker) TryPass(_ *base.EntryContext) bool {
 	if b.cut.Get() {
-		return base.NewTokenResultBlocked(base.BlockTypeCircuitBreaking, "CircuitBreaking")
+		return false
 	}
 
 	rule := b.rule
 	if rule == nil {
-		return base.NewTokenResultPass()
+		return true
 	}
 
 	// TODO need to optimize here.
@@ -231,7 +231,7 @@ func (b *errorCountCircuitBreaker) Check(_ *base.EntryContext) *base.TokenResult
 		resNode := stat.GetResourceNode(rule.Resource)
 		if resNode == nil {
 			logger.Errorf("Resource(%s)'s stat node is nil.", rule.Resource)
-			return base.NewTokenResultPass()
+			return true
 		}
 		b.metric = resNode.GetOrCreateSlidingWindowMetric(rule.SampleCount, rule.IntervalInMs)
 		logger.Errorf("Delayed to initialize the metric of errorCountCircuitBreaker.")
@@ -239,7 +239,7 @@ func (b *errorCountCircuitBreaker) Check(_ *base.EntryContext) *base.TokenResult
 
 	err := b.metric.GetQPS(base.MetricEventError)
 	if err < float64(rule.Threshold) {
-		return base.NewTokenResultPass()
+		return true
 	}
 
 	if b.cut.CompareAndSet(false, true) {
@@ -249,5 +249,5 @@ func (b *errorCountCircuitBreaker) Check(_ *base.EntryContext) *base.TokenResult
 			b.cut.Set(false)
 		}, logger)
 	}
-	return base.NewTokenResultBlocked(base.BlockTypeCircuitBreaking, "CircuitBreaking")
+	return false
 }
diff --git a/core/circuit_breaker/slot.go b/core/circuit_breaker/slot.go
index e879973..055bb8e 100644
--- a/core/circuit_breaker/slot.go
+++ b/core/circuit_breaker/slot.go
@@ -1,8 +1,6 @@
 package circuit_breaker
 
 import (
-	"time"
-
 	"github.com/alibaba/sentinel-golang/core/base"
 )
 
@@ -11,28 +9,23 @@ type CircuitBreakerSlot struct {
 
 func (b *CircuitBreakerSlot) Check(ctx *base.EntryContext) *base.TokenResult {
 	resource := ctx.Resource.Name()
+	result := ctx.RuleCheckResult
 	if len(resource) == 0 {
-		return base.NewTokenResultPass()
+		return result
+	}
+	if !checkPass(ctx) {
+		result.ResetToBlockedFrom(base.BlockTypeCircuitBreaking, "CircuitBreaking")
 	}
-	return checkPass(ctx)
+	return result
 }
 
-func checkPass(ctx *base.EntryContext) *base.TokenResult {
+func checkPass(ctx *base.EntryContext) bool {
 	breakers := getResBreakers(ctx.Resource.Name())
 	for _, breaker := range breakers {
-		r := breaker.Check(ctx)
-		if r.Status() == base.ResultStatusBlocked {
-			return r
-		}
-		if r.Status() == base.ResultStatusShouldWait {
-			if waitMs := r.WaitMs(); waitMs > 0 {
-				// Handle waiting action.
-				time.Sleep(time.Duration(waitMs) * time.Millisecond)
-			}
-			base.RefurbishTokenResult(r)
-			continue
+		isPass := breaker.TryPass(ctx)
+		if !isPass {
+			return false
 		}
-		base.RefurbishTokenResult(r)
 	}
-	return base.NewTokenResultPass()
+	return true
 }
diff --git a/core/flow/slot.go b/core/flow/slot.go
index d55627a..2db75e8 100644
--- a/core/flow/slot.go
+++ b/core/flow/slot.go
@@ -14,8 +14,9 @@ type FlowSlot struct {
 func (s *FlowSlot) Check(ctx *base.EntryContext) *base.TokenResult {
 	res := ctx.Resource.Name()
 	tcs := getTrafficControllerListFor(res)
+	result := ctx.RuleCheckResult
 	if len(tcs) == 0 {
-		return base.NewTokenResultPass()
+		return result
 	}
 
 	// Check rules in order
@@ -25,6 +26,10 @@ func (s *FlowSlot) Check(ctx *base.EntryContext) *base.TokenResult {
 			continue
 		}
 		r := canPassCheck(tc, ctx.StatNode, ctx.Input.AcquireCount)
+		if r == nil {
+			// nil means pass
+			continue
+		}
 		if r.Status() == base.ResultStatusBlocked {
 			return r
 		}
@@ -33,12 +38,10 @@ func (s *FlowSlot) Check(ctx *base.EntryContext) *base.TokenResult {
 				// Handle waiting action.
 				time.Sleep(time.Duration(waitMs) * time.Millisecond)
 			}
-			base.RefurbishTokenResult(r)
 			continue
 		}
-		base.RefurbishTokenResult(r)
 	}
-	return base.NewTokenResultPass()
+	return result
 }
 
 func canPassCheck(tc *TrafficShapingController, node base.StatNode, acquireCount uint32) *base.TokenResult {
diff --git a/core/flow/tc_default.go b/core/flow/tc_default.go
index 55f58c7..48d3702 100644
--- a/core/flow/tc_default.go
+++ b/core/flow/tc_default.go
@@ -26,7 +26,7 @@ func NewDefaultTrafficShapingChecker(metricType MetricType) *DefaultTrafficShapi
 
 func (d *DefaultTrafficShapingChecker) DoCheck(node base.StatNode, acquireCount uint32, threshold float64) *base.TokenResult {
 	if node == nil {
-		return base.NewTokenResultPass()
+		return nil
 	}
 	var curCount float64
 	if d.metricType == Concurrency {
@@ -37,5 +37,5 @@ func (d *DefaultTrafficShapingChecker) DoCheck(node base.StatNode, acquireCount
 	if curCount+float64(acquireCount) > threshold {
 		return base.NewTokenResultBlocked(base.BlockTypeFlow, "Flow")
 	}
-	return base.NewTokenResultPass()
+	return nil
 }
diff --git a/core/flow/tc_throttling.go b/core/flow/tc_throttling.go
index 94e8b5a..f9a85fd 100644
--- a/core/flow/tc_throttling.go
+++ b/core/flow/tc_throttling.go
@@ -31,7 +31,7 @@ func NewThrottlingChecker(timeoutMs uint32) *ThrottlingChecker {
 func (c *ThrottlingChecker) DoCheck(_ base.StatNode, acquireCount uint32, threshold float64) *base.TokenResult {
 	// Pass when acquire count is less or equal than 0.
 	if acquireCount <= 0 {
-		return base.NewTokenResultPass()
+		return nil
 	}
 	if threshold <= 0 {
 		return base.NewTokenResultBlocked(base.BlockTypeFlow, "Flow")
@@ -46,7 +46,7 @@ func (c *ThrottlingChecker) DoCheck(_ base.StatNode, acquireCount uint32, thresh
 	if expectedTime <= curNano {
 		// Contention may exist here, but it's okay.
 		atomic.StoreUint64(&c.lastPassedTime, curNano)
-		return base.NewTokenResultPass()
+		return nil
 	}
 	estimatedQueueingDuration := atomic.LoadUint64(&c.lastPassedTime) + interval - util.CurrentTimeNano()
 	if estimatedQueueingDuration > c.maxQueueingTimeNs {
diff --git a/core/stat/stat_slot.go b/core/stat/stat_slot.go
index 6706d8d..97d42bc 100644
--- a/core/stat/stat_slot.go
+++ b/core/stat/stat_slot.go
@@ -29,7 +29,7 @@ func (s *StatisticSlot) OnEntryBlocked(ctx *base.EntryContext, blockError *base.
 }
 
 func (s *StatisticSlot) OnCompleted(ctx *base.EntryContext) {
-	if ctx.Output.LastResult == nil || ctx.Output.LastResult.IsBlocked() {
+	if ctx.RuleCheckResult == nil || ctx.RuleCheckResult.IsBlocked() {
 		return
 	}
 	rt := util.CurrentTimeMillis() - ctx.StartTime()
diff --git a/core/system/slot.go b/core/system/slot.go
index 04d9d2b..36173f5 100644
--- a/core/system/slot.go
+++ b/core/system/slot.go
@@ -12,17 +12,23 @@ type SystemAdaptiveSlot struct {
 
 func (s *SystemAdaptiveSlot) Check(ctx *base.EntryContext) *base.TokenResult {
 	if ctx == nil || ctx.Resource == nil || ctx.Resource.FlowType() != base.Inbound {
-		return base.NewTokenResultPass()
+		return nil
 	}
 	rules := GetRules()
+	result := ctx.RuleCheckResult
 	for _, rule := range rules {
 		passed, m := s.doCheckRule(rule)
 		if passed {
 			continue
 		}
-		return base.NewTokenResultBlockedWithCause(base.BlockTypeSystemFlow, base.BlockTypeSystemFlow.String(), rule, m)
+		if result == nil {
+			result = base.NewTokenResultBlockedWithCause(base.BlockTypeSystemFlow, base.BlockTypeSystemFlow.String(), rule, m)
+		} else {
+			result.ResetToBlockedWithCauseFrom(base.BlockTypeSystemFlow, base.BlockTypeSystemFlow.String(), rule, m)
+		}
+		return result
 	}
-	return base.NewTokenResultPass()
+	return result
 }
 
 func (s *SystemAdaptiveSlot) doCheckRule(rule *SystemRule) (bool, float64) {


Test patch:
diff --git a/api/tracer_test.go b/api/tracer_test.go
index 8f8fa86..73411f9 100644
--- a/api/tracer_test.go
+++ b/api/tracer_test.go
@@ -40,7 +40,6 @@ func TestTraceErrorToCtx(t *testing.T) {
 		Resource: testRes,
 		StatNode: testStatNode,
 		Input:    nil,
-		Output:   nil,
 	}
 	tests[0].args.err = errors.New("biz error")
 
@@ -79,7 +78,6 @@ func TestTraceErrorToEntry(t *testing.T) {
 		Resource: testRes,
 		StatNode: testStatNode,
 		Input:    nil,
-		Output:   nil,
 	}
 	tests[0].args.entry = base.NewSentinelEntry(ctx, testRes, nil)
 	tests[0].args.err = errors.New("biz error")
diff --git a/core/base/context_test.go b/core/base/context_test.go
index 29ac2fb..1293e8d 100644
--- a/core/base/context_test.go
+++ b/core/base/context_test.go
@@ -9,6 +9,6 @@ import (
 func TestEntryContext_IsBlocked(t *testing.T) {
 	ctx := NewEmptyEntryContext()
 	assert.False(t, ctx.IsBlocked(), "empty context with no result should indicate pass")
-	ctx.Output = &SentinelOutput{LastResult: NewTokenResultBlocked(BlockTypeUnknown, "")}
+	ctx.RuleCheckResult = NewTokenResultBlocked(BlockTypeUnknown, "Unknown")
 	assert.True(t, ctx.IsBlocked(), "context with blocked request should indicate blocked")
 }
diff --git a/core/base/result_test.go b/core/base/result_test.go
deleted file mode 100644
index d5db9f9..0000000
--- a/core/base/result_test.go
+++ /dev/null
@@ -1,29 +0,0 @@
-package base
-
-import (
-	"testing"
-)
-
-func BenchmarkWithPool(b *testing.B) {
-	var result *TokenResult
-	b.ReportAllocs()
-	for i := 0; i < b.N; i++ {
-		for j := 0; j < 10000; j++ {
-			result = NewTokenResultPass()
-			RefurbishTokenResult(result)
-		}
-	}
-}
-
-func BenchmarkWithNoPool(b *testing.B) {
-	var result *TokenResult
-	b.ReportAllocs()
-	for i := 0; i < b.N; i++ {
-		for j := 0; j < 10000; j++ {
-			result = NewTokenResultEmpty()
-			result.status = ResultStatusPass
-			result.blockErr = nil
-			result.waitMs = 0
-		}
-	}
-}
diff --git a/core/circuit_breaker/circuit_breaker_test.go b/core/circuit_breaker/circuit_breaker_test.go
index ab0dca2..c5807d3 100644
--- a/core/circuit_breaker/circuit_breaker_test.go
+++ b/core/circuit_breaker/circuit_breaker_test.go
@@ -52,9 +52,9 @@ func (m *circuitBreakerMock) getRule() Rule {
 	return args.Get(0).(Rule)
 }
 
-func (m *circuitBreakerMock) Check(_ *base.EntryContext) *base.TokenResult {
+func (m *circuitBreakerMock) TryPass(_ *base.EntryContext) bool {
 	args := m.Called()
-	return args.Get(0).(*base.TokenResult)
+	return args.Bool(0)
 }
 
 func Test_AverageRtCircuitBreaker_Check(t *testing.T) {
@@ -92,23 +92,23 @@ func Test_AverageRtCircuitBreaker_Check(t *testing.T) {
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			for i := 0; i < 4; i++ {
-				if got := tt.breaker.Check(tt.args.ctx); got.IsPass() != true {
-					t.Errorf("averageRtCircuitBreaker.Check() = %v, want %v", got, true)
+				if got := tt.breaker.TryPass(tt.args.ctx); got != true {
+					t.Errorf("averageRtCircuitBreaker.TryPass() = %v, want %v", got, true)
 				}
 			}
 
-			if got := tt.breaker.Check(tt.args.ctx); got.IsPass() != false {
-				t.Errorf("averageRtCircuitBreaker.Check() = %v, want %v", got, false)
+			if got := tt.breaker.TryPass(tt.args.ctx); got != false {
+				t.Errorf("averageRtCircuitBreaker.TryPass() = %v, want %v", got, false)
 			}
 
 			// before auto recover
-			if got := tt.breaker.Check(tt.args.ctx); got.IsPass() != false {
-				t.Errorf("averageRtCircuitBreaker.Check() = %v, want %v", got, false)
+			if got := tt.breaker.TryPass(tt.args.ctx); got != false {
+				t.Errorf("averageRtCircuitBreaker.TryPass() = %v, want %v", got, false)
 			}
 
 			time.Sleep(2 * time.Second)
-			if got := tt.breaker.Check(tt.args.ctx); got.IsPass() != true {
-				t.Errorf("averageRtCircuitBreaker.Check() = %v, want %v", got, true)
+			if got := tt.breaker.TryPass(tt.args.ctx); got != true {
+				t.Errorf("averageRtCircuitBreaker.TryPass() = %v, want %v", got, true)
 			}
 		})
 	}
@@ -154,8 +154,8 @@ func Test_ErrorRatioCircuitBreaker_Check(t *testing.T) {
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			for i := 0; i < 4; i++ {
-				if got := tt.breaker.Check(tt.args.ctx); got.IsPass() != true {
-					t.Errorf("ErrorRatioCircuitBreaker.Check() = %v, want %v", got, true)
+				if got := tt.breaker.TryPass(tt.args.ctx); got != true {
+					t.Errorf("ErrorRatioCircuitBreaker.TryPass() = %v, want %v", got, true)
 				}
 			}
 
@@ -166,8 +166,8 @@ func Test_ErrorRatioCircuitBreaker_Check(t *testing.T) {
 			m2.On("GetQPS", base.MetricEventPass).Return(800)
 			m2.On("GetQPS", base.MetricEventBlock).Return(200)
 
-			if got := tt.breaker.Check(tt.args.ctx); got.IsPass() != false {
-				t.Errorf("ErrorRatioCircuitBreaker.Check() = %v, want %v", got, false)
+			if got := tt.breaker.TryPass(tt.args.ctx); got != false {
+				t.Errorf("ErrorRatioCircuitBreaker.TryPass() = %v, want %v", got, false)
 			}
 			time.Sleep(2 * time.Second)
 
@@ -180,8 +180,8 @@ func Test_ErrorRatioCircuitBreaker_Check(t *testing.T) {
 			m3.On("GetQPS", base.MetricEventBlock).Return(0)
 			m3.On("GetQPS", base.MetricEventPass).Return(0)
 			m3.On("GetQPS", base.MetricEventBlock).Return(0)
-			if got := tt.breaker.Check(tt.args.ctx); got.IsPass() != true {
-				t.Errorf("ErrorRatioCircuitBreaker.Check() = %v, want %v", got, true)
+			if got := tt.breaker.TryPass(tt.args.ctx); got != true {
+				t.Errorf("ErrorRatioCircuitBreaker.TryPass() = %v, want %v", got, true)
 			}
 		})
 	}
@@ -221,8 +221,8 @@ func Test_ErrorCountCircuitBreaker_Check(t *testing.T) {
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			for i := 0; i < 4; i++ {
-				if got := tt.breaker.Check(tt.args.ctx); got.IsPass() != true {
-					t.Errorf("ErrorCountCircuitBreaker.Check() = %v, want %v", got, true)
+				if got := tt.breaker.TryPass(tt.args.ctx); got != true {
+					t.Errorf("ErrorCountCircuitBreaker.TryPass() = %v, want %v", got, true)
 				}
 			}
 
@@ -230,8 +230,8 @@ func Test_ErrorCountCircuitBreaker_Check(t *testing.T) {
 			tt.breaker.metric = m2
 			m2.On("GetQPS", base.MetricEventError).Return(11)
 			for i := 0; i < 10; i++ {
-				if got := tt.breaker.Check(tt.args.ctx); got.IsPass() != false {
-					t.Errorf("ErrorCountCircuitBreaker.Check() = %v, want %v", got, true)
+				if got := tt.breaker.TryPass(tt.args.ctx); got != false {
+					t.Errorf("ErrorCountCircuitBreaker.TryPass() = %v, want %v", got, true)
 				}
 			}
 			time.Sleep(2 * time.Second)
@@ -239,8 +239,8 @@ func Test_ErrorCountCircuitBreaker_Check(t *testing.T) {
 			m3 := &ReadStatMock{}
 			tt.breaker.metric = m3
 			m3.On("GetQPS", base.MetricEventError).Return(1)
-			if got := tt.breaker.Check(tt.args.ctx); got.IsPass() != true {
-				t.Errorf("ErrorCountCircuitBreaker.Check() = %v, want %v", got, true)
+			if got := tt.breaker.TryPass(tt.args.ctx); got != true {
+				t.Errorf("ErrorCountCircuitBreaker.TryPass() = %v, want %v", got, true)
 			}
 		})
 	}
diff --git a/core/flow/tc_throttling_test.go b/core/flow/tc_throttling_test.go
index 01f6138..6481cc2 100644
--- a/core/flow/tc_throttling_test.go
+++ b/core/flow/tc_throttling_test.go
@@ -15,14 +15,15 @@ func TestThrottlingChecker_DoCheckNoQueueingSingleThread(t *testing.T) {
 	var qps float64 = 5
 
 	// The first request will pass.
-	assert.True(t, tc.DoCheck(nil, 1, qps).IsPass())
+	ret := tc.DoCheck(nil, 1, qps)
+	assert.True(t, ret == nil || ret.IsPass())
 
 	for i := 0; i < int(qps); i++ {
 		assert.True(t, tc.DoCheck(nil, 1, qps).IsBlocked())
 	}
 	time.Sleep(time.Duration(1000/int(qps)+10) * time.Millisecond)
 
-	assert.True(t, tc.DoCheck(nil, 1, qps).IsPass())
+	assert.True(t, tc.DoCheck(nil, 1, qps) == nil)
 	assert.True(t, tc.DoCheck(nil, 1, qps).IsBlocked())
 }
 
@@ -34,7 +35,7 @@ func TestThrottlingChecker_DoCheckSingleThread(t *testing.T) {
 		res := tc.DoCheck(nil, 1, qps)
 		resultList = append(resultList, res)
 	}
-	assert.True(t, resultList[0].IsPass())
+	assert.True(t, resultList[0] == nil)
 
 	for i := 1; i <= int(qps); i++ {
 		assert.True(t, resultList[i].Status() == base.ResultStatusShouldWait)
@@ -50,7 +51,7 @@ func TestThrottlingChecker_DoCheckQueueingParallel(t *testing.T) {
 	tc := NewThrottlingChecker(1000)
 	var qps float64 = 5
 
-	assert.True(t, tc.DoCheck(nil, 1, qps).IsPass())
+	assert.True(t, tc.DoCheck(nil, 1, qps) == nil)
 
 	wg := &sync.WaitGroup{}
 	gc := 24
diff --git a/core/system/slot_test.go b/core/system/slot_test.go
index 6911857..0da5d05 100644
--- a/core/system/slot_test.go
+++ b/core/system/slot_test.go
@@ -10,31 +10,32 @@ import (
 
 func TestCheckNilInput(t *testing.T) {
 	var sas *SystemAdaptiveSlot
-	e := base.NewTokenResultPass()
 
 	t.Run("NilInput", func(t *testing.T) {
 		r := sas.Check(nil)
-		assert.Equal(t, e, r)
+		assert.True(t, r == nil)
 	})
 
 	t.Run("NilResourceInput", func(t *testing.T) {
 		r := sas.Check(&base.EntryContext{})
-		assert.Equal(t, e, r)
+		assert.True(t, r == nil)
 	})
 
 	t.Run("UnsuitableFlowType", func(t *testing.T) {
 		rw := base.NewResourceWrapper("test", base.ResTypeCommon, base.Outbound)
 		r := sas.Check(&base.EntryContext{Resource: rw})
-		assert.Equal(t, e, r)
+		assert.True(t, r == nil)
 	})
 }
 
 func TestCheckEmptyRule(t *testing.T) {
 	var sas *SystemAdaptiveSlot
-	e := base.NewTokenResultPass()
 	rw := base.NewResourceWrapper("test", base.ResTypeCommon, base.Inbound)
-	r := sas.Check(&base.EntryContext{Resource: rw})
-	assert.Equal(t, e, r)
+	r := sas.Check(&base.EntryContext{
+		Resource:        rw,
+		RuleCheckResult: base.NewTokenResultPass(),
+	})
+	assert.True(t, r == nil || r.IsPass())
 }
 
 func TestDoCheckRuleConcurrency(t *testing.T) {
diff --git a/example/qps_limit_example.go b/example/qps/qps_limit_example.go
similarity index 100%
rename from example/qps_limit_example.go
rename to example/qps/qps_limit_example.go
diff --git a/example/qps/qps_limit_example_test.go b/example/qps/qps_limit_example_test.go
new file mode 100644
index 0000000..b8ae709
--- /dev/null
+++ b/example/qps/qps_limit_example_test.go
@@ -0,0 +1,62 @@
+package main
+
+import (
+	"fmt"
+	"log"
+	"math/rand"
+	"testing"
+	"time"
+
+	sentinel "github.com/alibaba/sentinel-golang/api"
+	"github.com/alibaba/sentinel-golang/core/base"
+	"github.com/alibaba/sentinel-golang/core/flow"
+	"github.com/alibaba/sentinel-golang/util"
+)
+
+func Benchmark_qps(b *testing.B) {
+	for i := 0; i < b.N; i++ {
+		doTest()
+	}
+}
+
+func doTest() {
+	// We should initialize Sentinel first.
+	err := sentinel.InitDefault()
+	if err != nil {
+		log.Fatalf("Unexpected error: %+v", err)
+	}
+
+	_, err = flow.LoadRules([]*flow.FlowRule{
+		{
+			Resource:        "some-test",
+			MetricType:      flow.QPS,
+			Count:           100,
+			ControlBehavior: flow.Reject,
+		},
+	})
+	if err != nil {
+		log.Fatalf("Unexpected error: %+v", err)
+		return
+	}
+
+	for i := 0; i < 10; i++ {
+		go func() {
+			for {
+				e, b := sentinel.Entry("some-test", sentinel.WithTrafficType(base.Inbound))
+				if b != nil {
+					// Blocked. We could get the block reason from the BlockError.
+					time.Sleep(time.Duration(rand.Uint64()%10) * time.Millisecond)
+				} else {
+					// Passed, wrap the logic here.
+					fmt.Println(util.CurrentTimeMillis(), "passed")
+					time.Sleep(time.Duration(rand.Uint64()%10) * time.Millisecond)
+
+					// Be sure the entry is exited finally.
+					e.Exit()
+				}
+
+			}
+		}()
+	}
+	time.Sleep(time.Second * 5)
+}
diff --git a/example/sentinel.yml b/example/qps/sentinel.yml
similarity index 100%
rename from example/sentinel.yml
rename to example/qps/sentinel.yml
diff --git a/util/time_test.go b/util/time_test.go
index 87566ce..d057286 100644
--- a/util/time_test.go
+++ b/util/time_test.go
@@ -83,12 +83,11 @@ func TestFormatDate(t *testing.T) {
 
 func TestCurrentTimeMillis(t *testing.T) {
 	got := CurrentTimeMillis()
-	t.Logf("CurrentTimeMillis() %d", got)
+	fmt.Printf("CurrentTimeMillis() %d", got)
 	fmt.Println(FormatTimeMillis(got))
 }
 
 func TestCurrentTimeNano(t *testing.T) {
 	got := CurrentTimeNano()
 	fmt.Println(got)
-	t.Logf("CurrentTimeNano() %d", got)
 }