Repository: alibaba/sentinel-golang
Pull request #388 (2021-02-01 03:12:42)

feat:block_err add opts func ,add RegistryBlockType func;
chore: unit test & bench test

<!--  Thanks for submitting a pull request! Here are some tips for you:
1. Please make sure you have read and understood the contributing guidelines: https://github.com/alibaba/blob/master/CONTRIBUTING.md
2. Please make sure the PR has a corresponding issue.
-->

### Describe what this PR does / why we need it

feat:
Add option func for block error to facilitate expansion, and add New and Reset methods;
Add custom registration BlockType function;

refactor:
Optimize performance of blockType String function. detail info in bench function.

### Does this pull request fix one issue?

<!--If that, add "Fixes #xxxx" below in the next line. For example, Fixes #15. Otherwise, add "NONE" -->

### Describe how you did it


### Describe how to verify it


### Special notes for reviews

cafra: block err 增加options 模式，方便自定义 slot 使用:
1. 注册错误类型；
2. 提高错误的扩展性；
3. 优化错误的String方法的性能；

Patch:
diff --git a/core/base/block_error.go b/core/base/block_error.go
index c110331..bc32fea 100644
--- a/core/base/block_error.go
+++ b/core/base/block_error.go
@@ -27,6 +27,50 @@ type BlockError struct {
 	snapshotValue interface{}
 }
 
+type BlockErrorOption func(*BlockError)
+
+func WithBlockType(blockType BlockType) BlockErrorOption {
+	return func(b *BlockError) {
+		b.blockType = blockType
+	}
+}
+
+func WithBlockMsg(blockMsg string) BlockErrorOption {
+	return func(b *BlockError) {
+		b.blockMsg = blockMsg
+	}
+}
+
+func WithRule(rule SentinelRule) BlockErrorOption {
+	return func(b *BlockError) {
+		b.rule = rule
+	}
+}
+
+func WithSnapshotValue(snapshotValue interface{}) BlockErrorOption {
+	return func(b *BlockError) {
+		b.snapshotValue = snapshotValue
+	}
+}
+
+func NewBlockError(opts ...BlockErrorOption) *BlockError {
+	b := &BlockError{
+		blockType: BlockTypeUnknown,
+	}
+
+	for _, opt := range opts {
+		opt(b)
+	}
+	return b
+}
+
+func (e *BlockError) ResetBlockError(opts ...BlockErrorOption) {
+	for _, opt := range opts {
+		opt(e)
+	}
+	return
+}
+
 func (e *BlockError) BlockMsg() string {
 	return e.blockMsg
 }
@@ -52,16 +96,12 @@ func NewBlockErrorFromDeepCopy(from *BlockError) *BlockError {
 	}
 }
 
-func NewBlockError(blockType BlockType) *BlockError {
-	return &BlockError{blockType: blockType}
-}
-
 func NewBlockErrorWithMessage(blockType BlockType, message string) *BlockError {
-	return &BlockError{blockType: blockType, blockMsg: message}
+	return NewBlockError(WithBlockType(blockType), WithBlockMsg(message))
 }
 
 func NewBlockErrorWithCause(blockType BlockType, blockMsg string, rule SentinelRule, snapshot interface{}) *BlockError {
-	return &BlockError{blockType: blockType, blockMsg: blockMsg, rule: rule, snapshotValue: snapshot}
+	return NewBlockError(WithBlockType(blockType), WithBlockMsg(blockMsg), WithRule(rule), WithSnapshotValue(snapshot))
 }
 
 func (e *BlockError) Error() string {
diff --git a/core/base/result.go b/core/base/result.go
index dd65a9b..3257903 100644
--- a/core/base/result.go
+++ b/core/base/result.go
@@ -16,6 +16,7 @@ package base
 
 import (
 	"fmt"
+	"strconv"
 	"time"
 )
 
@@ -28,25 +29,38 @@ const (
 	BlockTypeCircuitBreaking
 	BlockTypeSystemFlow
 	BlockTypeHotSpotParamFlow
+	// BlockTypeRegistryStart convenient to expand the type to avoid conflicts
+	BlockTypeRegistryStart
 )
 
+var (
+	blockTypeNames = []string{
+		BlockTypeUnknown:          "Unknown",
+		BlockTypeFlow:             "FlowControl",
+		BlockTypeIsolation:        "BlockTypeIsolation",
+		BlockTypeCircuitBreaking:  "CircuitBreaking",
+		BlockTypeSystemFlow:       "System",
+		BlockTypeHotSpotParamFlow: "HotSpotParamFlow",
+		BlockTypeRegistryStart:    "BlockTypeRegistryStart",
+	}
+
+	blockTypeErr = fmt.Errorf("block type err")
+)
+
+// RegistryBlockType adds block type and corresponding description in order.
+func RegistryBlockType(blockType BlockType, desc string) error {
+	if int(blockType) != len(blockTypeNames) {
+		return blockTypeErr
+	}
+	blockTypeNames = append(blockTypeNames, desc)
+	return nil
+}
+
 func (t BlockType) String() string {
-	switch t {
-	case BlockTypeUnknown:
-		return "Unknown"
-	case BlockTypeFlow:
-		return "FlowControl"
-	case BlockTypeIsolation:
-		return "BlockTypeIsolation"
-	case BlockTypeCircuitBreaking:
-		return "CircuitBreaking"
-	case BlockTypeSystemFlow:
-		return "System"
-	case BlockTypeHotSpotParamFlow:
-		return "HotSpotParamFlow"
-	default:
-		return fmt.Sprintf("%d", t)
+	if int(t) >= len(blockTypeNames) {
+		return strconv.Itoa(int(t))
 	}
+	return blockTypeNames[t]
 }
 
 type TokenResultStatus uint8
@@ -95,50 +109,32 @@ func (r *TokenResult) DeepCopyFrom(newResult *TokenResult) {
 		r.blockErr.snapshotValue = newResult.blockErr.snapshotValue
 	}
 }
-
 func (r *TokenResult) ResetToPass() {
 	r.status = ResultStatusPass
 	r.blockErr = nil
 	r.nanosToWait = 0
 }
 
-func (r *TokenResult) ResetToBlocked(blockType BlockType) {
+func (r *TokenResult) ResetToBlockedWith(opts ...BlockErrorOption) {
 	r.status = ResultStatusBlocked
 	if r.blockErr == nil {
-		r.blockErr = NewBlockError(blockType)
+		r.blockErr = NewBlockError(opts...)
 	} else {
-		r.blockErr.blockType = blockType
-		r.blockErr.blockMsg = ""
-		r.blockErr.rule = nil
-		r.blockErr.snapshotValue = nil
+		r.blockErr.ResetBlockError(opts...)
 	}
 	r.nanosToWait = 0
 }
 
+func (r *TokenResult) ResetToBlocked(blockType BlockType) {
+	r.ResetToBlockedWith(WithBlockType(blockType))
+}
+
 func (r *TokenResult) ResetToBlockedWithMessage(blockType BlockType, blockMsg string) {
-	r.status = ResultStatusBlocked
-	if r.blockErr == nil {
-		r.blockErr = NewBlockErrorWithMessage(blockType, blockMsg)
-	} else {
-		r.blockErr.blockType = blockType
-		r.blockErr.blockMsg = blockMsg
-		r.blockErr.rule = nil
-		r.blockErr.snapshotValue = nil
-	}
-	r.nanosToWait = 0
+	r.ResetToBlockedWith(WithBlockType(blockType), WithBlockMsg(blockMsg))
 }
 
 func (r *TokenResult) ResetToBlockedWithCause(blockType BlockType, blockMsg string, rule SentinelRule, snapshot interface{}) {
-	r.status = ResultStatusBlocked
-	if r.blockErr == nil {
-		r.blockErr = NewBlockErrorWithCause(blockType, blockMsg, rule, snapshot)
-	} else {
-		r.blockErr.blockType = blockType
-		r.blockErr.blockMsg = blockMsg
-		r.blockErr.rule = rule
-		r.blockErr.snapshotValue = snapshot
-	}
-	r.nanosToWait = 0
+	r.ResetToBlockedWith(WithBlockType(blockType), WithBlockMsg(blockMsg), WithRule(rule), WithSnapshotValue(snapshot))
 }
 
 func (r *TokenResult) IsPass() bool {
@@ -172,41 +168,31 @@ func (r *TokenResult) String() string {
 }
 
 func NewTokenResultPass() *TokenResult {
-	return &TokenResult{
-		status:      ResultStatusPass,
-		blockErr:    nil,
-		nanosToWait: 0,
-	}
+	return NewTokenResult(ResultStatusPass)
 }
 
 func NewTokenResultBlocked(blockType BlockType) *TokenResult {
-	return &TokenResult{
-		status:      ResultStatusBlocked,
-		blockErr:    NewBlockError(blockType),
-		nanosToWait: 0,
-	}
+	return NewTokenResult(ResultStatusBlocked, WithBlockType(blockType))
 }
 
 func NewTokenResultBlockedWithMessage(blockType BlockType, blockMsg string) *TokenResult {
-	return &TokenResult{
-		status:      ResultStatusBlocked,
-		blockErr:    NewBlockErrorWithMessage(blockType, blockMsg),
-		nanosToWait: 0,
-	}
+	return NewTokenResult(ResultStatusBlocked, WithBlockType(blockType), WithBlockMsg(blockMsg))
 }
 
 func NewTokenResultBlockedWithCause(blockType BlockType, blockMsg string, rule SentinelRule, snapshot interface{}) *TokenResult {
+	return NewTokenResult(ResultStatusBlocked, WithBlockType(blockType), WithBlockMsg(blockMsg), WithRule(rule), WithSnapshotValue(snapshot))
+}
+
+func NewTokenResult(status TokenResultStatus, blockErrOpts ...BlockErrorOption) *TokenResult {
 	return &TokenResult{
-		status:      ResultStatusBlocked,
-		blockErr:    NewBlockErrorWithCause(blockType, blockMsg, rule, snapshot),
+		status:      status,
+		blockErr:    NewBlockError(blockErrOpts...),
 		nanosToWait: 0,
 	}
 }
 
 func NewTokenResultShouldWait(waitNs time.Duration) *TokenResult {
-	return &TokenResult{
-		status:      ResultStatusShouldWait,
-		blockErr:    nil,
-		nanosToWait: waitNs,
-	}
+	result := NewTokenResult(ResultStatusShouldWait)
+	result.nanosToWait = waitNs
+	return result
 }


Test patch:
diff --git a/core/base/block_error_test.go b/core/base/block_error_test.go
new file mode 100644
index 0000000..40134db
--- /dev/null
+++ b/core/base/block_error_test.go
@@ -0,0 +1,50 @@
+package base
+
+import (
+	"reflect"
+	"testing"
+)
+
+func TestNewBlockError(t *testing.T) {
+	type args struct {
+		opts []BlockErrorOption
+	}
+	tests := []struct {
+		name string
+		args args
+		want *BlockError
+	}{
+		{
+			name: "normal",
+			args: struct{ opts []BlockErrorOption }{opts: []BlockErrorOption{
+				WithBlockType(BlockTypeFlow),
+				WithBlockMsg("test"),
+				WithRule(new(MockRule)),
+				WithSnapshotValue("snapshot"),
+			}},
+			want: NewBlockError(WithBlockType(BlockTypeFlow),
+				WithBlockMsg("test"),
+				WithRule(new(MockRule)),
+				WithSnapshotValue("snapshot")),
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := NewBlockError(tt.args.opts...); !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("NewBlockError() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+type MockRule struct {
+	Id string `json:"id"`
+}
+
+func (m *MockRule) String() string {
+	return "mock rule"
+}
+
+func (m *MockRule) ResourceName() string {
+	return "mock resource"
+}
diff --git a/core/base/result_test.go b/core/base/result_test.go
new file mode 100644
index 0000000..d705002
--- /dev/null
+++ b/core/base/result_test.go
@@ -0,0 +1,164 @@
+package base
+
+import (
+	"fmt"
+	"strconv"
+	"testing"
+)
+
+/*
+BenchmarkBlockType_String/Switch
+BenchmarkBlockType_String/Switch-4         	633041816	         1.89 ns/op
+BenchmarkBlockType_String/Slice
+BenchmarkBlockType_String/Slice-4          	1000000000	         0.387 ns/op
+BenchmarkBlockType_String/Map
+BenchmarkBlockType_String/Map-4            	55307811	        20.0 ns/op
+*/
+
+func BenchmarkBlockType_String(b *testing.B) {
+	b.ReportAllocs()
+
+	//BlockTypeNew1 := BlockType(6)
+	//RegistryBlockType(BlockTypeNew1, "new1")
+	BlockTypeTmp := BlockTypeFlow
+
+	b.Run("Switch", func(b *testing.B) {
+		for i := 0; i < b.N; i++ {
+			BlockTypeTmp.stringSwitch()
+		}
+	})
+	b.Run("Slice", func(b *testing.B) {
+		for i := 0; i < b.N; i++ {
+			BlockTypeTmp.string()
+		}
+	})
+	b.Run("Map", func(b *testing.B) {
+		for i := 0; i < b.N; i++ {
+			BlockTypeTmp.stringMap()
+		}
+	})
+}
+
+/*
+BenchmarkBlockType_Registry_String/Slice-4         	1000000000	         1.16 ns/op
+*/
+func BenchmarkBlockType_Registry_String(b *testing.B) {
+	b.ReportAllocs()
+
+	BlockTypeNew1 := BlockType(6)
+	RegistryBlockType(BlockTypeNew1, "new1")
+	BlockTypeTmp := BlockTypeNew1
+
+	b.Run("Registry_String", func(b *testing.B) {
+		for i := 0; i < b.N; i++ {
+			BlockTypeTmp.string()
+		}
+	})
+}
+
+func (t BlockType) stringSwitch() string {
+	switch t {
+	case BlockTypeUnknown:
+		return "Unknown"
+	case BlockTypeFlow:
+		return "FlowControl"
+	case BlockTypeIsolation:
+		return "BlockTypeIsolation"
+	case BlockTypeCircuitBreaking:
+		return "CircuitBreaking"
+	case BlockTypeSystemFlow:
+		return "System"
+	case BlockTypeHotSpotParamFlow:
+		return "HotSpotParamFlow"
+	default:
+		return fmt.Sprintf("%d", t)
+	}
+}
+
+var (
+	blockTypeMap = map[BlockType]string{
+		BlockTypeUnknown:          "Unknown",
+		BlockTypeFlow:             "FlowControl",
+		BlockTypeIsolation:        "BlockTypeIsolation",
+		BlockTypeCircuitBreaking:  "CircuitBreaking",
+		BlockTypeSystemFlow:       "System",
+		BlockTypeHotSpotParamFlow: "HotSpotParamFlow",
+	}
+)
+
+func (t BlockType) stringMap() string {
+	name, ok := blockTypeMap[t]
+	if ok {
+		return name
+	}
+	return fmt.Sprintf("%d", t)
+}
+
+func (t BlockType) string() string {
+	if int(t) >= len(blockTypeNames) {
+		return strconv.Itoa(int(t))
+	}
+	return blockTypeNames[t]
+}
+
+func TestRegistryBlockType(t *testing.T) {
+	type args struct {
+		blockType BlockType
+		desc      string
+	}
+
+	var (
+		New1BlockType = BlockType(BlockTypeRegistryStart + 1)
+		New2BlockType = BlockType(BlockTypeRegistryStart + 2)
+	)
+
+	tests := []struct {
+		name    string
+		args    args
+		wantErr bool
+	}{
+		{
+			name: "Unknown",
+			args: struct {
+				blockType BlockType
+				desc      string
+			}{blockType: BlockType(0), desc: "Unknown"},
+			wantErr: true,
+		},
+		{
+			name: "New1",
+			args: struct {
+				blockType BlockType
+				desc      string
+			}{blockType: New1BlockType, desc: "New1"},
+			wantErr: false,
+		},
+		{
+			name: "New2",
+			args: struct {
+				blockType BlockType
+				desc      string
+			}{blockType: New2BlockType, desc: "New2"},
+			wantErr: false,
+		},
+		{
+			name: "invalid",
+			args: struct {
+				blockType BlockType
+				desc      string
+			}{blockType: BlockType(12), desc: "12"},
+			wantErr: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if err := RegistryBlockType(tt.args.blockType, tt.args.desc); (err != nil) != tt.wantErr {
+				t.Errorf("RegistBlockType() error = %v, wantErr %v", err, tt.wantErr)
+			} else {
+				if tt.args.blockType.String() != tt.args.desc {
+					t.Errorf("RegistBlockType() string :%s not equal to desc:%s", tt.args.blockType.String(), tt.args.desc)
+				}
+			}
+		})
+	}
+}