--- deliantra/server/lib/cf.pm 2008/09/20 00:09:27 1.449 +++ deliantra/server/lib/cf.pm 2008/12/13 20:34:37 1.458 @@ -34,6 +34,7 @@ use Safe::Hole; use Storable (); +use Guard (); use Coro (); use Coro::State; use Coro::Handle; @@ -83,7 +84,7 @@ our %EXT_CORO = (); # coroutines bound to extensions our %EXT_MAP = (); # pluggable maps -our $RELOAD; # number of reloads so far +our $RELOAD; # number of reloads so far, non-zero while in reload our @EVENT; our $CONFDIR = confdir; @@ -130,6 +131,10 @@ our $JITTER; # average jitter our $TICK_START; # for load detecting purposes +our @POST_INIT; + +our $REATTACH_ON_RELOAD; # ste to true to force object reattach on reload (slow) + binmode STDOUT; binmode STDERR; @@ -307,6 +312,20 @@ sub encode_json($) { $json_coder->encode ($_[0]) } sub decode_json($) { $json_coder->decode ($_[0]) } +=item cf::post_init { BLOCK } + +Execute the given codeblock, I all extensions have been (re-)loaded, +but I the server starts ticking again. + +The cdoeblock will have a single boolean argument to indicate whether this +is a reload or not. + +=cut + +sub post_init(&) { + push @POST_INIT, shift; +} + =item cf::lock_wait $string Wait until the given lock is available. See cf::lock_acquire. @@ -314,7 +333,7 @@ =item my $lock = cf::lock_acquire $string Wait until the given lock is available and then acquires it and returns -a Coro::guard object. If the guard object gets destroyed (goes out of scope, +a L object. If the guard object gets destroyed (goes out of scope, for example when the coroutine gets canceled), the lock is automatically returned. @@ -343,6 +362,7 @@ # wait for lock, if any while ($LOCK{$key}) { + #local $Coro::current->{desc} = "$Coro::current->{desc} "; push @{ $LOCK{$key} }, $Coro::current; Coro::schedule; } @@ -357,7 +377,7 @@ $LOCK{$key} = []; $LOCKER{$key} = $Coro::current;#d# - Coro::guard { + Guard::guard { delete $LOCKER{$key};#d# # wake up all waiters, to be on the safe side $_->ready for @{ delete $LOCK{$key} }; @@ -373,7 +393,7 @@ sub freeze_mainloop { tick_inhibit_inc; - Coro::guard \&tick_inhibit_dec; + &Guard::guard (\&tick_inhibit_dec); } =item cf::periodic $interval, $cb @@ -1172,6 +1192,9 @@ } aio_rename "$filename~", $filename; + + $filename =~ s%/[^/]+$%%; + aio_pathsync $filename if $cf::USE_FSYNC; } else { warn "FATAL: $filename~: $!\n"; } @@ -1274,6 +1297,8 @@ $EXTICMD{$name} = $cb; } +use File::Glob (); + cf::player->attach ( on_command => sub { my ($pl, $name, $params) = @_; @@ -1315,6 +1340,19 @@ }, ); +# "readahead" all extensions +sub cache_extensions { + my $grp = IO::AIO::aio_group; + + add $grp IO::AIO::aio_readdir $LIBDIR, sub { + for (grep /\.ext$/, @{$_[0]}) { + add $grp IO::AIO::aio_load "$LIBDIR/$_", my $data; + } + }; + + $grp +} + sub load_extensions { cf::sync_job { my %todo; @@ -1446,7 +1484,7 @@ my ($login) = @_; $cf::PLAYER{$login} - or cf::sync_job { !aio_stat path $login } + or !aio_stat path $login } sub find($) { @@ -2773,6 +2811,12 @@ reply => undef, tooltip => "Shows which body parts you posess and are available", }, + "c/skills" => { + id => "infobox", + title => "Skills", + reply => undef, + tooltip => "Shows your experience per skill and item power", + }, "c/uptime" => { id => "infobox", title => "Uptime", @@ -3336,14 +3380,6 @@ warn "finished reloading resource files\n"; } -sub init { - my $guard = freeze_mainloop; - - evthread_start IO::AIO::poll_fileno; - - reload_resources; -} - sub reload_config { open my $fh, "<:utf8", "$CONFDIR/config" or return; @@ -3385,7 +3421,20 @@ } sub main { - atomic; + cf::init_globals; # initialise logging + + LOG llevInfo, "Welcome to Deliantra, v" . VERSION; + LOG llevInfo, "Copyright (C) 2005-2008 Marc Alexander Lehmann / Robin Redeker / the Deliantra team."; + LOG llevInfo, "Copyright (C) 1994 Mark Wedel."; + LOG llevInfo, "Copyright (C) 1992 Frank Tore Johansen."; + + cf::init_experience; + cf::init_anim; + cf::init_attackmess; + cf::init_dynamic; + cf::init_block; + + $Coro::current->prio (Coro::PRIO_MAX); # give the main loop max. priority # we must not ever block the main coroutine local $Coro::idle = sub { @@ -3396,19 +3445,33 @@ })->prio (Coro::PRIO_MAX); }; - { - my $guard = freeze_mainloop; + evthread_start IO::AIO::poll_fileno; + + cf::sync_job { + reload_resources; reload_config; db_init; + + cf::load_settings; + cf::load_materials; + cf::init_uuid; + cf::init_signals; + cf::init_commands; + cf::init_skills; + + cf::init_beforeplay; + + atomic; + load_extensions; - $Coro::current->prio (Coro::PRIO_MAX); # give the main loop max. priority - } + utime time, time, $RUNTIMEFILE; - utime time, time, $RUNTIMEFILE; + # no (long-running) fork's whatsoever before this point(!) + POSIX::close delete $ENV{LOCKUTIL_LOCK_FD} if exists $ENV{LOCKUTIL_LOCK_FD}; - # no (long-running) fork's whatsoever before this point(!) - POSIX::close delete $ENV{LOCKUTIL_LOCK_FD} if exists $ENV{LOCKUTIL_LOCK_FD}; + (pop @POST_INIT)->(0) while @POST_INIT; + }; EV::loop; } @@ -3476,7 +3539,14 @@ my $fh = aio_open "$uuid~", O_WRONLY | O_CREAT, 0644 or return; - my $value = uuid_str $uuid_skip + uuid_seq uuid_cur; + my $value = uuid_seq uuid_cur; + + unless ($value) { + warn "cowardly refusing to write zero uuid value!\n"; + return; + } + + my $value = uuid_str $value + $uuid_skip; $uuid_skip = 0; (aio_write $fh, 0, (length $value), $value, 0) <= 0 @@ -3508,39 +3578,49 @@ sub emergency_save() { my $freeze_guard = cf::freeze_mainloop; - warn "enter emergency perl save\n"; + warn "emergency_perl_save: enter\n"; cf::sync_job { + # this is a trade-off: we want to be very quick here, so + # save all maps without fsync, and later call a global sync + # (which in turn might be very very slow) + local $USE_FSYNC = 0; + # use a peculiar iteration method to avoid tripping on perl # refcount bugs in for. also avoids problems with players # and maps saved/destroyed asynchronously. - warn "begin emergency player save\n"; + warn "emergency_perl_save: begin player save\n"; for my $login (keys %cf::PLAYER) { my $pl = $cf::PLAYER{$login} or next; $pl->valid or next; delete $pl->{unclean_save}; # not strictly necessary, but cannot hurt $pl->save; } - warn "end emergency player save\n"; + warn "emergency_perl_save: end player save\n"; - warn "begin emergency map save\n"; + warn "emergency_perl_save: begin map save\n"; for my $path (keys %cf::MAP) { my $map = $cf::MAP{$path} or next; $map->valid or next; $map->save; } - warn "end emergency map save\n"; + warn "emergency_perl_save: end map save\n"; - warn "begin emergency database checkpoint\n"; + warn "emergency_perl_save: begin database checkpoint\n"; BDB::db_env_txn_checkpoint $DB_ENV; - warn "end emergency database checkpoint\n"; + warn "emergency_perl_save: end database checkpoint\n"; - warn "begin write uuid\n"; + warn "emergency_perl_save: begin write uuid\n"; write_uuid_sync 1; - warn "end write uuid\n"; + warn "emergency_perl_save: end write uuid\n"; }; - warn "leave emergency perl save\n"; + warn "emergency_perl_save: starting sync()\n"; + IO::AIO::aio_sync sub { + warn "emergency_perl_save: finished sync()\n"; + }; + + warn "emergency_perl_save: leave\n"; } sub post_cleanup { @@ -3576,11 +3656,9 @@ _gv_clear *{"$pkg$name"}; # use PApp::Util; PApp::Util::sv_dump *{"$pkg$name"}; } - warn "cleared package #$pkg\n";#d# + warn "cleared package $pkg\n";#d# } -our $RELOAD; # how many times to reload - sub do_reload_perl() { # can/must only be called in main if ($Coro::current != $Coro::main) { @@ -3590,6 +3668,8 @@ return if $RELOAD++; + my $t1 = EV::time; + while ($RELOAD) { warn "reloading..."; @@ -3677,12 +3757,17 @@ warn "loading extensions"; cf::load_extensions; - warn "reattaching attachments to objects/players"; - _global_reattach; # objects, sockets - warn "reattaching attachments to maps"; - reattach $_ for values %MAP; - warn "reattaching attachments to players"; - reattach $_ for values %PLAYER; + if ($REATTACH_ON_RELOAD) { + warn "reattaching attachments to objects/players"; + _global_reattach; # objects, sockets + warn "reattaching attachments to maps"; + reattach $_ for values %MAP; + warn "reattaching attachments to players"; + reattach $_ for values %PLAYER; + } + + warn "running post_init jobs"; + (pop @POST_INIT)->(1) while @POST_INIT; warn "leaving sync_job"; @@ -3695,6 +3780,9 @@ warn "reloaded"; --$RELOAD; } + + $t1 = EV::time - $t1; + warn "reload completed in ${t1}s\n"; }; our $RELOAD_WATCHER; # used only during reload @@ -3703,9 +3791,13 @@ # doing reload synchronously and two reloads happen back-to-back, # coro crashes during coro_state_free->destroy here. - $RELOAD_WATCHER ||= EV::timer 0, 0, sub { - do_reload_perl; - undef $RELOAD_WATCHER; + $RELOAD_WATCHER ||= cf::async { + Coro::AIO::aio_wait cache_extensions; + + $RELOAD_WATCHER = EV::timer $TICK * 1.5, 0, sub { + do_reload_perl; + undef $RELOAD_WATCHER; + }; }; }