--- deliantra/Deliantra-Client/DC/DB.pm 2008/07/18 22:40:51 1.38 +++ deliantra/Deliantra-Client/DC/DB.pm 2011/07/29 08:35:35 1.46 @@ -14,14 +14,15 @@ package DC::DB; -use strict; -use utf8; +use common::sense; use File::Path (); use Carp (); use Storable (); +use AnyEvent::Util (); use Config; use BDB; +use Fcntl (); use DC; @@ -29,6 +30,8 @@ our $DBDIR = "client-" . BDB::VERSION_MAJOR . "." . BDB::VERSION_MINOR . "-$Config{archname}"; our $DB_HOME = "$Deliantra::VARDIR/$DBDIR"; +sub FIRST_TILE_ID () { 64 } + unless (-d $DB_HOME) { if (-d "$Deliantra::VARDIR/$ODBDIR") { rename "$Deliantra::VARDIR/$ODBDIR", $DB_HOME; @@ -46,20 +49,81 @@ BDB::max_parallel 1; our $DB_ENV; +our $DB_ENV_FH; our $DB_STATE; our %DB_TABLE; our $TILE_SEQ; +sub all_databases { + opendir my $fh, $DB_HOME + or return; + + grep !/^(?:\.|log\.|_)/, readdir $fh +} + +sub try_verify_env($) { + my ($env) = @_; + + open my $lock, "+>$DB_HOME/__lock" + or die "__lock: $!"; + + flock $lock, &Fcntl::LOCK_EX + or die "flock: $!"; + + # we look at the __db.register env file that has been created by now + # and check for the number of registered processes - if there is + # only one, we verify all databases, otherwise we skip this + # we MUST NOT close the filehandle as longa swe keep the env open, as + # this destroys the record locks on it. + open $DB_ENV_FH, "<$DB_HOME/__db.register" + or die "__db.register: $!"; + + # __db.register contains one record per process, with X signifying + # empty records (of course, this is completely private to bdb...) + my $count = grep /^[^X]/, <$DB_ENV_FH>; + + if ($count == 1) { + # if any databases are corrupted, we simply delete all of them + + for (all_databases) { + my $dbh = db_create $env + or last; + + # a failed verify will panic the environment, which is fine with us + db_verify $dbh, "$DB_HOME/$_"; + + return if $!; # nuke database and recreate if verification failure + } + + } + + # close probably cleans those up, but we also want to run on windows, + # so better be safe. + flock $lock, &Fcntl::LOCK_UN + or die "funlock: $!"; + + 1 +} + sub try_open_db { File::Path::mkpath [$DB_HOME]; + undef $DB_ENV; + undef $DB_ENV_FH; + my $env = db_env_create; $env->set_errfile (\*STDERR); $env->set_msgfile (\*STDERR); $env->set_verbose (-1, 1); - $env->set_flags (BDB::AUTO_COMMIT | BDB::LOG_AUTOREMOVE | BDB::TXN_WRITE_NOSYNC); + $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; + + $env->set_timeout (3, BDB::SET_TXN_TIMEOUT); + $env->set_timeout (3, BDB::SET_LOCK_TIMEOUT); + $env->set_cachesize (0, 2048 * 1024, 0); db_env_open $env, $DB_HOME, @@ -68,6 +132,11 @@ $! and die "cannot open database environment $DB_HOME: " . BDB::strerror; + # now we go through the registered processes, if there is only one, we verify all files + # to make sure windows didn't corrupt them (as windows does....) + try_verify_env $env + or die "database environment failed verification"; + $DB_ENV = $env; 1 @@ -79,6 +148,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); @@ -152,7 +224,7 @@ unless ($TILE_SEQ) { $TILE_SEQ = $table->sequence; - $TILE_SEQ->initial_value (64); + $TILE_SEQ->initial_value (FIRST_TILE_ID); $TILE_SEQ->set_cachesize (0); db_sequence_open $TILE_SEQ, undef, "id", BDB::CREATE; } @@ -217,7 +289,7 @@ package DC::DB::Server; -use strict; +use common::sense; use EV (); use Fcntl; @@ -346,7 +418,8 @@ } sub run { - ($FH, my $fh) = DC::socketpipe; + ($FH, my $fh) = AnyEvent::Util::portable_socketpair + or die "unable to create database socketpair: $!"; my $oldfh = select $FH; $| = 1; select $oldfh; my $oldfh = select $fh; $| = 1; select $oldfh; @@ -406,6 +479,9 @@ package DC::DB; sub nuke_db { + undef $DB_ENV; + undef $DB_ENV_FH; + File::Path::mkpath [$DB_HOME]; eval { File::Path::rmtree $DB_HOME }; }