#################### GDBM_File
package gdbm;
use strict;
use warnings;
use GDBM_File;
use constant CACHE_FILE => '/tmp/gdbm';
sub new {
my $class = shift;
my $gdbm = GDBM_File->new(
CACHE_FILE,
GDBM_WRCREAT | GDBM_NOLOCK,
oct( '0640' ),
) || die "Could not create GDBM_File - $!";
return bless { dbm => $gdbm }, $class;
}
sub set {
my( $self, $key, $val ) = @_;
return $self->{dbm}->STORE( $key, $val );
}
sub get {
my( $self, $key ) = @_;
return $self->{dbm}->FETCH( $key );
}
sub all_keys {
my( $self ) = @_;
# warn '$self = ', $self;
my @keys;
my $k = $self->{dbm}->FIRSTKEY;
if( $k ) {
do {
# warn '$k = ', $k || 'undef';
my $v = $self->{dbm}->FETCH( $k );
# warn sprintf( '%s => %s', $k, Dumper( $v ) );
push @keys, $k if $k;
} while( $k = $self->{dbm}->NEXTKEY( $k ) );
}
return @keys;
}
sub init {
unlink CACHE_FILE;
}
#################### BerkeleyDB w/o Env
package bdb;
use strict;
use warnings;
use BerkeleyDB;
use constant CACHE_FILE => '/tmp/bdb';
sub new {
my $class = shift;
my $bdb = BerkeleyDB::Btree->new(
-Filename => CACHE_FILE,
-Flags => DB_CREATE
) || die "Could not create BerkeleyDB - $!";
return bless { dbm => $bdb }, $class;
}
sub set {
my( $self, $key, $val ) = @_;
$self->{dbm}->db_put( $key, $val );
$self->{dbm}->db_sync;
return;
}
sub get {
my( $self, $key ) = @_;
$self->{dbm}->db_get( $key, my $val );
return $val;
}
sub all_keys {
my( $self ) = @_;
my( $cursor ) = $self->{dbm}->db_cursor();
my @keys;
my( $k, $v ) = ( '', '' );
while( $cursor->c_get( $k, $v, DB_NEXT ) == 0 ) {
push @keys, $k;
}
return @keys;
}
sub init {
unlink CACHE_FILE, glob( '/tmp/__db.*' );
}
#################### BerkeleyDB w/Env
package bdbenv;
use strict;
use warnings;
use base 'bdb';
# This is the only one that works as desired...
# But has sporadic issues after running for serveral hours.
sub new {
my $class = shift;
my $env = BerkeleyDB::Env->new(
-Home => '/tmp',
-Flags => __PACKAGE__->DB_CREATE | __PACKAGE__->DB_INIT_MPOOL,
-Mode => oct( '0644' ),
) || die "Could not create BerkeleyDB::Env - '$BerkeleyDB::Error' - '$!'";
my $bdb = BerkeleyDB::Btree->new(
-Filename => __PACKAGE__->CACHE_FILE,
-Flags => __PACKAGE__->DB_CREATE,
-Mode => oct( '0644' ),
-Env => $env,
) || die "Could not create BerkeleyDB::Btree - '$BerkeleyDB::Error' - '$!'";
return bless { dbm => $bdb }, $class;
}
#################### Running code and utility subs.
package main;
use strict;
use warnings;
use POSIX();
use IO::Socket::INET;
use Time::HiRes();
use constant DBM_PACKAGE => 'gdbm';
# use constant DBM_PACKAGE => 'bdb';
# use constant DBM_PACKAGE => 'bdbenv';
sub all_letters {
return ( 'a' .. 'z', 'A' .. 'Z' );
}
sub random_sleep {
return Time::HiRes::sleep( ( .001, .002, .003, .004, .005, )[ int( rand 5 ) ] );
}
sub random_letter {
return ( all_letters() )[ int rand 52 ];
}
# Server process dies after 1000 requests and is replace by the parent.
sub start_server_process {
my( $socket ) = @_;
my $pid = fork;
if( not $pid ) {
my $db = DBM_PACKAGE->new(); # XXX # Switch these two around and it works for GDBM_File and BerkeleyDB w/o Env
for( 1 .. 1000 ) {
my $client = $socket->accept;
# my $db = DBM_PACKAGE->new(); # XXX # Switch these two around and it works for GDBM_File and BerkeleyDB w/o Env
my $output = "$$\n";
for my $k( $db->all_keys ) {
my $v = $db->get( $k );
$output .= "$k -> $v\n";
}
$client->send( $output );
}
# warn $$, ' Server child exiting...';
exit;
}
return $pid;
}
sub start_reader_process {
my $pid = fork;
if( not $pid ) {
$SIG{__WARN__} = sub { syswrite( STDERR, sprintf( '%s %s', $$, join( q[, ], @_ ) ) ); };
$SIG{__DIE__} = 'IGNORE';
my $db = DBM_PACKAGE->new();
sleep 1;
while( 1 ) {
my $socket = IO::Socket::INET->new(
PeerAddr => '127.0.0.1:54321',
) || Carp::confess 'No $socket: ', $!;
$socket->recv( my $message, 4096 );
random_sleep();
}
warn $$, ' Reader child exiting...';
exit;
}
return $pid;
}
sub run {
my %options = @_;
if( $options{daemonize} ) {
POSIX::setsid();
fork and exit;
}
if( not %options or $options{start} ) {
POSIX::setsid;
DBM_PACKAGE->init;
# Listen for UDP packets and add them to the cache.
if( not fork ) {
my $dbm = DBM_PACKAGE->new;
my $socket = IO::Socket::INET->new(
Proto => 'udp',
LocalAddr => '127.0.0.1:12345',
ReuseAddr => 1,
) || Carp::confess 'No $socket: ', $!;
while( 1 ) {
$socket->recv( my $message, 4096, );
my $time = time;
warn $$, ' adding message ', $message, ' time ', $time;
$dbm->set( $message, $time );
}
warn $$, ' Listener exiting...';
exit;
}
warn $$, ' Started listener...';
# Start listening socket in parent.
my $socket = IO::Socket::INET->new(
Proto => 'tcp',
LocalAddr => '127.0.0.1:54321',
Listen => 1,
ReuseAddr => 1,
) || Carp::confess 'No $socket: ', $!;
for( 1 .. 10 ) {
# Listen for TCP connections and send them the contents of the cache.
warn $$, ' Parent started server ', start_server_process( $socket );
}
warn $$, ' Started servers...';
if( not $options{noreaders} ) {
for ( 1 .. 40 ) {
# Make TCP connections to the above listeners for simulated load.
warn $$, ' Parent started reader ', start_reader_process();
}
warn $$, ' Started readers...';
}
else {
warn $$, ' Skipped readers...';
}
$SIG{__DIE__} = sub { kill 'TERM', -$$; };
while( my $pid = wait ) {
last if $pid == -1;
# start_server_process( $socket );
warn $$, ' Parent reaped server ', $pid, ' Parent started server ', start_server_process( $socket );
}
warn $$, ' Parent exiting...';
exit;
}
elsif( $options{readers} ) {
POSIX::setsid();
for ( 1 .. 10 ) {
# Make TCP connections to the above listeners for simulated load.
warn $$, ' Parent started reader ', start_reader_process();
}
1 while wait != -1;
}
elsif( my $message = $options{message} ) {
my $socket = IO::Socket::INET->new(
Proto => 'udp',
PeerAddr => '127.0.0.1:12345',
ReuseAddr => 1,
) || Carp::confess 'No $socket: ', $!;
$socket->send( $message );
}
elsif( $options{dump} ) {
my $socket = IO::Socket::INET->new(
PeerAddr => '127.0.0.1:54321',
) || Carp::confess 'No $socket: ', $!;
$socket->recv( my $message, 4096 );
print $message, "\n";
}
}
1;
__END__
####
perl -Mtest_dbm_1 -e 'run( start => 1, );'
####
perl -Mtest_dbm_1 -e 'run( message => random_letter() x 10, );' ;
perl -Mtest_dbm_1 -e 'run( dump => 1, );'