Changes for version 0.002000 - 2026-04-26
- Architectural: unified single-reader socket ownership model
- One reader loop dispatches frames to inflight queue or active subscription by frame type and connection mode
- Pipelines, auto-pipeline, and pubsub all participate in the same FIFO via inflight entries; the _reading_responses flag is removed
- Every submission goes through a write gate so inflight registration order matches wire-write order
- Callback-driven subscriptions consume from an internal message queue rather than reading the socket directly; subscribe / psubscribe / ssubscribe route through _execute_command and the unified reader
- Architectural: structured-concurrency contract via Future::Selector
- New Future::Selector field (_tasks) owns every fire-and-forget background task on the client (reader, reconnect, autopipeline submits, subscription callback driver). Replaces the previous slot-per-task / ->retain pattern.
- Reader failure now propagates to any caller awaiting inside the scope. Previously, an unhandled exception in the reader left awaiting callers hanging while the client reported connected=1; awaits now fail with the typed error.
- User-initiated disconnect takes a distinct path from _reader_fatal: drains write-gate waiters with a typed Disconnected error and cancels any in-flight reconnect task, so commands suspended on the write gate unwind instead of staying parked.
- Adds Future::Selector 0.05 as a runtime dependency.
- Security: TLS identity enforced by default
- SSL_hostname and SSL_verifycn_name set when connecting by hostname. IP literals verify against IP SAN by default (fails if the cert has no IP SAN for the connected address). New verify_hostname option for opt-out when connecting by IP to a hostname-only cert.
- Security: failed handshake no longer leaves object connected
- connect() rolls back (reset + typed error) on AUTH/SELECT failure
- Password "0" is now sent correctly (was silently skipped by truthy guard)
- Correctness: blocking command deadlines
- BLMPOP/BZMPOP read timeout from position 0 (was last)
- Server timeout 0 means indefinite (no client-side deadline)
- WAIT, WAITAOF, XREAD BLOCK, XREADGROUP BLOCK all covered
- Correctness: subscription lifecycle
- _close (intentional) distinct from _fail_fatal (unrecoverable); iterator next() returns undef vs typed error accordingly
- Reconnect uses _pause_for_reconnect / _resume_after_reconnect verbs that preserve replay state; _resume_after_reconnect sets in_pubsub=1 before replay to mirror initial-subscribe timing
- Identity-guarded parent-slot clearing prevents stale _close from wiping a newer subscription
- Correctness: pool release and shutdown
- Double-release is a loud no-op (was silent double-push into idle)
- Shutdown flag rejects further acquires; active releases are destroyed
- refaddr-based identity (was stringification)
- release(undef) is a silent no-op
- Correctness: timeout and reset invariant
- _await_with_deadline non-throwing helper replaces the wait_any + throwing await pattern that silently skipped timeout cleanup
- _reader_fatal detaches inflight before closing the socket so the typed error is preserved; idempotent via _fatal_in_progress guard under eval+finally
- Correctness: transaction state cleanup
- watch() and multi_start() now set their state flags only after the underlying command succeeds (previously set the flag, then awaited; a failed command left the client claiming to be in MULTI/WATCH state)
- watch_multi() unwinds WATCH on a callback die (previously a callback exception left the connection holding watches; the next command on the client would hit a poisoned state)
- DISCARD now correctly clears the watching flag (Redis DISCARD drops watches; the client previously claimed to still be watching)
- Breaking: removed `install` option from define_command
- The option used to install the script as a method on the Async::Redis class. Pass `install => 1` now dies with a clear message pointing callers at run_script(); use that or hold the returned Async::Redis::Script directly.
- Privacy: OTel command arguments no longer in spans by default
- otel_include_args now defaults to 0; pass 1 to re-enable
- Added: message_queue_depth constructor option (default 1)
- Added: key prefixing for PFADD, PFCOUNT, PFMERGE, GETBIT, SETBIT, BITCOUNT, BITPOS, HSTRLEN, ZMSCORE
- Added: Async::Redis::Cookbook (POD) — runnable, tested recipes for connection management, pipelines, transactions, pubsub, pool, scripts, and observability.
- Added: examples/ — async job queue, bulk insert, and a stress harness (examples/stress/) that drives all major features under load with chaos injection (CLIENT KILL) and integrity verification.
- Added: GitHub Actions CI workflow (thanks @GaNardelli, PR #4) runs the full test suite with RELEASE_TESTING=1 against a Dockerized Redis on every push and pull request.
- Documentation: TASK LIFECYCLE POD section explaining the Future::Selector contract; user-disconnect vs reader-fatal path distinction; broad POD review and corrections across the public surface.
Documentation
Practical Async::Redis recipes
Auto-generated Redis command methods
Modules
Async Redis client using Future::IO
Automatic command batching
Auto-generated Redis command methods
Base exception class for Redis errors
Connection failure exception
Disconnected exception
Protocol violation exception
Redis server error exception
Timeout exception
Cursor-based SCAN iterator
Key position detection for Redis commands
Command pipelining
Connection pool for Async::Redis
Reusable Lua script wrapper with EVALSHA optimization
PubSub subscription handler
Observability for Redis client
Transaction command collector
Redis connection URI parser
Examples
- examples/README.md
- examples/async-job-queue/PLAN.md
- examples/async-job-queue/README.md
- examples/async-job-queue/SPEC.md
- examples/async-job-queue/app.pl
- examples/bulk-insert/README.md
- examples/bulk-insert/app.pl
- examples/docker-compose.yml
- examples/pagi-chat/README.md
- examples/pagi-chat/app.pl
- examples/pagi-chat/lib/ChatApp/HTTP.pm
- examples/pagi-chat/public/css/style.css
- examples/pagi-chat/public/index.html
- examples/pagi-chat/public/js/app.js
- examples/pagi-chat/reset-redis.sh
- examples/slow-redis/README.md
- examples/slow-redis/app.pl
- examples/stress/README.md
- examples/stress/lib/Stress/Chaos.pm
- examples/stress/lib/Stress/Harness.pm
- examples/stress/lib/Stress/Integrity.pm
- examples/stress/lib/Stress/Metrics.pm
- examples/stress/lib/Stress/Output.pm
- examples/stress/lib/Stress/Workload.pm
- examples/stress/reset-redis.sh
- examples/stress/stress