-
Notifications
You must be signed in to change notification settings - Fork 39
Expand file tree
/
Copy pathworker.go
More file actions
116 lines (107 loc) · 3.06 KB
/
Copy pathworker.go
File metadata and controls
116 lines (107 loc) · 3.06 KB
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
package agilepool
import (
"sync/atomic"
"time"
)
type worker struct {
pool *Pool
lastActiveAt time.Time
}
func newWorker(p *Pool) *worker {
w := &worker{
pool: p,
}
return w
}
func (w *worker) run(task Task) {
w.lastActiveAt = time.Now()
if task != nil {
w.runTask(task)
}
// NOTE: workerPool.Put(w) is intentionally called only on the "terminal"
// exit paths (queue closed / nil task) below, NOT in a defer. Putting w to
// the sync.Pool when the worker has just been added to idleWorks would
// place the same *worker pointer in two containers at once; subsequent
// Submits could then concurrently spawn two goroutines on the same
// *worker via Pop and workerPool.Get respectively, causing a data race
// on w.lastActiveAt and phantom duplicates in idleWorks.
loop:
for {
select {
case task, ok := <-w.pool.taskQueue:
if !ok {
w.pool.logger.Println("taskQueue closed,exiting")
w.pool.addRunningWorkersNum(-1)
atomic.AddInt64(&w.pool.exitCount, 1)
w.pool.workerPool.Put(w)
return
}
if task == nil {
w.pool.logger.Println("nil task received, exiting")
w.pool.addRunningWorkersNum(-1)
atomic.AddInt64(&w.pool.exitCount, 1)
w.pool.workerPool.Put(w)
return
}
w.lastActiveAt = time.Now()
w.runTask(task)
default:
// Try the chunked buffer before the second channel check.
// Grab a batch of up to 8 tasks per lock acquisition to
// amortise the mutex overhead across multiple tasks and
// reduce contention with the submission path.
const batchSize = 8
var batch [batchSize]Task
n := w.pool.taskBuf.PopBatch(batch[:])
for i := 0; i < n; i++ {
w.lastActiveAt = time.Now()
w.runTask(batch[i])
}
if n > 0 {
continue
}
// Lock-free second check: catch tasks that arrived in the
// tiny window between the two select polls. Submit no longer
// holds p.lock, so serialisation via lock is unnecessary.
// If a task slips through both selects, the scaler will
// spawn workers within scalerPeriod (10ms) to pick it up.
select {
case task, ok := <-w.pool.taskQueue:
if !ok {
w.pool.logger.Println("taskQueue closed,exiting")
w.pool.addRunningWorkersNum(-1)
atomic.AddInt64(&w.pool.exitCount, 1)
w.pool.workerPool.Put(w)
return
}
if task == nil {
w.pool.logger.Println("nil task received, exiting")
w.pool.addRunningWorkersNum(-1)
atomic.AddInt64(&w.pool.exitCount, 1)
w.pool.workerPool.Put(w)
return
}
w.lastActiveAt = time.Now()
w.runTask(task)
default:
// Parking: no task found in second check, worker goes idle.
// Do NOT also put w in workerPool.sync.Pool — see the
// note at the top of run().
w.pool.addRunningWorkersNum(-1)
atomic.AddInt64(&w.pool.exitCount, 1)
w.pool.addToIdle(w)
break loop
}
}
}
}
func (w *worker) runTask(task Task) {
atomic.AddInt64(&w.pool.consumeCount, 1)
defer func() {
if p := recover(); p != nil {
w.pool.logger.Printf("worker exits from panic: %v\n%s\n", p, Stack(1))
}
}()
defer w.pool.done()
task.Process()
}