6
6
"runtime"
7
7
"strings"
8
8
"sync"
9
- "sync/atomic"
10
9
"time"
11
10
)
12
11
@@ -22,57 +21,29 @@ type workerPool struct {
22
21
23
22
// Function for serving server connections.
24
23
// It must leave c unclosed.
25
- ready workerChanStack
26
24
WorkerFunc ServeHandler
27
25
28
26
stopCh chan struct {}
29
27
30
28
connState func (net.Conn , ConnState )
31
29
30
+ ready []* workerChan
31
+
32
32
MaxWorkersCount int
33
33
34
34
MaxIdleWorkerDuration time.Duration
35
35
36
- workersCount int32
36
+ workersCount int
37
37
38
- mustStop atomic. Bool
38
+ lock sync. Mutex
39
39
40
40
LogAllErrors bool
41
+ mustStop bool
41
42
}
42
43
43
44
type workerChan struct {
44
- next * workerChan
45
-
46
- ch chan net.Conn
47
-
48
- lastUseTime int64
49
- }
50
-
51
- type workerChanStack struct {
52
- head atomic.Pointer [workerChan ]
53
- }
54
-
55
- func (s * workerChanStack ) push (ch * workerChan ) {
56
- for {
57
- oldHead := s .head .Load ()
58
- ch .next = oldHead
59
- if s .head .CompareAndSwap (oldHead , ch ) {
60
- break
61
- }
62
- }
63
- }
64
-
65
- func (s * workerChanStack ) pop () * workerChan {
66
- for {
67
- oldHead := s .head .Load ()
68
- if oldHead == nil {
69
- return nil
70
- }
71
-
72
- if s .head .CompareAndSwap (oldHead , oldHead .next ) {
73
- return oldHead
74
- }
75
- }
45
+ lastUseTime time.Time
46
+ ch chan net.Conn
76
47
}
77
48
78
49
func (wp * workerPool ) Start () {
@@ -87,8 +58,9 @@ func (wp *workerPool) Start() {
87
58
}
88
59
}
89
60
go func () {
61
+ var scratch []* workerChan
90
62
for {
91
- wp .clean ()
63
+ wp .clean (& scratch )
92
64
select {
93
65
case <- stopCh :
94
66
return
@@ -109,15 +81,15 @@ func (wp *workerPool) Stop() {
109
81
// Stop all the workers waiting for incoming connections.
110
82
// Do not wait for busy workers - they will stop after
111
83
// serving the connection and noticing wp.mustStop = true.
112
-
113
- for {
114
- ch := wp .ready .pop ()
115
- if ch == nil {
116
- break
117
- }
118
- ch .ch <- nil
84
+ wp .lock .Lock ()
85
+ ready := wp .ready
86
+ for i := range ready {
87
+ ready [i ].ch <- nil
88
+ ready [i ] = nil
119
89
}
120
- wp .mustStop .Store (true )
90
+ wp .ready = ready [:0 ]
91
+ wp .mustStop = true
92
+ wp .lock .Unlock ()
121
93
}
122
94
123
95
func (wp * workerPool ) getMaxIdleWorkerDuration () time.Duration {
@@ -127,22 +99,50 @@ func (wp *workerPool) getMaxIdleWorkerDuration() time.Duration {
127
99
return wp .MaxIdleWorkerDuration
128
100
}
129
101
130
- func (wp * workerPool ) clean () {
102
+ func (wp * workerPool ) clean (scratch * [] * workerChan ) {
131
103
maxIdleWorkerDuration := wp .getMaxIdleWorkerDuration ()
132
- criticalTime := time .Now ().Add (- maxIdleWorkerDuration ).UnixNano ()
133
104
134
- for {
135
- current := wp .ready .head .Load ()
136
- if current == nil || atomic .LoadInt64 (& current .lastUseTime ) >= criticalTime {
137
- break
138
- }
105
+ // Clean least recently used workers if they didn't serve connections
106
+ // for more than maxIdleWorkerDuration.
107
+ criticalTime := time .Now ().Add (- maxIdleWorkerDuration )
108
+
109
+ wp .lock .Lock ()
110
+ ready := wp .ready
111
+ n := len (ready )
139
112
140
- next := current .next
141
- if wp .ready .head .CompareAndSwap (current , next ) {
142
- current .ch <- nil
143
- wp .workerChanPool .Put (current )
113
+ // Use binary-search algorithm to find out the index of the least recently worker which can be cleaned up.
114
+ l , r := 0 , n - 1
115
+ for l <= r {
116
+ mid := (l + r ) / 2
117
+ if criticalTime .After (wp .ready [mid ].lastUseTime ) {
118
+ l = mid + 1
119
+ } else {
120
+ r = mid - 1
144
121
}
145
122
}
123
+ i := r
124
+ if i == - 1 {
125
+ wp .lock .Unlock ()
126
+ return
127
+ }
128
+
129
+ * scratch = append ((* scratch )[:0 ], ready [:i + 1 ]... )
130
+ m := copy (ready , ready [i + 1 :])
131
+ for i = m ; i < n ; i ++ {
132
+ ready [i ] = nil
133
+ }
134
+ wp .ready = ready [:m ]
135
+ wp .lock .Unlock ()
136
+
137
+ // Notify obsolete workers to stop.
138
+ // This notification must be outside the wp.lock, since ch.ch
139
+ // may be blocking and may consume a lot of time if many workers
140
+ // are located on non-local CPUs.
141
+ tmp := * scratch
142
+ for i := range tmp {
143
+ tmp [i ].ch <- nil
144
+ tmp [i ] = nil
145
+ }
146
146
}
147
147
148
148
func (wp * workerPool ) Serve (c net.Conn ) bool {
@@ -169,32 +169,47 @@ var workerChanCap = func() int {
169
169
}()
170
170
171
171
func (wp * workerPool ) getCh () * workerChan {
172
- for {
173
- ch := wp .ready .pop ()
174
- if ch != nil {
175
- return ch
172
+ var ch * workerChan
173
+ createWorker := false
174
+
175
+ wp .lock .Lock ()
176
+ ready := wp .ready
177
+ n := len (ready ) - 1
178
+ if n < 0 {
179
+ if wp .workersCount < wp .MaxWorkersCount {
180
+ createWorker = true
181
+ wp .workersCount ++
176
182
}
183
+ } else {
184
+ ch = ready [n ]
185
+ ready [n ] = nil
186
+ wp .ready = ready [:n ]
187
+ }
188
+ wp .lock .Unlock ()
177
189
178
- currentWorkers := atomic .LoadInt32 (& wp .workersCount )
179
- if int (currentWorkers ) < wp .MaxWorkersCount {
180
- if atomic .CompareAndSwapInt32 (& wp .workersCount , currentWorkers , currentWorkers + 1 ) {
181
- ch = wp .workerChanPool .Get ().(* workerChan )
182
- go wp .workerFunc (ch )
183
- return ch
184
- }
185
- } else {
186
- break
190
+ if ch == nil {
191
+ if ! createWorker {
192
+ return nil
187
193
}
194
+ vch := wp .workerChanPool .Get ()
195
+ ch = vch .(* workerChan )
196
+ go func () {
197
+ wp .workerFunc (ch )
198
+ wp .workerChanPool .Put (vch )
199
+ }()
188
200
}
189
- return nil
201
+ return ch
190
202
}
191
203
192
204
func (wp * workerPool ) release (ch * workerChan ) bool {
193
- atomic .StoreInt64 (& ch .lastUseTime , time .Now ().UnixNano ())
194
- if wp .mustStop .Load () {
205
+ ch .lastUseTime = time .Now ()
206
+ wp .lock .Lock ()
207
+ if wp .mustStop {
208
+ wp .lock .Unlock ()
195
209
return false
196
210
}
197
- wp .ready .push (ch )
211
+ wp .ready = append (wp .ready , ch )
212
+ wp .lock .Unlock ()
198
213
return true
199
214
}
200
215
@@ -230,5 +245,7 @@ func (wp *workerPool) workerFunc(ch *workerChan) {
230
245
}
231
246
}
232
247
233
- atomic .AddInt32 (& wp .workersCount , - 1 )
248
+ wp .lock .Lock ()
249
+ wp .workersCount --
250
+ wp .lock .Unlock ()
234
251
}
0 commit comments