DBIx-QuickDB

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN

      rather than rebuilding on top of it and failing with "table already
      exists".

0.000056  2026-07-21 11:35:25-07:00 America/Los_Angeles

    - Added a no_disconnect_handles driver attribute (inheritable by clones) that skips the teardown-time DBI handle disconnect, for callers that hold their own live handles to the db being stopped (e.g. MySQL, which shuts down fine with clients conn...

0.000055  2026-07-14 12:20:42-07:00 America/Los_Angeles

    - Data-dir cleanup is now robust on Windows: remove_tree could leave a SQLite data dir non-empty on MSWin32 (the OS releases file handles asynchronously, so an immediate rmdir gets ENOTEMPTY), which left the stale db behind and made the rebuilt i...
    - Driver cleanup() now disconnects this process's DBI handles before deleting the data dir; the watcher teardown paths already did, but the watcher-less path (SQLite, whose stop() is a no-op) reached cleanup() with handles still open, which on Wi...

0.000054  2026-07-13 10:53:23-07:00 America/Los_Angeles

0.000053  2026-07-10 11:07:52-07:00 America/Los_Angeles

    - Temp dir names now include the pid: File::Temp derives names from rand(), so forked or identically-seeded processes (e.g. under Test2::Plugin::SRand's date seed) drew identical DB-QUICK-* name sequences and could converge on one directory, corr...
    - MySQL-family servers are now started with loose-mysqlx=OFF; the X Plugin defaults ON with a global default socket (/tmp/mysqlx.sock) and TCP port (33060), so concurrent instances contended on shared resources despite per-instance datadirs.
    - Fixed stop()/eliminate() signals being silently lost when they raced watcher startup: SIGTERM/SIGINT were SIG_IGN'd (discarded) across the watcher's exec instead of blocked (held pending) like SIGUSR1 already was, so a stop landing in that wind...
    - Watcher::wait no longer kills a watcher that is legitimately blocked reaping an unkillable (e.g. disk-sleep) server, which orphaned a live server and let stop() return while the data dir was still locked; it now waits longer (QDB_STOP_LEASH), a...
    - Raised the default QDB_STOP_GRACE from 4 to 10 seconds and dropped the Pool tests' small-grace override: a normal MySQL 8 or loaded-host PostgreSQL clean shutdown routinely exceeds 4-5s, making escalation warnings and needless SIGKILLs (plus In...
    - PostgreSQL instances now run with autovacuum=off by default: on PostgreSQL <= 14 the autovacuum launcher could stall shutdown for 10s (PGSTAT_MAX_WAIT_TIME) waiting on the stats collector, routinely blowing the stop grace and drawing SIGQUIT es...
    - DESTROY/eliminate teardown now disconnects this process's DBI handles first (like stop() already did); a connected handle made PostgreSQL's smart shutdown wait out the whole stop grace.
    - Clones now start with fresh logs; clone_dir copied the source's error.log and cmd-log-* files, so a clone's logs appeared to contain the parent's history.
    - t/watcher_fast_kill.t child-readiness now uses pipe synchronization instead of polling for a marker file under a 5s deadline a loaded smoker could blow.
    - t/watcher_fast_kill.t: widened the SIGQUIT-to-SIGKILL escalation gap (grace 1 -> 4) in the fast-sig subtest; on slow smokers the child was not scheduled in time to run its QUIT handler before SIGKILL landed, spuriously failing the marker assert...
    - Data-dir deletion no longer aborts the process when the watcher and the owning process race to delete the same directory; File::Path's "cannot chdir ... aborting" die bypasses its error handler, and turned otherwise-passing FreeBSD smoker runs ...
    - The Drivers/QuickDB/Pool tests now run once per database installation, forking a subprocess per install; with AUTHOR_TESTING set the test helpers live-scan ~/dbs/*/bin for developer installs in addition to the system install (nothing in lib/ co...
    - Removed the stale hardcoded ~/dbs PATH prepends from the tests; added AGENTS.md with the developer testing protocol.
    - t/Drivers tests now quote the -target class name; the barewords failed to compile under Test2::Harness (yath), which enables strict before line 1.

Changes  view on Meta::CPAN

0.000050  2026-06-10 14:14:43-07:00 America/Los_Angeles

    - Watcher teardown blocks on the reap after sending SIGKILL instead of racing a deadline that was too short at low QDB_STOP_GRACE.
    - t/Pool wraps every server-spawning db()/fetch_db() in the resource-skip helper so a semaphore-exhausted host skips instead of failing.

0.000049  2026-06-09 17:14:55-07:00 America/Los_Angeles

    - Added Driver::destroy_quietly(): fast-destroy teardown for disposable clones (cleanup => 1) that kills, reaps, and removes the data dir immediately instead of a graceful shutdown that can block 2*QDB_STOP_GRACE+2 seconds.
    - Added an opt-in fast_destroy attribute: DESTROY uses destroy_quietly() when cleanup => 1, falls through to the graceful path when cleanup => 0, and is inherited by clones via clone_data().
    - Added per-driver fast_stop_sig() for fast/forced teardown: default SIGKILL, PostgreSQL overrides to SIGQUIT so the postmaster releases its SysV semaphores instead of leaking them; watcher escalates to SIGKILL if it does not stop promptly.
    - Added a watcher fast-eliminate request (SIGUSR1) that kills with fast_stop_sig, reaps, and deletes the data dir; blocked (not ignored) across the startup exec so a request racing startup stays pending instead of being dropped.
    - The graceful teardown path (stop()/eliminate()) now escalates a stuck shutdown through fast_stop_sig before SIGKILL, so it releases SysV semaphores instead of leaking them.
    - get_db() and the pool tests skip_all instead of failing when a host is out of System V semaphores/shared memory; added skipall_on_resource_error(), also covering the DBIx::QuickDB->import path (t/QuickDB) and mid-run pool clone builds (t/Pool).
    - On a start timeout, start() now reports the server's launch log and whether its process is still alive, instead of only the often-empty error log.
    - Added t/fast_destroy.t, t/watcher_fast_kill.t, and t/resource_skip.t.

0.000048  2026-06-06 10:53:27-07:00 America/Los_Angeles

    - Fixed a pid-reuse race that could shut down the wrong database server. After a server was stopped, its watcher exited and the OS could recycle the watcher's pid; a later teardown signal (e.g. eliminate() from the watcher object's DESTROY, which...
    - Removed two redundant pid-signalling paths that carried the same pid-reuse hazard: the post-wait() server-pid poll in Driver::stop() (the watcher already reaps the server before it exits, so stop() now just trusts wait()) and the direct server-...
    - Added t/clone_sequence.t, an explicit PostgreSQL regression asserting a clone of a stopped source continues its SERIAL sequence with the exact next id (no SEQ_LOG_VALS +32 jump); the broad Pool test only checks id ordering

lib/DBIx/QuickDB/Pool.pm  view on Meta::CPAN

    my ($age) = @_;

    my $dir = $self->{+CACHE_DIR};

    opendir(my $dh, $dir) or die "Could not open cache dir '$dir': $!";
    for my $name (readdir($dh)) {
        my $full = "$dir/$name";

        # Quarantines sit outside the canonical cache path (see
        # _remove_or_quarantine). Retry their removal on every pass: the
        # transient Windows file lock that blocked deletion may have cleared.
        if ($name =~ m/\.STALE-/) {
            remove_tree_robust($full) if -d $full;
            next;
        }

        # A live cache entry is a bare directory name. Its marker files
        # ("$name.lock", "$name.READY") do contain dots and are handled
        # through their entry, never scanned directly.
        next if $name =~ m/\./;

lib/DBIx/QuickDB/Pool.pm  view on Meta::CPAN

        $stamp = 0 unless $stamp =~ /^\d+\z/;

        next unless $age <= (time - $stamp);

        eval {
            # An expired SQLite source may still have DBI handles retained by
            # application/test code. On Windows those handles prevent unlinking
            # the database file. Close every matching handle before removal.
            disconnect_dbi_handles($full);

            # If removal remains blocked, moving the tree out of its canonical
            # path keeps a later build from seeing partially-removed SQLite
            # schema/data. Either way, invalidate the external markers: expiry
            # was explicitly requested, and build_db() will refuse to build on
            # any stubborn tree that could not be quarantined.
            $self->_remove_or_quarantine($full);
            unlink("$full.lock") if -e "$full.lock";
            unlink("$full.READY") if -e "$full.READY";
            1;
        } or warn $@;
    }

lib/DBIx/QuickDB/Watcher.pm  view on Meta::CPAN


    my $ddir = $self->{+DATA_DIR};
    my $ssig = $self->{+DB}->stop_sig // 'TERM';
    my $fsig = $self->{+DB}->fast_stop_sig // 'KILL';
    my $delete_data = $self->{+DB}->should_cleanup ? 1 : 0;
    my $owner_kill  = $delete_data && $self->{+DB}->fast_destroy
        ? 'FAST_TERM'
        : 'TERM';

    # Block (rather than ignore) every teardown signal across the exec. A
    # blocked signal stays *pending* instead of being discarded, so a stop(),
    # eliminate(), or fast_eliminate() that races server startup -- arriving
    # after the socket is up (so the caller's start() has returned) but before
    # _do_watch has installed its handlers -- is not lost: _do_watch unblocks
    # them once the handlers are in place and a pending one fires immediately.
    #
    # SIGTERM/SIGINT used to be SIG_IGN'd here instead, on the wrong belief
    # that an ignored signal is "held" across exec -- it is DISCARDED. A
    # stop() landing in the exec window (perl startup + module load in the
    # fresh watcher) was silently dropped, the watcher never learned it should
    # stop, and the caller's wait() eventually gave up and killed the watcher,
    # orphaning a still-running server whose data dir stayed locked. The
    # build-then-immediately-stop pattern (DBIx::QuickDB::Pool) hit that
    # window constantly.
    POSIX::sigprocmask(POSIX::SIG_BLOCK(), POSIX::SigSet->new(POSIX::SIGUSR1(), POSIX::SIGINT(), POSIX::SIGTERM()));

    exec(
        $^X, _watcher_inc_args(),

        '-e' => "require DBIx::QuickDB::Watcher; DBIx::QuickDB::Watcher->_do_watch()",

        master_pid  => $mpid,

lib/DBIx/QuickDB/Watcher.pm  view on Meta::CPAN


    my %params = @ARGV;

    my $kill = $params{kill} // '';
    my $hup  = $params{hup}  // 0;
    local $SIG{TERM} = sub { $kill = 'TERM' };
    local $SIG{INT}  = sub { $kill = 'INT' };
    local $SIG{USR1} = sub { $kill = 'FAST_TERM' };
    local $SIG{HUP}  = sub { $hup  = 1 };

    # watch() blocked the teardown signals before exec so a stop/eliminate/
    # fast_eliminate racing startup stays pending rather than being discarded.
    # Now that the handlers above are installed, unblock them -- any pending
    # teardown fires here and sets $kill before we enter the watch loop.
    POSIX::sigprocmask(POSIX::SIG_UNBLOCK(), POSIX::SigSet->new(POSIX::SIGUSR1(), POSIX::SIGINT(), POSIX::SIGTERM()));

    my $blah;
    close(STDIN);
    open(STDIN, '<', \$blah) or warn "$!";

    my $master_pid  = $params{master_pid} or die "No master pid provided";

lib/DBIx/QuickDB/Watcher.pm  view on Meta::CPAN

    # escalates to SIGKILL on the server after QDB_STOP_GRACE and then BLOCKS
    # until the server is reaped, so this must outlast the watcher's own
    # escalation schedule (grace + grace/2, plus slack).
    my $timeout = _stop_grace() * 2 + 2;

    # A watcher that outlives $timeout is almost never hung -- the usual
    # cause is a server the kernel has not been able to kill yet (e.g. stuck
    # in disk-sleep under heavy I/O), with the watcher dutifully blocking on
    # the post-SIGKILL reap. Killing the watcher at that point orphans a
    # still-alive server, so stop() would return "success" while the data dir
    # is locked by a live postmaster/mysqld and the next start on that dir
    # fails on the stale lock file. So past $timeout we only warn, keep
    # waiting on a much longer leash, and SIGKILL the watcher purely as a
    # last resort. Tunable via QDB_STOP_LEASH (extra seconds past $timeout).
    my $extra = $ENV{QDB_STOP_LEASH};
    $extra = 60 unless defined($extra) && $extra =~ /^\d+$/ && $extra > 0;
    my $leash = $timeout + $extra;

    my ($warned, $nuked);
    my $start = time;
    while(kill(0, $pid)) {

lib/DBIx/QuickDB/Watcher.pm  view on Meta::CPAN

    # reaped the server; the pid may since have been recycled, and probing it
    # would report a false "alive" and stall for the whole timeout. A missing
    # data dir proves nothing here -- teardown removes a disposable dir whether
    # or not it managed to stop the server.
    $self->_latch_server_reaped;

    if (!$self->{+SERVER_REAPED} and my $spid = $self->{+SERVER_PID}) {
        my $sstart = time;
        while (kill(0, $spid)) {
            if (time - $sstart > $timeout) {
                warn "Server (pid $spid) still appears to be alive after its watcher exited; its data dir may still be locked";
                last;
            }
            sleep 0.02;
        }
    }
}

sub DESTROY {
    my $self = shift;



( run in 1.548 second using v1.01-cache-2.11-cpan-800906f7e73 )