@@ -10,66 +10,123 @@ import (
10
10
// SizedGroup interface enforces constructor usage and doesn't allow direct creation of sizedGroup
11
11
type SizedGroup struct {
12
12
options
13
- wg sync.WaitGroup
14
- sema Locker
13
+ wg sync.WaitGroup
14
+ workers chan struct {}
15
+ scheduledJobs chan struct {}
16
+ jobQueue chan func (ctx context.Context )
17
+ workersMutex sync.Mutex
15
18
}
16
19
17
20
// NewSizedGroup makes wait group with limited size alive goroutines
18
21
func NewSizedGroup (size int , opts ... GroupOption ) * SizedGroup {
19
- res := SizedGroup {sema : NewSemaphore (size )}
22
+ if size < 0 {
23
+ size = 1
24
+ }
25
+ res := SizedGroup {workers : make (chan struct {}, size )}
20
26
res .options .ctx = context .Background ()
21
27
for _ , opt := range opts {
22
28
opt (& res .options )
23
29
}
30
+
31
+ // queue size either equal to number of workers or larger, otherwise does not make sense
32
+ queueSize := size
33
+ if res .tresholdDiscard > 0 {
34
+ queueSize += res .tresholdDiscard
35
+ }
36
+
37
+ res .jobQueue = make (chan func (ctx context.Context ), queueSize )
38
+ res .scheduledJobs = make (chan struct {}, queueSize )
24
39
return & res
25
40
}
26
41
27
42
// Go calls the given function in a new goroutine.
28
43
// Every call will be unblocked, but some goroutines may wait if semaphore locked.
29
44
func (g * SizedGroup ) Go (fn func (ctx context.Context )) {
30
- canceled := func () bool {
31
- select {
32
- case <- g .ctx .Done ():
33
- return true
34
- default :
35
- return false
36
- }
45
+ if g .canceled () {
46
+ return
37
47
}
38
48
39
- if canceled () {
49
+ g .wg .Add (1 )
50
+ if ! g .preLock {
51
+ go func () {
52
+ defer g .wg .Done ()
53
+ if g .canceled () {
54
+ return
55
+ }
56
+ g .scheduledJobs <- struct {}{}
57
+ fn (g .ctx )
58
+ <- g .scheduledJobs
59
+ }()
40
60
return
41
61
}
42
-
43
- if g .preLock {
44
- lockOk := g .sema .TryLock ()
45
- if ! lockOk && g .discardIfFull {
46
- // lock failed and discardIfFull is set, discard this goroutine
62
+
63
+ toRun := func (job func (ctx context.Context )) {
64
+ defer g .wg .Done ()
65
+ if g .canceled () {
47
66
return
48
67
}
49
- if ! lockOk && ! g .discardIfFull {
50
- g .sema .Lock () // make sure we have block until lock is acquired
51
- }
68
+ job (g .ctx )
69
+ <- g .scheduledJobs
52
70
}
53
71
54
- g .wg .Add (1 )
55
- go func () {
56
- defer g .wg .Done ()
57
-
58
- if canceled () {
59
- return
72
+ startWorkerIfNeeded := func () {
73
+ g .workersMutex .Lock ()
74
+ select {
75
+ case g .workers <- struct {}{}:
76
+ g .workersMutex .Unlock ()
77
+ go func () {
78
+ for {
79
+ select {
80
+ case job := <- g .jobQueue :
81
+ toRun (job )
82
+ default :
83
+ g .workersMutex .Lock ()
84
+ select {
85
+ case job := <- g .jobQueue :
86
+ g .workersMutex .Unlock ()
87
+ toRun (job )
88
+ continue
89
+ default :
90
+ <- g .workers
91
+ g .workersMutex .Unlock ()
92
+ }
93
+ return
94
+ }
95
+ }
96
+ }()
97
+ default :
98
+ g .workersMutex .Unlock ()
60
99
}
100
+ }
61
101
62
- if ! g .preLock {
63
- g .sema .Lock ()
102
+ if g .discardIfFull {
103
+ select {
104
+ case g .scheduledJobs <- struct {}{}:
105
+ g .jobQueue <- fn
106
+ startWorkerIfNeeded ()
107
+ default :
108
+ g .wg .Done ()
64
109
}
65
110
66
- fn (g .ctx )
67
- g .sema .Unlock ()
68
- }()
111
+ return
112
+ }
113
+
114
+ g .scheduledJobs <- struct {}{}
115
+ g .jobQueue <- fn
116
+ startWorkerIfNeeded ()
69
117
}
70
118
71
119
// Wait blocks until the SizedGroup counter is zero.
72
120
// See sync.WaitGroup documentation for more information.
73
121
func (g * SizedGroup ) Wait () {
74
122
g .wg .Wait ()
75
123
}
124
+
125
+ func (g * SizedGroup ) canceled () bool {
126
+ select {
127
+ case <- g .ctx .Done ():
128
+ return true
129
+ default :
130
+ return false
131
+ }
132
+ }
0 commit comments