@@ -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,33 @@ 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 )
128
+ w .taskQueue .release ()
121
129
}
122
130
} else if task != nil {
123
- w .taskQueue <- task
131
+ if err := w .taskQueue .add (ctx , task ); err != nil {
132
+ if ! errors .Is (err , context .Canceled ) {
133
+ w .logger .ErrorContext (ctx , "error adding task to queue" , "error" , err )
134
+ w .taskQueue .release ()
135
+ }
136
+ }
124
137
continue // check for new tasks right away
138
+ } else {
139
+ // Did not use the reserved slot, release
140
+ w .taskQueue .release ()
125
141
}
126
142
143
+ // Optionally wait between unsuccessful polling attempts
127
144
if w .options .PollingInterval > 0 {
128
145
select {
129
146
case <- ticker .C :
@@ -135,40 +152,28 @@ func (w *Worker[Task, TaskResult]) poller(ctx context.Context) {
135
152
}
136
153
137
154
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
155
var wg sync.WaitGroup
145
156
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
-
157
+ for t := range w .taskQueue .tasks {
152
158
wg .Add (1 )
153
159
154
160
t := t
155
161
go func () {
162
+ defer w .taskQueue .release ()
156
163
defer wg .Done ()
157
164
158
165
// Create new context to allow tasks to complete when root context is canceled
159
166
taskCtx := context .Background ()
160
167
if err := w .handle (taskCtx , t ); err != nil {
161
168
w .logger .ErrorContext (taskCtx , "error handling task" , "error" , err )
162
169
}
163
-
164
- if sem != nil {
165
- <- sem
166
- }
167
170
}()
168
171
}
169
172
173
+ // Wait for all pending tasks to finish
170
174
wg .Wait ()
171
175
176
+ // Then notify anyone waiting for this that the dispatcher is done.
172
177
w .dispatcherDone <- struct {}{}
173
178
}
174
179
0 commit comments