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.
54 lines
2.1 KiB
Markdown
54 lines
2.1 KiB
Markdown
# 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:
|
|
|
|
```go
|
|
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 `PoolSize` goroutines without
|
|
any additional synchronisation code.
|
|
- Backpressure is explicit: a full queue returns `false` rather 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, `BufferSize` or `PoolSize` must be tuned
|
|
in config.
|
|
- Closing the channel is a one-way signal: once `OnStop` closes it, `Dispatch` must
|
|
not be called again. This is safe in practice because `launcher` ensures `OnStop`
|
|
is only called after the application has stopped dispatching work, but there is no
|
|
runtime guard against misuse.
|
|
- The `for range` pattern requires no sentinel values and is idiomatic Go for
|
|
fan-out worker pools.
|