Data-HashMap-Shared
view release on metacpan or search on metacpan
xt/edge-cases.t view on Meta::CPAN
# mutate the returned value
$got .= " world";
is($got, "hello world", 'local mutation works');
# shared value should be unchanged
my $stored = shm_ss_get $map, "key1";
is($stored, "hello", 'shared value unchanged after mutating returned SV');
# second call should return existing value (also a copy)
my $got2 = shm_ss_get_or_set $map, "key1", "other";
is($got2, "hello", 'get_or_set returns existing value on second call');
unlink $path;
}
# put_ttl with ttl_sec=0 creates permanent entry on TTL-enabled map
{
my $path = tmpfile();
my $map = Data::HashMap::Shared::II->new($path, 1000, 0, 2);
shm_ii_put_ttl $map, 1, 100, 0; # permanent
shm_ii_put $map, 2, 200; # default TTL (2s)
my $rem1 = shm_ii_ttl_remaining $map, 1;
is($rem1, 0, 'ttl_remaining is 0 for permanent entry');
my $rem2 = shm_ii_ttl_remaining $map, 2;
ok(defined $rem2 && $rem2 > 0, 'ttl_remaining > 0 for TTL entry');
sleep 3;
# permanent entry survives
my $v1 = shm_ii_get $map, 1;
is($v1, 100, 'permanent entry (ttl=0) survives past default TTL');
# TTL entry expired
my $v2 = shm_ii_get $map, 2;
ok(!defined $v2, 'default TTL entry expired');
unlink $path;
}
# stale write lock recovery: simulate dead process holding wrlock
{
my $path = tmpfile();
my $map = Data::HashMap::Shared::II->new($path, 1000);
shm_ii_put $map, 1, 42;
# Simulate a stale lock by forking a child that sets the lock and dies
my $pid = fork();
if ($pid == 0) {
my $child_map = Data::HashMap::Shared::II->new($path, 1000);
# Do a normal put to prove the child can access the map
shm_ii_put $child_map, 2, 99;
POSIX::_exit(0);
}
waitpid($pid, 0);
# Now manually corrupt the lock to simulate the child dying while holding it.
# The header is at the start of the mmap file. rwlock is at offset 128
# and encodes 0x80000000 | pid when write-locked. seq is at offset 64.
open my $fh, '+<:raw', $path or die "Cannot open $path: $!";
# Set rwlock = 0x80000000 | $pid (write-locked by dead child)
seek($fh, 128, 0);
print $fh pack('V', 0x80000000 | $pid);
# Set seq to odd (writer active)
seek($fh, 64, 0);
print $fh pack('V', 1);
close $fh;
# Re-open the map â the stale lock is now in the mmap
undef $map;
$map = Data::HashMap::Shared::II->new($path, 1000);
# This should recover after SHM_LOCK_TIMEOUT_SEC (2s) since $pid is dead
my $val = shm_ii_get $map, 1;
# The data written before corruption should still be readable
is($val, 42, 'recovered from stale lock without deadlock');
ok(shm_ii_stat_recoveries($map) > 0, 'stat_recoveries incremented after recovery');
unlink $path;
}
# error diagnostics: wrong variant gives informative message
{
my $path = tmpfile();
my $map = Data::HashMap::Shared::II->new($path, 100);
shm_ii_put $map, 1, 1;
undef $map;
eval { Data::HashMap::Shared::SS->new($path, 100) };
like($@, qr/variant mismatch/, 'variant mismatch gives diagnostic error');
like($@, qr/file=\d+, expected=\d+/, 'error includes variant IDs');
unlink $path;
}
# error diagnostics: bad path gives errno message
{
eval { Data::HashMap::Shared::II->new('/nonexistent/path/test.shm', 100) };
like($@, qr/No such file|Permission denied/, 'bad path gives errno in error');
}
# unlink: instance method
{
my $path = tmpfile();
my $map = Data::HashMap::Shared::II->new($path, 100);
shm_ii_put $map, 1, 42;
ok(-f $path, 'backing file exists');
ok($map->unlink, 'instance unlink returns true');
ok(!-f $path, 'backing file removed after unlink');
# map still works (mmap stays alive after unlink)
my $v = shm_ii_get $map, 1;
is($v, 42, 'map still readable after unlink');
}
# unlink: class method
{
my $path = tmpfile();
my $map = Data::HashMap::Shared::II->new($path, 100);
undef $map;
ok(-f $path, 'backing file exists before class unlink');
ok(Data::HashMap::Shared::II->unlink($path), 'class unlink returns true');
( run in 0.341 second using v1.01-cache-2.11-cpan-e1769b4cff6 )