Packages

SuperWorker is a powerful Elixir library for working with supervisors and background jobs. It provides a much simpler approach than traditional supervisors. This library is currently under development and is unstable, so it is not recommended for production use.

Current section

Files

Jump to
super_worker CHANGELOG.md
Raw

CHANGELOG.md

# Changelog
All notable changes to this project are documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [0.10.0]
### Added
- **FunctionChain integration** — run `SuperWorker.FunctionChain` through the
pool and the supervisor:
- `SuperWorker.Pool.FunctionChain` — a ready-made
`SuperWorker.Pool.Worker` (`worker:` option) that runs a chain for every
pool job, with per-job `run_opts` (`:arg_overrides`/`:context`) and an
`:on_error` policy (`:error` fails the job, `:retry` hands it to the
pool's retry budget);
- `SuperWorker.Supervisor.FunctionChain.chain_node_fun/2` — run a chain as
one node of a supervisor process chain (`add_chain_worker/4`);
- `SuperWorker.Supervisor.FunctionChain.job_loop/2` — a standalone/group
worker loop that runs every received message through a chain, with a
request/response envelope (`{:run, ref, job, from}`) and an optional
`:on_result` callback.
- **Pool restart tuning**`:max_restarts` / `:max_seconds` options shared
by the pool supervisor and each partition supervisor (defaults: 10/10).
### Fixed
- **Pool**: an expected, final failure (`{:error, reason, state}` from a
`SuperWorker.Pool.Worker`) now dead-letters like exhausted retries and
worker crashes — `:on_failure` is the single source of truth for "this job
did not complete", as documented in the `SuperWorker.Pool.Worker`
behaviour. Previously such failures were only delivered to the caller.
- **Pool docs**: documented the queue owner (`SuperWorker.Pool.Partition`
GenServer), retry ordering (retried jobs re-enter the **front** of their
partition queue), `run/3` / `await/2` timeout options, the `:on_result`
payload shape, and the `:circuit_open` vs `:on_failure` boundary.
## [0.8.0]
### Added
- ** `SuperWorker.Pool`** - Function chain for easy work with pipe of functions.
## [0.8.0]
### Added
- **`SuperWorker.Pool`** — a simple, partitioned process pool:
- start with a bare function, an MFA (`task:`), or a module implementing the
`SuperWorker.Pool.Worker` behaviour (`worker:` — stateful workers with
`init/1` + `handle_job/2` returning `{:ok, ...}` / `{:error, ...}` /
`{:retry, ...}`);
- three submission styles: `run/2,3` (sync), `run_async/2` + `await/2`
(Task-like ref), `cast/2` (fire-and-forget with optional `:on_result`);
- partitioned for scalability: jobs are routed to one of `:partitions`
(default `System.schedulers_online()`) independent queue + worker groups
via round robin or `:erlang.phash2` hash (`:routing` option);
- bounded per-partition queue (`:max_queue`) — overflow callers immediately
get `{:error, :overloaded}`;
- configurable retries (`:max_attempts`) with fixed/exponential backoff and
jitter, rescheduled via `Process.send_after` (never `Process.sleep` — a
retrying job never holds its worker);
- crash isolation: each worker is a supervised process; a crash restarts
only that worker, its in-flight job is requeued **once** (charged to the
retry budget) so poison-pill jobs cannot crash-loop;
- `:on_failure` dead-letter callback after retries are exhausted (no silent
job loss);
- Plug-style `SuperWorker.Pool.Middleware` pipeline with built-in
`Telemetry` (`[:super_worker, :pool, :job, :start/:stop/:exception/:retry/...]`)
and `CircuitBreaker` (per-partition, trips after N consecutive failures
and rejects submissions with `{:error, :circuit_open}`);
- `size < partitions` rounds workers up (`ceil(size/partitions)`, min 1) so
no partition is dead; `info/1` reports live per-partition counters.
## [0.7.0]
### Fixed
- **Chain data loss under backpressure**: `loop_send/3` (queue-full wait loop) consumed `{:new_data, ...}` messages arriving while a worker was blocked and discarded them as unknown messages. Non-matching messages now stay in the mailbox and are processed in order once the queue drains.
- **Chain worker queue wedge**: `forward_data/6` queued every forwarded message even when no downstream worker existed (the finished callback ran instead), so the last worker's queue accumulated entries nothing would ever confirm and the chain froze after `queue_length` messages. The queue entry is now dropped when no downstream worker consumed the message.
- **Shutdown did not kill workers**: `Looper.shutdown/2` compared a partition *pid* against the partition *id*, so the kill branch never matched; it also dereferenced `worker.pid`, a field the `Worker` struct does not have. Shutdown now resolves live pids from the registry and compares against the partition hash order, as documented in `stop/3`.
- **`queue_length` chain option was ignored**: chain workers were always spawned with the default queue length (50) regardless of the chain's `queue_length` config; workers now inherit it at spawn time.
- **`Supervisor` partition cache never worked**: `handle_call({:query_target_partition, _})` discarded the result of `put_in_cache/3`, so every API call paid a fresh lookup. The cache is now persisted (and cleared on partition restarts, as before).
- **`Chain.restart_all_workers/1` logged a spurious failure** on every successful respawn because `do_spawn_worker/2` returns the worker struct, not `{:ok, _}`; success/failure classification is now correct.
- **`send_to_group_random/3,4` replied with the caller's data** instead of `:ok` because `Group.send_message/3` returned the result of `send/2`; it now returns `:ok`.
- **Documented pid `:link` was rejected**: `Validator` only accepted booleans for `:link`, making the pid-link feature unreachable through `start_with_config/1`; pids are now accepted (the ConfigLoader parser already allowed them).
- Removed dead code: `Worker.validate_option/2` clauses for `:order` were unreachable because `:order` is filtered out before validation.
### Changed
- Test suite expanded to cover failure paths across `Chain`, `Group`, `Looper`, `Supervisor`, `Db`, `Parser`, `Bootstrap`, `Validator`, `Worker`, `CircuitBreaker` and `Error` (total coverage 91% → 97%): group send/broadcast APIs, shutdown kills, restart-skip on normal exits, spawn-failure handling, queue-full backpressure, stale-registry cleanup and option-validation errors.
## [0.6.0]
### Added
- Introspection API on `SuperWorker.Supervisor`:
- `running_supervisors/0` — discover live supervisors on the node;
- `supervisor_info/1,2` — partition health (liveness, message queue depth) plus group/chain/worker counts;
- `list_groups/1,2`, `list_chains/1,2`, `list_standalone_workers/1,2`.
- `SuperWorker.Supervisor.Utils.safe_call/1,3` — invoke user functions without letting exceptions escape.
- Fault tolerance: crashed partitions are detected via monitors and restarted individually by the supervisor master; partitions monitor the master so nothing outlives the supervisor.
- `SuperWorker.Log.debug/1` now compiles away completely when disabled, including its arguments.
### Changed
- `SuperWorker.CircuitBreaker.call/2` executes the protected function in the caller process instead of inside the breaker GenServer, so concurrent calls are not serialized and slow calls cannot block state queries. Half-open probes are limited by `half_open_max_calls`; the call that trips the threshold returns the real error.
- Chain finished-callback failures return the actual reason (`{:error, {kind, reason}}`) instead of a generic `{:error, :callback_failed}`.
- `SuperWorker.TermStorage.get/1` distinguishes a stored `nil` from a missing key; `get_all/0` returns plain `{key, value}` pairs without the internal module prefix.
- `stop/3` with any shutdown type other than `:kill` falls back to a brutal kill instead of crashing partitions (graceful shutdown is not implemented yet).
### Fixed
- A crashing partition no longer takes down the whole supervisor.
- `Chain.restart_all_workers/1` crashed on `worker.pid` (field does not exist); it now resolves pids from the registry and respawns every node.
- `Chain.restart_worker/2` skipped respawning when invoked from the crash handler because the ref row was already cleaned up.
- `restart_group_worker` API always replied `:ok`, discarding failures.
- Invalid GenServer child specs crashed callers (`convert_gen_server_specs/2` matched `{:ok, _}` unconditionally).
- GenServer start failures escaped as uncaught throws and could crash partition processes; they are converted to `{:error, :spawn_failed}`.
- Standalone workers stored the partition number as their supervisor id; `get_my_supervisor/0` now returns the supervisor id.
- Typo in error reason: `:chan_not_found``:chain_not_found`.
- Undefined variables inside debug log closures surfaced when compiling with `debug_log: true`.
### Documentation
- README sections for fault tolerance, introspection and utilities.
- Moduledocs for `Looper`, `Partition`, `TermStorage` and `Log`; truthful `stop/3` docs; corrected specs.
## [0.5.0]
Initial documented release: groups, chains and standalone workers under one dynamic supervisor with per-parent restart strategies.