--- deliantra/Deliantra-Client/DC/DB.pm 2008/03/30 06:05:52 1.35 +++ deliantra/Deliantra-Client/DC/DB.pm 2011/08/13 23:18:19 1.47 @@ -14,21 +14,24 @@ 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; -our $ODBDIR = "cfplus-" . BDB::VERSION . "-$Config{archname}"; -our $DBDIR = "client-" . 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"; +sub FIRST_TILE_ID () { 64 } + unless (-d $DB_HOME) { if (-d "$Deliantra::VARDIR/$ODBDIR") { rename "$Deliantra::VARDIR/$ODBDIR", $DB_HOME; @@ -46,28 +49,96 @@ 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]; - $DB_ENV = db_env_create; + 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); - $DB_ENV->set_errfile (\*STDERR); - $DB_ENV->set_msgfile (\*STDERR); - $DB_ENV->set_verbose (-1, 1); + $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_flags (BDB::AUTO_COMMIT | BDB::LOG_AUTOREMOVE | BDB::TXN_WRITE_NOSYNC); - $DB_ENV->set_cachesize (0, 2048 * 1024, 0); + $env->set_timeout (3, BDB::SET_TXN_TIMEOUT); + $env->set_timeout (3, BDB::SET_LOCK_TIMEOUT); - db_env_open $DB_ENV, $DB_HOME, + $env->set_cachesize (0, 2048 * 1024, 0); + + 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; + # 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 } @@ -77,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); @@ -150,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; } @@ -215,9 +289,10 @@ package DC::DB::Server; -use strict; +use common::sense; use EV (); +use AnyEvent (); use Fcntl; our %CB; @@ -344,7 +419,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; @@ -393,7 +469,7 @@ $CB{die} = sub { die shift }; - $fh_r_watcher = EV::io $FH, EV::READ , \&fh_read; + $fh_r_watcher = AE::io $FH, 0, \&fh_read; $fh_w_watcher = EV::io $FH, EV::WRITE, \&fh_write; } @@ -404,6 +480,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 }; } @@ -428,7 +507,7 @@ }; } - $WATCHER = EV::io BDB::poll_fileno, EV::READ, \&BDB::poll_cb; + $WATCHER = AE::io BDB::poll_fileno, 0, \&BDB::poll_cb; $SYNC = EV::timer_ns 0, 60, sub { $_[0]->stop; db_env_txn_checkpoint $DB_ENV, 0, 0, 0, sub { }; @@ -436,6 +515,9 @@ } END { + db_env_txn_checkpoint $DB_ENV, 0, 0, 0 + if $DB_ENV; + undef $TILE_SEQ; %DB_TABLE = (); undef $DB_ENV;