Revision history for DBIO-Forked

0.900001  2026-07-12
    First release. The dependency-free, fork-based async layer for DBIO
    drivers: a generic async-storage backend that makes ANY sync DBIO
    driver async by running each query in a forked child that speaks the
    ordinary sync driver, with results streamed back over a pipe. It needs
    no async-capable DB client and no event loop -- only core Perl (fork,
    pipe, Storable, IO::Select, POSIX) plus DBIO core. A sibling of
    DBIO::Async on the same layer: both satisfy the core
    DBIO::Storage::Async contract, but DBIO::Forked targets the drivers
    that will never get a native async client (Oracle, SQLite, DB2,
    Sybase, ...).

    * Storage
        - DBIO::Forked::Storage subclasses core DBIO::Storage::Async and
          activates per connection via connect(..., { async => 'forked' }),
          registered on core through register_async_mode -- not a
          class-level backend declaration. _run_forked forks per query;
          the child runs the inherited sync storage's ordinary CRUD (its
          _verify_pid fork handling reconnects fresh, so no SQL is
          rebuilt and no connect_info is replayed), Storable-freezes the
          rows over a pipe, and _exits. select/select_single child
          results are reshaped to the async backend contract (raw row
          arrayrefs / a single arrayref) so real ResultSet/Row async
          (all_async/first_async/etc.) gets the right shape; insert_async
          resolves the returned-columns hashref.
        - txn_do_async routes the whole transaction through one forked
          child: the body closure is fork-inherited, so BEGIN / body /
          COMMIT|ROLLBACK all run on the child's freshly-reconnected sync
          connection. Two caller limits, documented and tested: the
          body's return value must be Storable-serializable (a live
          Row/ResultSet fails loud as a failed Future via
          _serialization_error, never a corrupt blob), and the body must
          be sync-only (a nested *_async call inside it would re-fork).

    * Future
        - DBIO::Forked::Future, a loop-free Future over a pipe read fd
          (is_ready peeks non-blocking, get blocks then Storable-thaws and
          reaps the child, zombie-free). Carries the full future_class
          surface core's ResultSet-async helpers call: done/fail/
          needs_all class constructors plus and_then -- a drop-in for
          DBIO::Future::Immediate, not just the minimal DBIO::Future
          contract. needs_all blocks serially on the already-parallel
          forked children (wall time = slowest child); get is
          wantarray-aware (scalar context returns the first value).

    * Tests
        - Permanent integration test t/05-sqlite-live.t proves Forked
          end-to-end over a real dbio-sqlite + DBD::SQLite file-backed
          database: the txn body runs in a real fork (child pid != parent
          pid), the child's committed insert is visible in the parent via
          the shared file DB, needs_all collects parallel forked reads,
          and no zombies remain. Skips cleanly when DBD::SQLite /
          dbio-sqlite are absent, preserving the dependency-free default
          suite.

    * Dependencies
        - Dependency-free by design: only core Perl (fork, pipe,
          Storable, IO::Select, POSIX) plus DBIO core -- no Future /
          Future::IO / event-loop dependency, unlike DBIO::Async.
          DBIO::SQLite is a test-only recommends for the live integration
          test (DBD::SQLite comes in transitively as its own hard
          dependency, so it is not declared separately here).
