@@ -27,7 +27,7 @@ type Worker[Task, TaskResult any] struct {
27
27
28
28
tw TaskWorker [Task , TaskResult ]
29
29
30
- taskQueue chan * Task
30
+ taskQueue * workQueue [ Task ]
31
31
32
32
logger * slog.Logger
33
33
@@ -64,7 +64,7 @@ func NewWorker[Task, TaskResult any](
64
64
return & Worker [Task , TaskResult ]{
65
65
tw : tw ,
66
66
options : options ,
67
- taskQueue : make ( chan * Task ),
67
+ taskQueue : newWorkQueue [ Task ]( options . MaxParallelTasks ),
68
68
logger : b .Options ().Logger ,
69
69
dispatcherDone : make (chan struct {}, 1 ),
70
70
}
@@ -91,7 +91,7 @@ func (w *Worker[Task, TaskResult]) WaitForCompletion() error {
91
91
w .pollersWg .Wait ()
92
92
93
93
// Wait for tasks to finish
94
- close (w .taskQueue )
94
+ close (w .taskQueue . tasks )
95
95
<- w .dispatcherDone
96
96
97
97
return nil
@@ -114,16 +114,32 @@ func (w *Worker[Task, TaskResult]) poller(ctx context.Context) {
114
114
default :
115
115
}
116
116
117
+ // Reserve slot for work we might get. This blocks if there are no slots available.
118
+ if err := w .taskQueue .reserve (ctx ); err != nil {
119
+ if errors .Is (err , context .Canceled ) {
120
+ return
121
+ }
122
+ }
123
+
117
124
task , err := w .poll (ctx , 30 * time .Second )
118
125
if err != nil {
119
126
if ! errors .Is (err , context .Canceled ) {
120
127
w .logger .ErrorContext (ctx , "error polling task" , "error" , err )
121
128
}
122
129
} else if task != nil {
123
- w .taskQueue <- task
130
+ if err := w .taskQueue .add (ctx , task ); err != nil {
131
+ if ! errors .Is (err , context .Canceled ) {
132
+ w .logger .ErrorContext (ctx , "error adding task to queue" , "error" , err )
133
+ w .taskQueue .release ()
134
+ }
135
+ }
124
136
continue // check for new tasks right away
137
+ } else {
138
+ // Did not use the reserved slot, release
139
+ w .taskQueue .release ()
125
140
}
126
141
142
+ // Optionally wait between unsuccessful polling attempts
127
143
if w .options .PollingInterval > 0 {
128
144
select {
129
145
case <- ticker .C :
@@ -135,40 +151,28 @@ func (w *Worker[Task, TaskResult]) poller(ctx context.Context) {
135
151
}
136
152
137
153
func (w * Worker [Task , TaskResult ]) dispatcher () {
138
- var sem chan struct {}
139
-
140
- if w .options .MaxParallelTasks > 0 {
141
- sem = make (chan struct {}, w .options .MaxParallelTasks )
142
- }
143
-
144
154
var wg sync.WaitGroup
145
155
146
- for t := range w .taskQueue {
147
- // If limited max tasks, wait for a slot to open up
148
- if sem != nil {
149
- sem <- struct {}{}
150
- }
151
-
156
+ for t := range w .taskQueue .tasks {
152
157
wg .Add (1 )
153
158
154
159
t := t
155
160
go func () {
161
+ defer w .taskQueue .release ()
156
162
defer wg .Done ()
157
163
158
164
// Create new context to allow tasks to complete when root context is canceled
159
165
taskCtx := context .Background ()
160
166
if err := w .handle (taskCtx , t ); err != nil {
161
167
w .logger .ErrorContext (taskCtx , "error handling task" , "error" , err )
162
168
}
163
-
164
- if sem != nil {
165
- <- sem
166
- }
167
169
}()
168
170
}
169
171
172
+ // Wait for all pending tasks to finish
170
173
wg .Wait ()
171
174
175
+ // Then notify anyone waiting for this that the dispatcher is done.
172
176
w .dispatcherDone <- struct {}{}
173
177
}
174
178
0 commit comments