task-queue is a small Go-based background job queue built around Redis. It currently supports:
- immediate tasks
- one-off scheduled tasks
- cron-style recurring tasks
- worker execution with retries and acknowledgements
- a scheduler loop for promoting due tasks
- a reaper loop for timed-out task inspection
This repository is still evolving and should be treated as a learning project rather than a production-ready queue.
The latest architecture diagram lives in arch.svg.
The current runtime is organized like this:
- A client submits a task request to
cmd/producer. - The producer enriches the request with task metadata and writes it through the Redis-backed broker.
- Redis stores each task as a hash and tracks task IDs across queue and sorted-set structures.
cmd/workerstarts a worker pool and an embedded scheduler loop.- Workers block on the pending queue, move tasks into processing, execute registered handlers, then
AckorNack. - The scheduler polls scheduled tasks and requeues due one-off and cron tasks into the pending queue.
Due scheduled and cron tasks are inserted with
RPUSHso they are consumed ahead of the normal FIFO backlog. cmd/reaperpolls for expired task timeouts. Recovery and cleanup are still incomplete.
Task handlers are now compiled to WebAssembly and executed by workers through Wazero.
-
cmd/producerAccepts CLI flags, builds a task request, and either enqueues or schedules the task. -
cmd/workerStarts the worker pool, registers task handlers, and runs the scheduler loop in the same process. -
cmd/reaperStarts the timeout reaper loop.
-
pkg/taskCore task model, stages, kinds, and Redis serialization helpers. -
pkg/producerCreates tasks from request payloads and sends them to the broker. -
pkg/brokerBroker interface plus the Redis implementation for enqueue, dequeue, ack/nack, scheduling, and timeout lookups. -
pkg/workerWorker pool, task processing loop, retry handling, and handler dispatch. -
pkg/registryRegisters task source code, compiles it to WASM, stores compiled modules in-memory, and lazy-loads persisted.wasmfiles. -
pkg/wasmCompilerLanguage-aware compiler adapters used by the registry to build task functions into WASM artifacts (currently TinyGo for Go source). -
pkg/schedulerPolls the scheduled set and promotes due tasks into the pending queue. -
pkg/reaperPolls the timeout set for expired tasks. The actual timeout recovery path is not finished yet. -
pkg/configDefault queue names and worker/task settings.
Each task is stored as a Redis hash keyed as task_metadata:<taskId>. Redis also tracks task IDs in:
task_queue:pendingtask_queue:processingtask_queue:finishedtask_queue:dlqtask_set:scheduledtask_set:timeout
Pending queue behavior:
- Immediate tasks use
LPUSHand workers consume withBLMOVE ... RIGHT LEFT, which makes the default pending flow FIFO. - Due scheduled and cron tasks use
RPUSH, so they land on the side workers pop first and therefore run with higher priority than the existing pending backlog.
Current task kinds:
0: immediate1: scheduled2: cron
Current task stages:
pendingin_progresscompletedfailed
The worker now executes task logic from WASM modules instead of calling in-process Go handler functions.
Current task function contract:
- export
alloc(size uint32) uint32 - export
execute(ptr uint32, length uint32) uint64 - export start function
_initialize
Current logging behavior:
- command, broker, scheduler, reaper, registry, compiler, and worker events emit structured logs
- wasm
stdoutandstderrare captured per task execution in the worker - wasm logs can be mirrored to the terminal and optionally persisted to disk
Execution flow:
- Worker fetches task payload from Redis.
- Worker looks up task module in the registry.
- If needed, registry loads
<taskName>.wasmfrom the wasm directory and compiles it with Wazero. - Worker instantiates the module with WASI enabled.
- Worker calls
alloc, writes payload bytes into module memory, then callsexecute. - Worker reads
(resultPtr,resultLen)packed in the returneduint64.
Registration flow:
- Worker process registers tasks via
registry.Registerwith source code text and language. - Registry writes temporary source to disk.
- Registry compiles source to WASM (TinyGo for language
go). - Registry stores the resulting module in memory and on disk for reuse.
Notes:
- The sample task registration lives in
cmd/workerand registerstask_1from an inline Go source string. - Current code stores task wasm files in
/wasm_task_functions.
-
Producer: Creates a task with default metadata such as retry count and timeout, then either pushes it to the pending queue or stores it in the scheduled set.
-
Worker pool: Uses a Redis blocking move from pending to processing, executes task logic via a WASM module (
alloc+execute), and then acknowledges success or requeues/fails the task on error. -
Scheduler: Polls the scheduled set, promotes due tasks, and for cron tasks computes the next run before re-adding them to the scheduled set. Promoted scheduled/cron tasks are
RPUSHed so they can preempt the normal pending queue backlog. -
Reaper: Reads expired task IDs from the timeout set and logs them. Moving timed-out tasks to the DLQ is still a TODO in code.
- Go
1.25.6 - Redis
7.x - TinyGo (required to compile Go task source into WASM during task registration)
- Binaryen (required by TinyGo toolchain for WASM optimization and linking)
- Docker and Docker Compose if you want a local Redis container
docker compose up -d redisCreate .env from .env.example:
REDIS_URL=localhost:6379
TASK_QUEUE_LOG_LEVEL=info
TASK_QUEUE_LOG_FORMAT=pretty
TASK_QUEUE_SAVE_WASM_LOGS=false
TASK_QUEUE_MIRROR_WASM_LOGS=true
TASK_QUEUE_WASM_LOG_DIR=runtime-logs/wasmNotes:
TASK_QUEUE_LOG_FORMAT=prettyis the readable default for local development.- Set
TASK_QUEUE_LOG_FORMAT=jsonif you want logs that are easier to ship to a collector. - Set
TASK_QUEUE_SAVE_WASM_LOGS=trueto save wasm task logs on disk. - Saved wasm logs are written under
TASK_QUEUE_WASM_LOG_DIR/<task-name>/<task-id>.log.
go run ./cmd/worker -registry ./registryNotes:
- This process also starts the scheduler loop.
- The worker expects a registry directory path and compiles supported task source files there into WASM at startup.
In another terminal:
go run ./cmd/reaperImmediate task:
go run ./cmd/producer -task task_1 -payload "{\"name\":\"world\"}" -kind 0Scheduled task:
go run ./cmd/producer -task task_1 -payload "{\"name\":\"later\"}" -kind 1 -scheduled_at 2026-03-29T12:00:00+05:30Cron task:
go run ./cmd/producer -task task_1 -payload "{\"name\":\"repeat\"}" -kind 2 -cron "*/10 * * * * *"Notes:
- Scheduled tasks require an RFC3339 timestamp in the future.
- Cron tasks use the
robfig/cronparser with seconds enabled, so the expression is six-field. - When wasm log persistence is enabled, each task completion log includes the saved wasm log path.
Implemented now:
- Redis-backed enqueue/dequeue flow
- task retries via
Nack - finished queue on
Ack - scheduled and cron task promotion
- timeout tracking via a Redis sorted set
- WASM task registration and execution via TinyGo + Wazero
Coming soon:
- timed-out task recovery and DLQ cleanup in the reaper
- stronger validation and broker-side atomicity improvements
- task result persistence and richer wasm task tooling
This project is licensed under the MIT License. See LICENSE.