Data-PerfectHash-Shared

 view release on metacpan or  search on metacpan

chd.h  view on Meta::CPAN

    for (size_t i=0;i<N;i++){
        uint64_t bk = ph_hash_of(b, idx[i], seed0) % r;
        uint64_t slot = ph_hash_of(b, idx[i], ph_mix(seed0, disp[bk])) % n;
        if (b->key_type==PH_TYPE_INT) ((int64_t*)(img+slots_off))[slot] = b->ints[idx[i]];
        else { PhStrSlot *ss=&((PhStrSlot*)(img+slots_off))[slot]; size_t len=b->sslots[idx[i]].len;
               memcpy(img+arena_off+awrite, b->arena+b->sslots[idx[i]].off, len);
               ss->off=awrite; ss->len=len; awrite+=len; }
    }
    /* Atomic write: build a temp file in the SAME directory, then rename(2) it
     * over `path`. Readers mmap `path`, and O_TRUNC on a mapped file would
     * SIGBUS them; rename instead leaves the old inode alive until they unmap.
     * mkstemp creates it O_CREAT|O_EXCL 0600 (no symlink to follow, so unlike
     * ph_open's read side this needs no O_NOFOLLOW); fchmod applies the requested mode after. */
    size_t plen = strlen(path);
    char *tmp = (char*)malloc(plen + 12);
    if (!tmp) { snprintf(err,el,"out of memory"); PHB_FREEALL(); return -1; }
    memcpy(tmp, path, plen); memcpy(tmp+plen, ".tmpXXXXXX", 11);   /* 10 chars + NUL */
    int fd = mkstemp(tmp);                                          /* creates 0600, O_EXCL; fills XXXXXX */
    if (fd < 0) { snprintf(err,el,"mkstemp %s: %s", tmp, strerror(errno)); free(tmp); PHB_FREEALL(); return -1; }
    int rc = 0;
    if (fchmod(fd, mode) != 0) { snprintf(err,el,"fchmod: %s", strerror(errno)); rc=-1; }

t/07_atomic_rebuild.t  view on Meta::CPAN

use strict; use warnings; use Test::More; use Config; use POSIX (); use File::Temp 'tempdir';
use Data::PerfectHash::Shared;

# F1: rebuild-over-a-mapped-file must be atomic (temp file + rename), never an
# in-place O_TRUNC rewrite of the very inode a reader has mmap'd. A process that
# has load()ed an image keeps the OLD inode alive (open fd + mapping); rebuilding
# the same path must leave that reader seeing the OLD data, not crash it with
# SIGBUS (which is what O_TRUNC on a mapped file does) and not silently show it
# the NEW file's bytes. This runs under xt/asan.t too (it globs t/*.t).
#
# The post-rebuild query is isolated in a forked child: a SIGBUS is a signal,
# not a Perl exception eval{} can trap, so we inspect how the child died. Same
# fork / POSIX::_exit / ($? & 127) pattern as t/02_corrupt.t.
plan skip_all => 'fork required' unless $Config{d_fork};

my $dir  = tempdir(CLEANUP => 1);
my $path = "$dir/rebuild.phs";

# 1. build the OLD image ([1..200]) and load (mmap) it.
Data::PerfectHash::Shared->build_int($path, [1 .. 200]);
my $set = Data::PerfectHash::Shared->load($path);
ok  $set->has(50),   'sanity: old member 50 present before rebuild';
ok !$set->has(1000), 'sanity: 1000 absent before rebuild';

# 2. REBUILD the same path with a DISJOINT key set ([1000..1200]). With the
#    atomic temp+rename build this creates a NEW inode and renames it over
#    $path; the OLD inode stays alive for the already-loaded $set. With the old
#    O_TRUNC write it would truncate/rewrite the inode $set has mapped.
Data::PerfectHash::Shared->build_int($path, [1000 .. 1200]);

# 3. query the OLD mapping in a child; it MUST still answer from the OLD data.
my $rf  = "$dir/result";
my $kid = fork;
plan skip_all => "fork failed: $!" unless defined $kid;
if (!$kid) {
    my $old = $set->has(50)   ? 1 : 0;   # old member -> must stay 1
    my $new = $set->has(1000) ? 1 : 0;   # new-file key -> must stay 0 via old inode



( run in 2.594 seconds using v1.01-cache-2.11-cpan-14f38c9f855 )