Files
worker/docs/adr/ADR-003-channel-task-queue.md
Rene Nochebuena 631c98396e docs(worker): correct tier from 2 to 3 and fix dependency tier refs
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.
2026-03-19 13:13:41 +00:00

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.