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.
This commit is contained in:
53
docs/adr/ADR-003-channel-task-queue.md
Normal file
53
docs/adr/ADR-003-channel-task-queue.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user