0.000036  2026-05-10 21:03:04-07:00 America/Los_Angeles

    - Add IPC::Manager::Role::Service::run_returns_to_caller, a default-false opt-in that lets a service ask _ipcm_service to return to its caller after run() returns instead of calling POSIX::_exit / exit. The hook is meaningful only when the service was launched via exec.stay_in_begin = 1, which invokes _ipcm_service synchronously inside the boot -M BEGIN hook of the exec'd perl process: returning out of _ipcm_service then propagates back through IPC::Manager::Service::State::import and out of BEGIN, while perl is still parsing the -e entry script. The combination -- exec.stay_in_begin = 1 on the launching side and run_returns_to_caller true on the consuming class -- is the supported pattern for "preload services that hand off to a forked, freshly-goto::file'd test grandchild" (e.g. the Test2-Harness preload service). Existing services that do not consume Role::Service, or that consume it without overriding the hook, still get the previous "always exit after run() returns" behaviour
    - IPC::Manager::Service::State::_ipcm_service honours run_returns_to_caller by storing run()'s exit code in an outer lexical, letting the setjump body return normally, and propagating the value back to its caller. The setjump body is refactored so the exit() / POSIX::_exit() calls live outside its eval and only fire when the opt-in is not in effect; behaviour for CODE-ref services, services that never return from run(), and services that return with run_returns_to_caller false is unchanged. The module's POD gains an "EXEC AND STAY-IN-BEGIN MODE" section describing the supported pattern
    - IPC::Manager::Service::State::ipcm_service now forwards the exec.stay_in_begin flag to the exec'd child instead of stripping it. The parent-side delete of $params{exec} happened before the JSON serialisation that is read by the child's import(), so the child always saw $params->{exec} as undef and the stay_in_begin branch was unreachable -- every exec-mode service that opted in to stay_in_begin silently degraded to the runtime-from-"-e" path, which prevents the goto::file + Long::Jump BEGIN-time handoff pattern from working at all. The fix re-serialises just { stay_in_begin => 0|1 } into the child's params so the child's import() sees the flag and can call _ipcm_service synchronously from inside BEGIN
    - Add [PruneCruft] to dist.ini so build artifacts (blib/,
      pm_to_blib, MYMETA.*) no longer leak into release tarballs.

0.000035  2026-04-26 20:18:58-07:00 America/Los_Angeles

    - Loosen the upper-bound timing assertion in t/unit/peer_active_timeout.t subtest 'positive timeout: returns 0 after timeout when peer never becomes active' from < 3s to < 10s. The previous bound fired on a CPAN smoker (perl 5.40.0, x86_64-linux) where peer_active('foo', 0.3) elapsed ~4.27s under heavy load (test-suite wallclock ~943s), even though peer_active itself behaved correctly. The lower bound (>= 0.25s) is unchanged, so the correctness assertion that the call waits at least the requested timeout still holds; the upper bound is only a runaway-wait guard, for which 10s gives loaded smokers headroom
    - Loosen the upper-bound timing assertion in t/unit/Util.t subtest 'tinysleep sleeps for the requested duration' from < 1.0s to < 5.0s. The previous bound fired on three CPAN smokers (perl 5.40.2, 5.12.5, 5.40.4 on x86_64-linux) where tinysleep(0.1) elapsed up to ~1.42s under heavy load (test-suite wallclocks ~970-1172s), even though tinysleep returned promptly. The lower bound (>= 0.05s) is unchanged; the upper bound is only a runaway-sleep guard

0.000034  2026-04-26 03:43:44-07:00 America/Los_Angeles

    - IPC::Manager::Client::ConnectionUnix::_drain_reads now early-returns "EOF" when its $fh argument is undef. During global destruction perl is free to destroy the IO::Socket::UNIX stored in $entry->{fh} before the Client's DESTROY runs disconnect, leaving $entry->{fh} undef when _read_messages / _resolve_pending walk the connection cache; the unguarded sysread then died with "Can't use an undefined value as a symbol reference" mid-shutdown. Treating a missing handle as already-closed lets the disconnect drain finish cleanly
    - IPC::Manager::Serializer::JSON::Zstd now accepts constructor arguments for compression level and preset dictionary: ipcm_spawn(serializer => ['JSON::Zstd', level => 9, dictionary => '/path/to/dict']) builds a single configured instance. The default compression level is 3 (Compress::Zstd's library default); when a dictionary path is supplied the constructor loads it once via Compress::Zstd::CompressionDictionary / DecompressionDictionary and reuses the loaded handles for every serialize/deserialize call. Class-method use (serializer => 'JSON::Zstd') is unchanged: no instance is constructed, level 3 and no dictionary apply. The serializer slot of the cinfo connection-info string accepts the same forms (string class or [$class, %args] arrayref); IPC::Manager caches built instances by class+args so peer connections share one serializer rather than reconstructing per send_message or per peer. The cinfo string itself is still always plain JSON and never compressed
    - IPC::Manager::Base::FS::requeue_message / read_resume_file now use 4-byte length-prefixed framing for entries in the resume file instead of newline-delimited records. The newline framing was unsafe for binary serializers (zstd, etc.) whose decoders reject trailing garbage; length-prefixing makes the format safe regardless of the serializer's encoding. Resume files do not survive across processes, so this is not a wire-compat break for any deployment that does not move resume files between IPC::Manager versions
    - Add IPC::Manager::Serializer::JSON::Zstd, a subclass of IPC::Manager::Serializer::JSON that zstd-compresses payloads with Compress::Zstd before sending and decompresses them on receipt. Uses Compress::Zstd's default compression level and no preset dictionary. JSON::Zstd is now selected as the default serializer when Compress::Zstd 0.20 or newer is installed; if Compress::Zstd is missing or older, ipcm_default_serializer falls back to plain JSON. Compress::Zstd is added as a runtime recommendation, not a hard dependency. IPC::Manager::Test::test_generic compares the deserialized info string against $guard->serializer instead of hard-coding IPC::Manager::Serializer::JSON, so the check works regardless of which serializer ipcm_default_serializer picked
    - IPC::Manager::Client::UnixSocket no longer appends a trailing newline to serialized payloads on send (applies to send_message, _outbox_try_write, and _drain_blocking). The byte was vestigial under SOCK_DGRAM (datagrams self-frame) and broke binary serializers whose decoders reject trailing garbage. JSON receivers tolerated the extra byte, so removing it is wire-compatible with previous JSON-only deployments
    - IPC::Manager::Service::Handle::await_all_responses now short-circuits its gone-peer probe. The poll previously ran _gone_pending_peers on every loop iteration just to test "did anyone go away", which calls _pending_peer_active per pending response and can hit the filesystem or the database. The boolean check is now done by a new _have_gone_pending_peers helper that returns on the first gone peer; _gone_pending_peers is still used on the croak path that needs the peer names
    - Fix IPC::Manager::Service::Handle::await_all_responses hanging forever when a service crashes without responding. await_all_responses now mirrors await_response's peer-death semantics: pending peers are checked each polling cycle, and if any become inactive it does one final non-blocking drain and croaks "peer(s) ... went away while awaiting responses" instead of polling indefinitely. Also accepts an optional $timeout argument, throwing if the deadline passes with pending responses still outstanding
    - Bound the IPC::Manager::Spawn::wait peer-disappear poll. The loop used to call sleep(1) forever waiting for clients to disconnect, so a wedged service or a slow-filesystem CPAN smoker could trap shutdown indefinitely. The poll now respects a deadline (default 60s, overridable via the peer_timeout argument or $ENV{IPC_MANAGER_SPAWN_PEER_TIMEOUT}); when it expires the still-registered peers are warned about and the child-reap loop runs anyway
    - On per-subtest alarm timeout, IPC::Manager::Test->run_all/run_one now SIGTERM (then SIGKILL) the subtest's child process group before confessing, so service grandchildren and workers are reaped instead of being orphaned. Orphans were continuing to write Test2::IPC events into the parent's tempdir after the parent exited, producing downstream "IPC Fatal Error: Leftover files in the directory" bailouts on top of the original timeout. The subtest child becomes the leader of its own process group via POSIX::setpgid, which is also called from the parent to close the setpgid race
    - Add IPC::Manager::Client::ConnectionUnix, a connection-oriented UNIX-socket driver built on SOCK_STREAM. Each client either listens for incoming connections (default) or drops a marker file under the route directory, and messages flow over established per-peer connections with a 4-byte length-prefixed framing and a hello-frame peer identification handshake. send_message does not auto-reconnect on EPIPE: a failed send drops the cached connection and propagates the error so the caller decides whether to retry; the next send to the same peer establishes a fresh connection. ConnectionUnix is registered in ipcm_default_protocol_list, documented under CLIENT PROTOCOLS, and exercised by the full t/ConnectionUnix/ integration tree
    - Add IPC::Manager::Role::Client::Connection, a Role::Tiny role that gives connection-oriented drivers a uniform per-peer connection API (has_connection, connections, disconnect_connection, last_activity, close_idle_connections). Consumers provide _connections (the cache hashref) and _close_connection (handle teardown); the role itself runs no background timer, so close_idle_connections is caller-driven
    - Support dynamic select-handle sets in IPC::Manager::Role::Service::Select. Drivers can now advertise have_dynamic_handles_for_select to indicate that handles_for_select changes over the lifetime of the client (e.g. ConnectionUnix accepting new connections), and the service select cache rebuilds its IO::Select set each iteration for those drivers instead of caching it once at startup
    - Bump the optional Atomic::Pipe dependency from 0.022 to 0.026 in dist.ini, the AtomicPipe client _viable check, and the Test2::Require::Module gates on every t/AtomicPipe/ and t/unit/atomicpipe_*/ test
    - Add IPC::Manager::Role::Outbox: per-peer non-blocking send queue consumed by FIFO/datagram-socket clients. Services flip their client to send_blocking=0 at startup; outbound sends that would EAGAIN are queued and the service event loop drains them when the underlying transport becomes writable. Memory- and DB-backed clients keep the simple blocking send_message via a base-class no-op fallback. Per-peer message order across the outbox is NOT preserved (documented in Role::Outbox POD); callers that need strict order must keep the client in send_blocking=1
    - IPC::Manager::Client base class sets $self->{_creator_pid} once in init and uses it (instead of +PID) in DESTROY. The role's init re-runs after fork and rewrites +PID, so DESTROY in a forked child would otherwise unlink the FIFO / socket / route entry the parent still owns. Pinning DESTROY to the originating process keeps cleanup correct across post-fork peer churn. The same base class also gains default fallbacks for the full Outbox API (try_send_message, pending_sends, drain_pending, have_writable_handles, writable_handles, send_blocking, set_send_blocking, can_send_to) so service code can call the API uniformly across all client backends; clients that do not consume Role::Outbox (memory- and DB-backed) treat sends as immediate and never queue
    - Client::AtomicPipe and Client::UnixSocket consume Role::Outbox. Writer pipes/sockets are non-blocking when send_blocking=0 and blocking when send_blocking=1 (the default for non-service callers); send_message in either mode preserves the fire-and-die semantics of earlier versions
    - Client::ConnectionUnix consumes Role::Outbox with a per-connection send_buffer instead of a whole-payload queue, since SOCK_STREAM partial writes mid-frame cannot be re-sent from the start without corrupting the stream. _outbox_try_write appends the framed bytes to the connection's send_buffer and flushes non-blocking; pending_sends, drain_pending, have_writable_handles and writable_handles are overridden to walk per-connection send_buffers so the service event loop wakes on writability and drains correctly. send_message branches on send_blocking: blocking mode runs the synchronous _send_frame and propagates errors to the caller; non-blocking mode delegates to try_send_message
    - Service::Select adds select_write() returning a fresh IO::Select over the client's writable-with-backlog handles. Service::watch now uses IO::Select->select($r, $w, undef, $cycle) so the loop wakes on either readable or writable readiness

0.000033  2026-04-25 01:47:50-07:00 America/Los_Angeles

    - Reset the IPC::Manager::Test->run_all alarm per subtest instead of using a single 180s budget for the whole sweep. On slow CPAN tester hardware the cumulative runtime in JSONFile-NoInotify mode exceeded 180s, firing SIGALRM mid-sweep regardless of which subtest was actually running and producing FAIL reports. Each subtest now gets its own 180s budget; the failure message names the subtest that timed out; an IPC_MANAGER_TEST_TIMEOUT environment variable overrides the default
    - Use Time::HiRes::stat for nanosecond-resolution mtime in IPC::Manager::Client::JSONFile NoInotify cache-staleness check, and drop the artificial "wait $INTERVAL before SHA-comparing" gate. Same-second cross-process writes are now detected by sub-second mtime alone on Linux/BSD, with SHA still serving as a tiebreaker on filesystems that round mtime to whole seconds. SHA on small JSON state files is microseconds, so removing the 1s delay does not regress poll cost
    - Speed up the two slowest IPC::Manager::Test subtests. test_sync_request_peer_exits_after_response replaces a 50-iter 0.05s pid_is_running poll with a blocking waitpid (kernel returns immediately on direct-child exit) and falls back to the poll only when waitpid reports ECHILD, cutting that subtest from ~12.9s to <1s. test_intercept_errors drops two arbitrary Time::HiRes::sleep fences (the follow-up sync_request is FIFO-ordered behind the peer_delta and general_message dispatches, so service-event ordering already provides the synchronisation those sleeps were emulating), cutting it from ~3.6s to ~2.3s
    - Add IPC::Manager::Test->run_one(protocol => ..., test => ...) as a single-subtest counterpart to run_all, and split the ten t/<Protocol>.t aggregate runners into one .t file per subtest under t/<Protocol>/. Each subtest is now its own test file, so prove -j parallelises across subtests instead of running them serially within a single test file. Whole-suite wallclock with prove -Ilib -j16 -r t/ drops from ~71s to ~24s

0.000032  2026-04-23 08:27:50-07:00 America/Los_Angeles

    - Accidentally uploaded wrong 0.000031 tarball, re-releasing to fix

0.000031  2026-04-23 08:25:32-07:00 America/Los_Angeles

    - Fix a race in IPC::Manager::Service::Handle::await_response where a peer that responded and then exited could be reported as "peer ... went away" even though the response had already been delivered to the local inbox. await_response now does one final non-blocking poll and re-checks for the response after the peer-active probe reports the peer gone, so an in-transit reply still completes the request instead of croaking

0.000030  2026-04-22 13:17:49-07:00 America/Los_Angeles

    - Fix a regression from 0.000028 that made every UnixSocket test fail on CPAN testers when TMPDIR produced a moderately long route. The route-length check in IPC::Manager::Client::UnixSocket croaked unconditionally as soon as the route left less than 42 bytes of budget, even for short peer ids that would have fit verbatim. The check is now deferred to IPC::Manager::Base::FS::on_disk_name and only fires when a peer id must be hashed to an on-disk name but the hashed form does not fit under the route. Short peer ids pass through unchanged

0.000029  2026-04-21 07:31:45-07:00 America/Los_Angeles

    - Expose ipcm_service's first-fork child pid on the returned handle/peer via a new $handle->child_pid accessor. Supervisors that track services by pid (e.g. Test2-Harness2's Role::ResourceServiceHost) can now use the standard ipcm_service spawn path (pre_fork_hook, post_fork_hook, ready() handshake, peer wiring) instead of rolling their own fork helper. child_pid is the pid fork returned in ipcm_service's parent branch: with no post_fork_hook it equals the service pid; with an interpose-style post_fork_hook (parent becomes a long-lived wrapper, child runs the service loop) it is the wrapper pid, which is the correct watchable lifetime; with a daemonizing post_fork_hook (double-fork + parent-exit) it becomes stale once the hook completes, in which case callers should use $handle->service_pid instead. child_pid is undef on handles/peers constructed outside the ipcm_service spawn path

0.000028  2026-04-20 13:48:46-07:00 America/Los_Angeles

    - Support arbitrarily long peer names. DBI-backed drivers (PostgreSQL, MySQL, MariaDB, SQLite) widen the ipcm_peers.id and ipcm_messages to/from columns from VARCHAR(36) to VARCHAR(512). Filesystem-based drivers (UnixSocket, AtomicPipe, MessageFiles) transparently hash peer ids that are too long, contain path-unsafe characters, or collide with the reserved "h-" prefix to a 42-character on-disk form ("h-" + 40 hex chars of sha256), storing a .name sidecar file so peers(), peer_exists(), all_stats(), and message routing keep using the real peer names. UnixSocket sizes its threshold dynamically to respect the sun_path limit and croaks up front if the route leaves no room

0.000027  2026-04-19 09:58:22-07:00 America/Los_Angeles

0.000026  2026-04-19 08:36:18-07:00 America/Los_Angeles

    - Split IPC::Manager::Client::SharedMem out into its own CPAN distribution (IPC-Manager-Client-SharedMem). The SharedMem module, its tests, the SysV configure-time probe, and the matching Makefile.PL / dist.ini shims no longer ship with IPC-Manager. Install IPC::Manager::Client::SharedMem separately if you need the SysV shared-memory protocol

0.000025  2026-04-19 06:33:45-07:00 America/Los_Angeles

    - Add optional $timeout argument to Service::Peer::ready(), Service::Handle::ready(), and the underlying Client::peer_active(). With no timeout the behavior is unchanged (one-shot check); with a timeout the call blocks until the peer becomes active or the timeout elapses. A timeout of 0 blocks indefinitely. Polls via IO::Select on the client's peer-change descriptor when available (inotify on FS-based protocols) and otherwise falls back to a 0.05s sleep-and-retry loop, replacing the 0.025s busy-wait in Service::State

0.000024  2026-04-18 14:57:47-07:00 America/Los_Angeles

    - Fix sync_request hang when a peer disappears mid-request: await_response now snapshots the peer at send time and croaks if the peer is fully removed from the bus before the response arrives. Suspend-capable protocols (those advertising suspend_supported) keep waiting across a clean suspend or a restart-under-a-new-pid because the response can still be delivered once the peer resumes — only full unregistration counts as "gone"
    - Add optional $timeout argument to sync_request() and await_response(); without it the behavior is unchanged (blocks indefinitely), with it the call croaks once the deadline elapses
    - Sweep orphaned Atomic::Pipe reassembly state when a peer leaves the bus: a new Client peer_left() hook (called from the service loop on peer-delta removal) lets AtomicPipe drop per-(pid, tid) parts/buffers entries belonging to dead peers instead of accumulating them forever

0.000023  2026-04-17 12:06:27-07:00 America/Los_Angeles

    - Strengthen SysV IPC configure-time probe: add concurrent-contention and rapid create/destroy phases to catch hosts where semop sporadically returns EINVAL only under load
    - Add kernel-limit probe that disables SharedMem when /proc/sys/kernel/sem or shmmni/shmmax are below workable minimums
    - Add persistence probe that disables SharedMem when an external reaper (systemd-logind RemoveIPC, ipcrm cronjob, cgroup IPC policy, etc.) destroys a freshly-created semaphore mid-probe
    - Detect systemd-logind RemoveIPC=yes without user linger and include it as a diagnostic hint in the install-time failure message

0.000022  2026-04-16 16:49:45-07:00 America/Los_Angeles

    - Reap children in the service loop via waitpid(-1, WNOHANG) so unrelated zombies are cleaned up
    - Add run_on_pid hook and on_pid service callback for reaped non-worker child processes
    - Add pids key to the watch() activity hash mapping reaped non-worker pid to exit value

0.000021  2026-04-16 07:18:10-07:00 America/Los_Angeles

    - Fill in missing Changes entries for 0.000020

0.000020  2026-04-16 01:09:03-07:00 America/Los_Angeles

    - Refactor Service::State to use Long::Jump instead of labelled redo
    - Disable SharedMem at install time when host SysV IPC is broken
    - Fix test_service_callbacks race waiting for on_peer_delta
    - Fix test_workers race where parent reads empty pid file

0.000019  2026-04-13 15:01:40-07:00 America/Los_Angeles

    - Add post_fork_hook to Role::Service and post_fork callback to Service for double-fork/daemonize support

0.000018  2026-04-12 02:20:35-07:00 America/Los_Angeles

    - Fix UnixSocket reads on FreeBSD: use recv() instead of diamond operator for SOCK_DGRAM sockets
    - Fix SharedMem semaphore EINVAL race: disconnect Handle client before Spawn destruction
    - Fix message ordering on systems with coarse Time::HiRes resolution
    - Add sort_messages to Client base class to centralize message sort logic

0.000017  2026-04-11 09:16:39-07:00 America/Los_Angeles

    - Fix on_general_message test race where file was observed before content was written

0.000016  2026-04-10 09:25:35-07:00 America/Los_Angeles

    - Fix Spawn shutdown race where fast services exit before wait() captures their PIDs

0.000015  2026-04-09 16:16:09-07:00 America/Los_Angeles

    - Use Cpanel::JSON::XS directly and lazily reinitialize to survive global destruction
    - Replace JSON::PP prereq with Cpanel::JSON::XS in dist.ini
    - Fix Spawn::wait() to waitpid child processes before destroying shared resources
    - Add debug flag to Spawn, reduce test suite noise

0.000014  2026-04-08 14:58:59-07:00 America/Los_Angeles

    - Refactor viable() into base-class wrapper around _viable() so it never throws
    - Fix DB tests failing to skip when DBI is too old
    - Fix DB viable() not detecting missing server binaries (initdb, mysqld, etc.)
    - Fix inotify tests failing when Linux::Inotify2 < 2.3 is installed
    - Enforce Atomic::Pipe minimum version 0.022 in viable check and tests
    - Add JSON::PP prereq, remove unused Sub::Util, fix IO::Select version in dist.ini
    - Add 180-second timeout with stack trace to integration tests

0.000013  2026-04-08 08:14:28-07:00 America/Los_Angeles

    - Fix SQLite DEFAULT FALSE incompatibility with older SQLite versions
    - Fix UnixSocket broadcast test race on FreeBSD and older Linux kernels

0.000012  2026-04-07 16:36:22-07:00 America/Los_Angeles

    - Fix JSONFile cache staleness detection when Linux::Inotify2 is absent
    - JSONFile now requires either Linux::Inotify2 or Digest::SHA (recommended dep)
    - Split inotify-affected tests into Inotify/NoInotify variants

0.000011  2026-04-05 14:10:41-07:00 America/Los_Angeles

    - Reintegrate DBI and UnixSocket clients back into this dist
    - Add JSONFile client
    - Add SharedMemory client, rename InMemory to LocalMemory
    - Add exec-services feature and refactor tests
    - Fix DBI clients: blob handling, viable() checks, message ordering, SQLite timestamps
    - Fix UnixSocket client: add version requirement and suspend_supported method
    - Fix error propagation when requests cause errors
    - Various bugfixes for hidden errors
    - Add unit and integration tests for DBI and UnixSocket clients
    - Add documentation
    - Require newer DBIx::QuickDB

0.000010  2026-04-01 16:34:06-07:00 America/Los_Angeles

    - Break DBI and UnixSocket clients into their own dists

0.000009  2026-03-30 11:07:14-07:00 America/Los_Angeles

    - Skip postgresql tests on systems without initdb

0.000008  2026-03-29 09:37:55-07:00 America/Los_Angeles

    - Remove leftover debugging

0.000007  2026-03-28 12:05:47-07:00 America/Los_Angeles

    - Fix issues with listed deps

0.000006  2026-03-26 19:18:54-07:00 America/Los_Angeles

    - Introduce 'Service' feature

0.000005  2026-02-23 19:54:20-08:00 America/Los_Angeles

    - Fix Linux::Inotify dep in test
    - Fix SQLite dep in test

0.000004  2026-02-23 10:49:55-08:00 America/Los_Angeles

    - Fix UnixSocket issues usually seen on BSD

0.000003  2026-02-19 20:41:21-08:00 America/Los_Angeles

    - Add IO::Select and handle capabilities where possible
    - Possibly fix UnixSocket

0.000002  2026-02-19 18:57:37-08:00 America/Los_Angeles

    - Attempt to add debugging for UnixSocket issues

0.000001  2025-12-31 20:40:44-08:00 America/Los_Angeles

    - Initial Release
