--- deliantra/server/lib/cf.pm 2007/02/11 22:42:09 1.207 +++ deliantra/server/lib/cf.pm 2007/02/17 03:19:44 1.219 @@ -24,6 +24,9 @@ use IO::AIO 2.32 (); use YAML::Syck (); use Time::HiRes; +use Compress::LZF; + +Compress::LZF::sfreeze_cr { }; # prime Compress::LZF so it does not use require later use Event; $Event::Eval = 1; # no idea why this is required, but it is @@ -48,7 +51,7 @@ our $TICK = MAX_TIME * 1e-6; our $TICK_WATCHER; our $AIO_POLL_WATCHER; -our $WRITE_RUNTIME_WATCHER; +our $NEXT_RUNTIME_WRITE; # when should the runtime file be written our $NEXT_TICK; our $NOW; our $USE_FSYNC = 1; # use fsync to write maps - default off @@ -272,9 +275,7 @@ my $guard = Coro::guard { $TICK_WATCHER->start; - $WRITE_RUNTIME_WATCHER->start; }; - $WRITE_RUNTIME_WATCHER->stop; $TICK_WATCHER->stop; $guard } @@ -355,6 +356,8 @@ } sub write_runtime { + my $guard = cf::lock_acquire "write_runtime"; + my $runtime = cf::localdir . "/runtime"; my $fh = aio_open "$runtime~", O_WRONLY | O_CREAT, 0644 @@ -786,12 +789,6 @@ aio_unlink $filename; aio_unlink "$filename.pst"; } - - #d##TODO# nuke non .map-files if exist - if ($filename =~ s/\.map$//) { - aio_unlink $filename; - aio_unlink "$filename.pst"; - } } } @@ -808,9 +805,6 @@ my ($data, $av); - #d#TODO remove .map if file does not exist - aio_stat $filename and $filename =~ s/\.map$//; - (aio_load $filename, $data) >= 0 or return; @@ -1306,11 +1300,6 @@ utf8::encode (my $save = $self->save_path); IO::AIO::aioreq_pri 4; Coro::AIO::aio_unlink $save; IO::AIO::aioreq_pri 4; Coro::AIO::aio_unlink "$save.pst"; - - #d#TODO remove .map and also nuke - $save =~ s/\.map// or return;#d# - IO::AIO::aioreq_pri 4; Coro::AIO::aio_unlink $save;#d# - IO::AIO::aioreq_pri 4; Coro::AIO::aio_unlink "$save.pst";#d# } sub load_header_from($) { @@ -1608,9 +1597,18 @@ warn "resetting map ", $self->path;#d# + $self->in_memory (cf::MAP_SWAPPED); + + # need to save uniques path + unless ($self->{deny_save}) { + my $uniq = $self->uniq_path; utf8::encode $uniq; + + $self->_save_objects ($uniq, cf::IO_UNIQUES) + if $uniq; + } + delete $cf::MAP{$self->path}; - $self->in_memory (cf::MAP_SWAPPED); $self->clear; $_->clear_links_to ($self) for values %cf::MAP; @@ -1810,6 +1808,8 @@ return unless $self->contr->active; $self->activate_recursive; + + local $self->{_prev_pos} = $link_pos; # ugly hack for rent.ext $self->enter_map ($map, $x, $y); } @@ -1832,12 +1832,12 @@ if ($pl->ob->{_link_pos}) { $pl->ob->enter_link; (async { - # we need this sleep as the login has a concurrent enter_exit running - # and this sleep increases chances of the player not ending up in scorn $pl->ob->reply (undef, "There was an internal problem at your last logout, " . "the server will try to bring you to your intended destination in a second.", cf::NDI_RED); + # we need this sleep as the login has a concurrent enter_exit running + # and this sleep increases chances of the player not ending up in scorn Coro::Timer::sleep 1; $pl->ob->leave_link; })->prio (2); @@ -2184,106 +2184,70 @@ =over 4 -=item $hashref = cf::db_get $family - -Return a hashref for use by the extension C<$family>, which can be -modified. After modifications, you have to call C or -C. - =item $value = cf::db_get $family => $key -Returns a single value from the database - -=item cf::db_put $family => $hashref - -Stores the given family hashref into the database. Updates are delayed, if -you want the data to be synced to disk immediately, use C. +Returns a single value from the database. =item cf::db_put $family => $key => $value -Stores the given C<$value> in the family hash. Updates are delayed, if you -want the data to be synced to disk immediately, use C. - -=item cf::db_dirty - -Marks the database as dirty, to be updated at a later time. - -=item cf::db_sync - -Immediately write the database to disk I. +Stores the given C<$value> in the family. =cut our $DB; -{ - my $path = cf::localdir . "/database.pst"; +sub db_init { + unless ($DB) { + $DB = BDB::db_create $DB_ENV; - sub db_load() { - $DB = stat $path ? Storable::retrieve $path : { }; - } + cf::sync_job { + eval { + $DB->set_flags (BDB::CHKSUM); - my $pid; + BDB::db_open $DB, undef, "db", undef, BDB::BTREE, + BDB::CREATE | BDB::AUTO_COMMIT, 0666; + cf::cleanup "db_open(db): $!" if $!; + }; + cf::cleanup "db_open(db): $@" if $@; + }; - sub db_save() { - waitpid $pid, 0 if $pid; - if (0 == ($pid = fork)) { - $DB->{_meta}{version} = 1; - Storable::nstore $DB, "$path~"; - rename "$path~", $path; - cf::_exit 0 if defined $pid; - } - } + my $path = cf::localdir . "/database.pst"; + if (stat $path) { + cf::sync_job { + my $pst = Storable::retrieve $path; - my $dirty; + cf::db_put (board => data => $pst->{board}); + cf::db_put (guildrules => data => $pst->{guildrules}); + cf::db_put (rent => balance => $pst->{rent}{balance}); + BDB::db_env_txn_checkpoint $DB_ENV; - sub db_sync() { - db_save if $dirty; - undef $dirty; + unlink $path; + }; + } } +} - my $idle = Event->idle ( - reentrant => 0, - min => 10, - max => 20, - repeat => 0, - data => WF_AUTOCANCEL, - cb => \&db_sync, - ); - - sub db_dirty() { - $dirty = 1; - $idle->start; - } +sub db_get($$) { + my $key = "$_[0]/$_[1]"; - sub db_get($;$) { - @_ >= 2 - ? $DB->{$_[0]}{$_[1]} - : ($DB->{$_[0]} ||= { }) - } + cf::sync_job { + BDB::db_get $DB, undef, $key, my $data; - sub db_put($$;$) { - if (@_ >= 3) { - $DB->{$_[0]}{$_[1]} = $_[2]; - } else { - $DB->{$_[0]} = $_[1]; - } - db_dirty; + $! ? () + : Compress::LZF::sthaw $data } +} - cf::global->attach ( - prio => 10000, - on_cleanup => sub { - db_sync; - }, - ); +sub db_put($$$) { + BDB::dbreq_pri 4; + BDB::db_put $DB, undef, "$_[0]/$_[1]", Compress::LZF::sfreeze_cr $_[2], 0, sub { }; } ############################################################################# # the server's init and main functions -sub load_resources { - load_regions sprintf "%s/%s/regions", cf::datadir, cf::mapdir +sub init_resources { + load_resource_file sprintf "%s/%s/regions", cf::datadir, cf::mapdir or die "unable to load regions file\n";#d# } @@ -2309,7 +2273,7 @@ } sub init { - load_resources; + init_resources; } sub main { @@ -2322,7 +2286,7 @@ }; cfg_load; - db_load; + db_init; load_extensions; $TICK_WATCHER->start; @@ -2371,11 +2335,22 @@ $map->save; } warn "end emergency map save\n"; + + warn "begin emergency database checkpoint\n"; + BDB::db_env_txn_checkpoint $DB_ENV; + warn "end emergency database checkpoint\n"; }; warn "leave emergency perl save\n"; } +sub post_cleanup { + my ($make_core) = @_; + + warn Carp::longmess "post_cleanup backtrace" + if $make_core; +} + sub reload() { # can/must only be called in main if ($Coro::current != $Coro::main) { @@ -2385,16 +2360,17 @@ warn "reloading..."; - warn "cancelling server ticker"; - $TICK_WATCHER->cancel; + warn "entering sync_job"; - cf::emergency_save; - - eval { - # if anything goes wrong in here, we should simply crash as we already saved + cf::sync_job { + cf::write_runtime; # external watchdog should not bark + cf::emergency_save; + cf::write_runtime; # external watchdog should not bark warn "syncing database to disk"; - cf::db_sync; + BDB::db_env_txn_checkpoint $DB_ENV; + + # if anything goes wrong in here, we should simply crash as we already saved warn "cancelling all WF_AUTOCANCEL watchers"; for (Event::all_watchers) { @@ -2403,9 +2379,10 @@ warn "flushing outstanding aio requests"; for (;;) { + BDB::flush; IO::AIO::flush; Coro::cede; - last unless IO::AIO::nreqs; + last unless IO::AIO::nreqs || BDB::nreqs; warn "iterate..."; } @@ -2470,7 +2447,6 @@ warn "loading config and database again"; cf::cfg_load; - cf::db_load; warn "loading extensions"; cf::load_extensions; @@ -2481,18 +2457,16 @@ reattach $_ for values %MAP; warn "loading reloadable resources"; - load_resources; - - warn "restarting server ticker"; + init_resources; - $TICK_WATCHER->start; - }; + warn "leaving sync_job"; - if ($@) { + 1 + } or do { warn $@; warn "error while reloading, exiting."; exit 1; - } + }; warn "reloaded"; }; @@ -2543,12 +2517,17 @@ $RUNTIME += $TICK; $NEXT_TICK += $TICK; + if ($NOW >= $NEXT_RUNTIME_WRITE) { + $NEXT_RUNTIME_WRITE = $NOW + 10; + Coro::async_pool { + write_runtime + or warn "ERROR: unable to write runtime file: $!"; + }; + } + $WAIT_FOR_TICK->broadcast; $WAIT_FOR_TICK_ONE->send if $WAIT_FOR_TICK_ONE->awaited; - Event::sweep; - Coro::cede_notself; - # my $AFTER = Event::time; # warn $AFTER - $NOW;#d# @@ -2591,17 +2570,21 @@ $DB_ENV = BDB::db_env_create; cf::sync_job { - BDB::db_env_open - $DB_ENV, - $BDB_ENV_DIR, - BDB::INIT_LOCK | BDB::INIT_LOG | BDB::INIT_MPOOL | BDB::INIT_TXN - | BDB::RECOVER | BDB::REGISTER | BDB::USE_ENVIRON | BDB::CREATE, - 0666; - - cf::cleanup "$!" if $!; - - $DB_ENV->set_flags (BDB::AUTO_COMMIT | BDB::REGION_INIT | BDB::TXN_NOSYNC, 1); - $DB_ENV->set_lk_detect; + eval { + BDB::db_env_open + $DB_ENV, + $BDB_ENV_DIR, + BDB::INIT_LOCK | BDB::INIT_LOG | BDB::INIT_MPOOL | BDB::INIT_TXN + | BDB::RECOVER | BDB::REGISTER | BDB::USE_ENVIRON | BDB::CREATE, + 0666; + + cf::cleanup "db_env_open($BDB_ENV_DIR): $!" if $!; + + $DB_ENV->set_flags (BDB::AUTO_COMMIT | BDB::REGION_INIT | BDB::TXN_NOSYNC, 1); + $DB_ENV->set_lk_detect; + }; + + cf::cleanup "db_env_open(db): $@" if $@; }; } } @@ -2613,26 +2596,14 @@ IO::AIO::max_poll_time $TICK * 0.1; $AIO_POLL_WATCHER = Event->io ( reentrant => 0, + data => WF_AUTOCANCEL, fd => IO::AIO::poll_fileno, poll => 'r', prio => 6, - data => WF_AUTOCANCEL, cb => \&IO::AIO::poll_cb, ); } -$WRITE_RUNTIME_WATCHER = Event->timer ( - reentrant => 0, - data => WF_AUTOCANCEL, - after => 1, - interval => 10, - prio => 6, # keep it lowest so it acts like a watchdog - cb => Coro::unblock_sub { - write_runtime - or warn "ERROR: unable to write runtime file: $!"; - }, -); - END { cf::emergency_save } 1