worker depends on launcher (now correctly Tier 2) and logz (Tier 1), placing it at Tier 3. The previous docs cited launcher as Tier 1 and logz as Tier 0, both of which were wrong.
2.1 KiB
ADR-003: Channel-Based Buffered Task Queue
Status: Accepted Date: 2026-03-18
Context
A worker pool requires a mechanism to hand off work from callers to goroutines.
Common options include a mutex-protected slice, a ring buffer, or a Go channel.
The pool must support multiple concurrent producers (callers of Dispatch) and
multiple concurrent consumers (worker goroutines), while providing a simple
backpressure signal when capacity is exhausted.
Decision
The task queue is a buffered chan Task with capacity Config.BufferSize (env
WORKER_BUFFER_SIZE, default 100). All worker goroutines receive from the same
channel using for task := range w.taskQueue. Producers call Dispatch which
uses a non-blocking select with a default branch:
select {
case w.taskQueue <- task:
return true
default:
// queue full — log and return false
return false
}
Dispatch returns bool: true if the task was enqueued, false if the queue
was full. The caller decides what to do with a rejected task (retry, log, discard).
Closing the channel in OnStop is the drain signal: range over a closed channel
drains buffered items and then exits naturally, so no separate "stop" message is
needed.
Consequences
- The channel scheduler distributes tasks across all
PoolSizegoroutines without any additional synchronisation code. - Backpressure is explicit: a full queue returns
falserather than blocking the caller or growing unboundedly. Callers that must not drop tasks should implement retry logic at their layer. - Channel capacity is fixed at construction time. There is no dynamic resizing; if
workload consistently fills the buffer,
BufferSizeorPoolSizemust be tuned in config. - Closing the channel is a one-way signal: once
OnStopcloses it,Dispatchmust not be called again. This is safe in practice becauselauncherensuresOnStopis only called after the application has stopped dispatching work, but there is no runtime guard against misuse. - The
for rangepattern requires no sentinel values and is idiomatic Go for fan-out worker pools.