--- deliantra/Deliantra-Client/DC/DB.pm 2007/12/26 20:46:39 1.28 +++ deliantra/Deliantra-Client/DC/DB.pm 2008/12/24 04:09:27 1.41 @@ -1,10 +1,10 @@ =head1 NAME -dc::DB - async. database and filesystem access for cfplus +DC::DB - async. database and filesystem access for deliantra =head1 SYNOPSIS - use dc::DB; + use DC::DB; =head1 DESCRIPTION @@ -12,48 +12,72 @@ =cut -package dc::DB; +package DC::DB; use strict; use utf8; +use File::Path (); use Carp (); use Storable (); use Config; use BDB; -use dc; +use DC; -our $DBDIR = "cfplus-" . BDB::VERSION . "-$Config{archname}"; +our $ODBDIR = "cfplus-" . BDB::VERSION_MAJOR . "." . BDB::VERSION_MINOR . "-$Config{archname}"; +our $DBDIR = "client-" . BDB::VERSION_MAJOR . "." . BDB::VERSION_MINOR . "-$Config{archname}"; our $DB_HOME = "$Deliantra::VARDIR/$DBDIR"; -if (!-e $DB_HOME and -e "$Deliantra::OLDDIR/$DBDIR") { - rename "$Deliantra::OLDDIR/$DBDIR", $DB_HOME; - print STDERR "INFO: moved old database from $Deliantra::OLDDIR/$DBDIR to $DB_HOME\n"; +sub FIRST_TILE_ID () { 64 } + +unless (-d $DB_HOME) { + if (-d "$Deliantra::VARDIR/$ODBDIR") { + rename "$Deliantra::VARDIR/$ODBDIR", $DB_HOME; + print STDERR "INFO: moved old database from $Deliantra::VARDIR/$ODBDIR to $DB_HOME\n"; + } elsif (-d "$Deliantra::OLDDIR/$ODBDIR") { + rename "$Deliantra::OLDDIR/$DBDIR", $DB_HOME; + print STDERR "INFO: moved old database from $Deliantra::OLDDIR/$ODBDIR to $DB_HOME\n"; + } else { + File::Path::mkpath [$DB_HOME] + or die "unable to create database directory $DB_HOME: $!"; + } } +BDB::max_poll_time 0.03; +BDB::max_parallel 1; + our $DB_ENV; our $DB_STATE; our %DB_TABLE; +our $TILE_SEQ; -sub open_db { - mkdir $DB_HOME, 0777; +sub try_open_db { + File::Path::mkpath [$DB_HOME]; + + my $env = db_env_create; + + $env->set_errfile (\*STDERR); + $env->set_msgfile (\*STDERR); + $env->set_verbose (-1, 1); - $DB_ENV = db_env_create; + $env->set_flags (BDB::AUTO_COMMIT | BDB::REGION_INIT); + $env->set_flags (&BDB::LOG_AUTOREMOVE ) if BDB::VERSION v0, v4.7; + $env->log_set_config (&BDB::LOG_AUTO_REMOVE) if BDB::VERSION v4.7; - $DB_ENV->set_errfile (\*STDERR); - $DB_ENV->set_msgfile (\*STDERR); - $DB_ENV->set_verbose (-1, 1); + $env->set_timeout (3, BDB::SET_TXN_TIMEOUT); + $env->set_timeout (3, BDB::SET_LOCK_TIMEOUT); - $DB_ENV->set_flags (BDB::AUTO_COMMIT | BDB::LOG_AUTOREMOVE | BDB::TXN_WRITE_NOSYNC); - $DB_ENV->set_cachesize (0, 2048 * 1024, 0); + $env->set_cachesize (0, 2048 * 1024, 0); - db_env_open $DB_ENV, $DB_HOME, + db_env_open $env, $DB_HOME, BDB::CREATE | BDB::REGISTER | BDB::RECOVER | BDB::INIT_MPOOL | BDB::INIT_LOCK | BDB::INIT_TXN, 0666; $! and die "cannot open database environment $DB_HOME: " . BDB::strerror; + $DB_ENV = $env; + 1 } @@ -63,6 +87,9 @@ $table =~ s/([^a-zA-Z0-9_\-])/sprintf "=%x=", ord $1/ge; + $DB_ENV#d# + or return ::clienterror ("trying to create table $_[0] with empty db_env $DB_ENV" => 1);#d# + my $db = db_create $DB_ENV; $db->set_flags (BDB::CHKSUM); @@ -77,20 +104,9 @@ ############################################################################# -unless (eval { open_db }) { - warn "$@";#d# - eval { File::Path::rmtree $DB_HOME }; - open_db; -} - -our $WATCHER = EV::io BDB::poll_fileno, EV::READ, \&BDB::poll_cb; - -our $SYNC = EV::timer_ns 0, 60, sub { - $_[0]->stop; - db_env_txn_checkpoint $DB_ENV, 0, 0, 0, sub { }; -}; - -our $tilemap; +our $WATCHER; +our $SYNC; +our $facemap; sub exists($$$) { my ($db, $key, $cb) = @_; @@ -142,48 +158,30 @@ my $table = table "facemap"; my $id; - db_get $table, undef, $name, $id, 0; - return $cb->($id) unless $!; - - for (1..100) { - my $txn = $DB_ENV->txn_begin; - db_get $table, $txn, id => $id, 0; - - $id = 64 if $id < 64; + db_get $table, undef, $name => $id, 0; + $! or return $cb->($id); - ++$id; - - db_put $table, $txn, id => $id, 0; - db_txn_finish $txn; - - $SYNC->again unless $SYNC->is_active; + unless ($TILE_SEQ) { + $TILE_SEQ = $table->sequence; + $TILE_SEQ->initial_value (FIRST_TILE_ID); + $TILE_SEQ->set_cachesize (0); + db_sequence_open $TILE_SEQ, undef, "id", BDB::CREATE; + } - return $cb->($id) unless $!; + db_sequence_get $TILE_SEQ, undef, 1, my $id; - select undef, undef, undef, 0.01 * rand; - } + die "unable to allocate tile id: $!" + if $!; + + db_put $table, undef, $name => $id, 0; + $cb->($id); - die "maximum number of transaction retries reached - database problems?"; } sub get_tile_id_sync($) { my ($name) = @_; - # fetch the full face table first - unless ($tilemap) { - do_table facemap => sub { - $tilemap = $_[0]; - delete $tilemap->{id}; - my %maptile = reverse %$tilemap;#d# - if ((scalar keys %$tilemap) != (scalar keys %maptile)) {#d# - $tilemap = { };#d# - dc::error "FATAL: facemap is not a 1:1 mapping, please report this and delete your $DB_HOME directory!\n";#d# - }#d# - }; - BDB::flush; - } - - $tilemap->{$name} ||= do { + $facemap->{$name} ||= do { my $id; do_get_tile_id $name, sub { $id = $_[0]; @@ -202,31 +200,33 @@ sub sync { # for debugging - #dc::DB::Server::req (sync => sub { }); - dc::DB::Server::sync (); + #DC::DB::Server::req (sync => sub { }); + DC::DB::Server::sync (); } sub unlink($$) { - dc::DB::Server::req (unlink => @_); + DC::DB::Server::req (unlink => @_); } sub read_file($$) { - dc::DB::Server::req (read_file => @_); + DC::DB::Server::req (read_file => @_); } sub write_file($$$) { - dc::DB::Server::req (write_file => @_); + DC::DB::Server::req (write_file => @_); } sub prefetch_file($$$) { - dc::DB::Server::req (prefetch_file => @_); + DC::DB::Server::req (prefetch_file => @_); } sub logprint($$$) { - dc::DB::Server::req (logprint => @_); + DC::DB::Server::req (logprint => @_); } -package dc::DB::Server; +############################################################################# + +package DC::DB::Server; use strict; @@ -357,7 +357,7 @@ } sub run { - ($FH, my $fh) = dc::socketpipe; + ($FH, my $fh) = DC::socketpipe; my $oldfh = select $FH; $| = 1; select $oldfh; my $oldfh = select $fh; $| = 1; select $oldfh; @@ -365,7 +365,7 @@ my $pid = fork; if (defined $pid && !$pid) { - local $SIG{QUIT}; + local $SIG{QUIT} = "IGNORE"; local $SIG{__DIE__}; local $SIG{__WARN__}; eval { @@ -381,7 +381,7 @@ $req = Storable::thaw $req; my ($id, $type, @args) = @$req; - my $cb = dc::DB::Server->can ("do_$type") + my $cb = DC::DB::Server->can ("do_$type") or die "$type: unknown database request type\n"; my $res = pack "N/a*", Storable::freeze [$id, $cb->(@args)]; (syswrite $fh, $res) == length $res @@ -398,11 +398,11 @@ warn $error if $error; - dc::_exit 0; + DC::_exit 0; } close $fh; - dc::fh_nonblocking $FH, 1; + DC::fh_nonblocking $FH, 1; $CB{die} = sub { die shift }; @@ -414,6 +414,49 @@ close $FH; } +package DC::DB; + +sub nuke_db { + File::Path::mkpath [$DB_HOME]; + eval { File::Path::rmtree $DB_HOME }; +} + +sub open_db { + unless (eval { try_open_db }) { + warn "$@";#d# + eval { nuke_db }; + try_open_db; + } + + # fetch the full face table first + unless ($facemap) { + do_table facemap => sub { + $facemap = $_[0]; + delete $facemap->{id}; + my %maptile = reverse %$facemap;#d# + if ((scalar keys %$facemap) != (scalar keys %maptile)) {#d# + $facemap = { };#d# + DC::error "FATAL: facemap is not a 1:1 mapping, please report this and delete your $DB_HOME directory!\n";#d# + }#d# + }; + } + + $WATCHER = EV::io BDB::poll_fileno, EV::READ, \&BDB::poll_cb; + $SYNC = EV::timer_ns 0, 60, sub { + $_[0]->stop; + db_env_txn_checkpoint $DB_ENV, 0, 0, 0, sub { }; + }; +} + +END { + db_env_txn_checkpoint $DB_ENV, 0, 0, 0 + if $DB_ENV; + + undef $TILE_SEQ; + %DB_TABLE = (); + undef $DB_ENV; +} + 1; =back