--- deliantra/server/lib/cf.pm 2007/01/14 18:00:34 1.173 +++ deliantra/server/lib/cf.pm 2007/01/19 23:32:52 1.185 @@ -10,7 +10,7 @@ use Safe; use Safe::Hole; -use Coro 3.3 (); +use Coro 3.4 (); use Coro::Event; use Coro::Timer; use Coro::Signal; @@ -959,7 +959,7 @@ my ($login) = @_; $cf::PLAYER{$login} - or cf::sync_job { !aio_stat $login } + or cf::sync_job { !aio_stat path $login } } sub find($) { @@ -1123,7 +1123,6 @@ sub generate_random_map { my ($self, $rmp) = @_; - # mit "rum" bekleckern, nicht $self->_create_random_map ( $rmp->{wallstyle}, $rmp->{wall_name}, $rmp->{floorstyle}, $rmp->{monsterstyle}, @@ -1342,7 +1341,8 @@ or return; if ($map->should_reset) { - $cf::WAIT_FOR_TICK->wait; + # doing this can freeze the server in a sync job, obviously + #$cf::WAIT_FOR_TICK->wait; $map->reset; undef $guard; $map = find $path @@ -1444,18 +1444,7 @@ } our %MAP_PREFETCH; -our $MAP_PREFETCHER = Coro::async { - while () { - for my $path (keys %MAP_PREFETCH) { - my $map = find $path - or next; - $map->load; - - delete $MAP_PREFETCH{$path}; - } - Coro::schedule; - } -}; +our $MAP_PREFETCHER = undef; sub find_async { my ($path, $origin) = @_; @@ -1466,8 +1455,19 @@ return $map if $map->in_memory == cf::MAP_IN_MEMORY; } - $MAP_PREFETCH{$path} = 0; - $MAP_PREFETCHER->ready; + undef $MAP_PREFETCH{$path}; + $MAP_PREFETCHER ||= cf::async { + while (%MAP_PREFETCH) { + for my $path (keys %MAP_PREFETCH) { + my $map = find $path + or next; + $map->load; + + delete $MAP_PREFETCH{$path}; + } + } + undef $MAP_PREFETCHER; + }; () } @@ -1537,20 +1537,6 @@ $self->reset_at <= $cf::RUNTIME } -sub rename { - my ($self, $new_path) = @_; - - normalise $new_path; - - $self->unlink_save; - - delete $cf::MAP{$self->path}; - $self->{path} = $new_path; $self->path ($self->{path}); - $cf::MAP{$self->path} = $self; - - $self->save; -} - sub reset { my ($self) = @_; @@ -1577,9 +1563,18 @@ sub nuke { my ($self) = @_; + delete $cf::MAP{$self->path}; + + $self->unlink_save; + + bless $self, "cf::map"; + delete $self->{deny_reset}; $self->{deny_save} = 1; $self->reset_timeout (1); - $self->rename ("{nuke}/" . ($nuke_counter++)); + $self->path ($self->{path} = "{nuke}/" . ($nuke_counter++)); + + $cf::MAP{$self->path} = $self; + $self->reset; # polite request, might not happen } @@ -1820,8 +1815,10 @@ my ($spec) = @_; my $rmp = { # defaults - xsize => 10, - ysize => 10, + xsize => (cf::rndm 15, 40), + ysize => (cf::rndm 15, 40), + symmetry => (cf::rndm 1, cf::SYMMETRY_XY), + #layout => string, }; for (split /\n/, $spec) { @@ -1836,6 +1833,8 @@ sub prepare_random_map { my ($exit) = @_; + my $guard = cf::lock_acquire "exit_prepare:$exit"; + # all this does is basically replace the /! path by # a new random map path (?random/...) with a seed # that depends on the exit object @@ -1853,9 +1852,12 @@ my $data = cf::to_json $rmp; my $md5 = Digest::MD5::md5_hex $data; + my $meta = "$cf::RANDOM_MAPS/$md5.meta"; - if (my $fh = aio_open "$cf::RANDOM_MAPS/$md5.meta", O_WRONLY | O_CREAT, 0666) { + if (my $fh = aio_open "$meta~", O_WRONLY | O_CREAT, 0666) { aio_write $fh, 0, (length $data), $data, 0; + undef $fh; + aio_rename "$meta~", $meta; $exit->slaying ("?random/$md5"); $exit->msg (undef); @@ -2175,7 +2177,7 @@ undef $dirty; } - my $idle = Event->idle (min => $TICK * 2.8, max => 10, repeat => 0, data => WF_AUTOCANCEL, cb => sub { + my $idle = Event->idle (min => 10, max => 20, repeat => 0, data => WF_AUTOCANCEL, cb => sub { db_sync; }); @@ -2235,12 +2237,16 @@ # we must not ever block the main coroutine local $Coro::idle = sub { Carp::cluck "FATAL: Coro::idle was called, major BUG, use cf::sync_job!\n";#d# - async { Event::one_event }; + (async { + Event::one_event; + })->prio (Coro::PRIO_MAX); }; cfg_load; db_load; load_extensions; + + $TICK_WATCHER->start; Event::loop; } @@ -2292,39 +2298,47 @@ sub reload() { # can/must only be called in main if ($Coro::current != $Coro::main) { - warn "can only reload from main coroutine\n"; + warn "can only reload from main coroutine"; return; } warn "reloading..."; - warn "freezing server"; - my $guard = freeze_mainloop; - cf::emergency_save; + warn "cancelling server ticker"; + $TICK_WATCHER->cancel; - warn "sync database to disk"; - cf::db_sync; - IO::AIO::flush; + cf::emergency_save; eval { # if anything goes wrong in here, we should simply crash as we already saved - warn "cancel all watchers"; + warn "syncing database to disk"; + cf::db_sync; + + warn "cancelling all WF_AUTOCANCEL watchers"; for (Event::all_watchers) { $_->cancel if $_->data & WF_AUTOCANCEL; } - warn "cancel all extension coros"; + warn "flushing outstanding aio requests"; + for (;;) { + IO::AIO::flush; + Coro::cede; + last unless IO::AIO::nreqs; + warn "iterate..."; + } + + warn "cancelling all extension coros"; $_->cancel for values %EXT_CORO; %EXT_CORO = (); - warn "remove commands"; + warn "removing commands"; %COMMAND = (); - warn "remove ext commands"; + warn "removing ext commands"; %EXTCMD = (); - warn "unload/nuke all extensions"; + warn "unloading/nuking all extensions"; for my $pkg (@EXTS) { warn "... unloading $pkg"; @@ -2339,11 +2353,11 @@ Symbol::delete_package $pkg; } - warn "unload all perl modules loaded from $LIBDIR"; + warn "unloading all perl modules loaded from $LIBDIR"; while (my ($k, $v) = each %INC) { next unless $v =~ /^\Q$LIBDIR\E\/.*\.pm$/; - warn "removing <$k>"; + warn "... unloading $k"; delete $INC{$k}; $k =~ s/\.pm$//; @@ -2356,32 +2370,38 @@ Symbol::delete_package $k; } - warn "get rid of safe::, as good as possible"; + warn "getting rid of safe::, as good as possible"; Symbol::delete_package "safe::$_" for qw(cf::attachable cf::object cf::object::player cf::client cf::player cf::map cf::party cf::region); - warn "unload cf.pm \"a bit\""; + warn "unloading cf.pm \"a bit\""; delete $INC{"cf.pm"}; # don't, removes xs symbols, too, # and global variables created in xs #Symbol::delete_package __PACKAGE__; + warn "unload completed, starting to reload now"; + warn "reloading cf.pm"; require cf; cf::_connect_to_perl; # nominally unnecessary, but cannot hurt - warn "load config and database again"; + warn "loading config and database again"; cf::cfg_load; cf::db_load; - warn "load extensions"; + warn "loading extensions"; cf::load_extensions; - warn "reattach attachments to objects/players"; + warn "reattaching attachments to objects/players"; _global_reattach; - warn "reattach attachments to maps"; + warn "reattaching attachments to maps"; reattach $_ for values %MAP; + + warn "restarting server ticker"; + + $TICK_WATCHER->start; }; if ($@) { @@ -2393,24 +2413,41 @@ warn "reloaded"; }; +our $RELOAD_WATCHER; # used only during reload + register_command "reload" => sub { my ($who, $arg) = @_; if ($who->flag (FLAG_WIZ)) { - $who->message ("start of reload."); - reload; - $who->message ("end of reload."); + $who->message ("reloading server."); + + # doing reload synchronously and two reloads happen back-to-back, + # coro crashes during coro_state_free->destroy here. + + $RELOAD_WATCHER ||= Event->timer (after => 0, data => WF_AUTOCANCEL, cb => sub { + reload; + undef $RELOAD_WATCHER; + }); } }; unshift @INC, $LIBDIR; +my $bug_warning = 0; + $TICK_WATCHER = Event->timer ( reentrant => 0, + parked => 1, prio => 0, at => $NEXT_TICK || $TICK, data => WF_AUTOCANCEL, cb => sub { + if ($Coro::current != $Coro::main) { + Carp::cluck "major BUG: server tick called outside of main coro, skipping it" + unless ++$bug_warning > 10; + return; + } + $NOW = Event::time; cf::server_tick; # one server iteration @@ -2440,13 +2477,11 @@ $WRITE_RUNTIME_WATCHER = Event->timer ( data => WF_AUTOCANCEL, - after => 0, + after => 1, interval => 10, - cb => sub { - (Coro::unblock_sub { - write_runtime - or warn "ERROR: unable to write runtime file: $!"; - })->(); + cb => Coro::unblock_sub { + write_runtime + or warn "ERROR: unable to write runtime file: $!"; }, );