Data-PerfectHash-Shared
view release on metacpan or search on metacpan
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 3.353 seconds using v1.01-cache-2.11-cpan-14f38c9f855 )