-
Notifications
You must be signed in to change notification settings - Fork 423
/
slot_chain.go
200 lines (180 loc) · 5.49 KB
/
slot_chain.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
package base
import (
"sync"
"github.com/alibaba/sentinel-golang/logging"
"github.com/alibaba/sentinel-golang/util"
"github.com/pkg/errors"
)
// StatPrepareSlot is responsible for some preparation before statistic
// For example: init structure and so on
type StatPrepareSlot interface {
// Prepare function do some initialization
// Such as: init statistic structure、node and etc
// The result of preparing would store in EntryContext
// All StatPrepareSlots execute in sequence
// Prepare function should not throw panic.
Prepare(ctx *EntryContext)
}
// RuleCheckSlot is rule based checking strategy
// All checking rule must implement this interface.
type RuleCheckSlot interface {
// Check function do some validation
// It can break off the slot pipeline
// Each TokenResult will return check result
// The upper logic will control pipeline according to SlotResult.
Check(ctx *EntryContext) *TokenResult
}
// StatSlot is responsible for counting all custom biz metrics.
// StatSlot would not handle any panic, and pass up all panic to slot chain
type StatSlot interface {
// OnEntryPass function will be invoked when StatPrepareSlots and RuleCheckSlots execute pass
// StatSlots will do some statistic logic, such as QPS、log、etc
OnEntryPassed(ctx *EntryContext)
// OnEntryBlocked function will be invoked when StatPrepareSlots and RuleCheckSlots fail to execute
// It may be inbound flow control or outbound cir
// StatSlots will do some statistic logic, such as QPS、log、etc
// blockError introduce the block detail
OnEntryBlocked(ctx *EntryContext, blockError *BlockError)
// OnCompleted function will be invoked when chain exits.
// The semantics of OnCompleted is the entry passed and completed
// Note: blocked entry will not call this function
OnCompleted(ctx *EntryContext)
}
// SlotChain hold all system slots and customized slot.
// SlotChain support plug-in slots developed by developer.
type SlotChain struct {
statPres []StatPrepareSlot
ruleChecks []RuleCheckSlot
stats []StatSlot
// EntryContext Pool, used for reuse EntryContext object
ctxPool sync.Pool
}
func NewSlotChain() *SlotChain {
return &SlotChain{
statPres: make([]StatPrepareSlot, 0, 5),
ruleChecks: make([]RuleCheckSlot, 0, 5),
stats: make([]StatSlot, 0, 5),
ctxPool: sync.Pool{
New: func() interface{} {
ctx := NewEmptyEntryContext()
ctx.RuleCheckResult = NewTokenResultPass()
ctx.Data = make(map[interface{}]interface{})
ctx.Input = &SentinelInput{
AcquireCount: 1,
Flag: 0,
Args: make([]interface{}, 0),
Attachments: make(map[interface{}]interface{}),
}
return ctx
},
},
}
}
// Get a EntryContext from EntryContext ctxPool, if ctxPool doesn't have enough EntryContext then new one.
func (sc *SlotChain) GetPooledContext() *EntryContext {
ctx := sc.ctxPool.Get().(*EntryContext)
ctx.startTime = util.CurrentTimeMillis()
return ctx
}
func (sc *SlotChain) RefurbishContext(c *EntryContext) {
if c != nil {
c.Reset()
sc.ctxPool.Put(c)
}
}
func (sc *SlotChain) AddStatPrepareSlotFirst(s StatPrepareSlot) {
ns := make([]StatPrepareSlot, 0, len(sc.statPres)+1)
// add to first
ns = append(ns, s)
sc.statPres = append(ns, sc.statPres...)
}
func (sc *SlotChain) AddStatPrepareSlotLast(s StatPrepareSlot) {
sc.statPres = append(sc.statPres, s)
}
func (sc *SlotChain) AddRuleCheckSlotFirst(s RuleCheckSlot) {
ns := make([]RuleCheckSlot, 0, len(sc.ruleChecks)+1)
ns = append(ns, s)
sc.ruleChecks = append(ns, sc.ruleChecks...)
}
func (sc *SlotChain) AddRuleCheckSlotLast(s RuleCheckSlot) {
sc.ruleChecks = append(sc.ruleChecks, s)
}
func (sc *SlotChain) AddStatSlotFirst(s StatSlot) {
ns := make([]StatSlot, 0, len(sc.stats)+1)
ns = append(ns, s)
sc.stats = append(ns, sc.stats...)
}
func (sc *SlotChain) AddStatSlotLast(s StatSlot) {
sc.stats = append(sc.stats, s)
}
// The entrance of slot chain
// Return the TokenResult and nil if internal panic.
func (sc *SlotChain) Entry(ctx *EntryContext) *TokenResult {
// This should not happen, unless there are errors existing in Sentinel internal.
// If happened, need to add TokenResult in EntryContext
defer func() {
if err := recover(); err != nil {
logging.Panicf("Sentinel internal panic in SlotChain, err: %+v", err)
ctx.SetError(errors.Errorf("%+v", err))
return
}
}()
// execute prepare slot
sps := sc.statPres
if len(sps) > 0 {
for _, s := range sps {
s.Prepare(ctx)
}
}
// execute rule based checking slot
rcs := sc.ruleChecks
var ruleCheckRet *TokenResult
if len(rcs) > 0 {
for _, s := range rcs {
sr := s.Check(ctx)
if sr == nil {
// nil equals to check pass
continue
}
// check slot result
if sr.IsBlocked() {
ruleCheckRet = sr
break
}
}
}
if ruleCheckRet == nil {
ctx.RuleCheckResult.ResetToPass()
} else {
ctx.RuleCheckResult = 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.
if !ruleCheckRet.IsBlocked() {
s.OnEntryPassed(ctx)
} else {
// The block error should not be nil.
s.OnEntryBlocked(ctx, ruleCheckRet.blockErr)
}
}
}
return ruleCheckRet
}
func (sc *SlotChain) exit(ctx *EntryContext) {
if ctx == nil || ctx.Entry() == nil {
logging.Errorf("nil ctx or nil associated entry")
return
}
// The OnCompleted is called only when entry passed
if ctx.IsBlocked() {
return
}
for _, s := range sc.stats {
s.OnCompleted(ctx)
}
// relieve the context here
}