--- deliantra/server/lib/cf.pm 2007/01/18 00:06:55 1.178 +++ deliantra/server/lib/cf.pm 2007/01/27 23:59:29 1.196 @@ -10,7 +10,7 @@ use Safe; use Safe::Hole; -use Coro 3.4 (); +use Coro 3.5 (); use Coro::Event; use Coro::Timer; use Coro::Signal; @@ -351,7 +351,10 @@ my $fh = aio_open "$runtime~", O_WRONLY | O_CREAT, 0644 or return; - my $value = $cf::RUNTIME + 1 + 10; # 10 is the runtime save interval, for a monotonic clock + my $value = $cf::RUNTIME + 90 + 10; + # 10 is the runtime save interval, for a monotonic clock + # 60 allows for the watchdog to kill the server. + (aio_write $fh, 0, (length $value), $value, 0) <= 0 and return; @@ -959,7 +962,7 @@ my ($login) = @_; $cf::PLAYER{$login} - or cf::sync_job { !aio_stat $login } + or cf::sync_job { !aio_stat path $login } } sub find($) { @@ -1139,18 +1142,26 @@ ) } +=item cf::map->register ($regex, $prio) + +Register a handler for the map path matching the given regex at the +givne priority (higher is better, built-in handlers have priority 0, the +default). + +=cut + sub register { - my (undef, $regex) = @_; + my (undef, $regex, $prio) = @_; my $pkg = caller; no strict; push @{"$pkg\::ISA"}, __PACKAGE__; - $EXT_MAP{$pkg} = qr<$regex>; + $EXT_MAP{$pkg} = [$prio, qr<$regex>]; } # also paths starting with '/' -$EXT_MAP{"cf::map"} = qr{^(?=/)}; +$EXT_MAP{"cf::map"} = [0, qr{^(?=/)}]; sub thawer_merge { my ($self, $merge) = @_; @@ -1167,6 +1178,8 @@ sub normalise { my ($path, $base) = @_; + $path = "$path"; # make sure its a string + # map plan: # # /! non-realised random map exit (special hack!) @@ -1203,8 +1216,8 @@ $path = normalise $path, $base; - for my $pkg (keys %EXT_MAP) { - if ($path =~ $EXT_MAP{$pkg}) { + for my $pkg (sort { $EXT_MAP{$b}[0] <=> $EXT_MAP{$a}[0] } keys %EXT_MAP) { + if ($path =~ $EXT_MAP{$pkg}[1]) { my $self = bless cf::map::new, $pkg; $self->{path} = $path; $self->path ($path); $self->init; # pass $1 etc. @@ -1212,7 +1225,7 @@ } } - Carp::carp "unable to resolve path '$path'."; + Carp::carp "unable to resolve path '$path' (base '$base')."; () } @@ -1288,35 +1301,46 @@ 1 } -sub load_orig { +sub load_header_orig { my ($self) = @_; $self->load_header_from ($self->load_path) } -sub load_temp { +sub load_header_temp { my ($self) = @_; $self->load_header_from ($self->save_path) } +sub prepare_temp { + my ($self) = @_; + + $self->last_access ((delete $self->{last_access}) + || $cf::RUNTIME); #d# + # safety + $self->{instantiate_time} = $cf::RUNTIME + if $self->{instantiate_time} > $cf::RUNTIME; +} + +sub prepare_orig { + my ($self) = @_; + + $self->{load_original} = 1; + $self->{instantiate_time} = $cf::RUNTIME; + $self->last_access ($cf::RUNTIME); + $self->instantiate; +} + sub load_header { my ($self) = @_; - if ($self->load_temp) { - $self->last_access ((delete $self->{last_access}) - || $cf::RUNTIME); #d# - # safety - $self->{instantiate_time} = $cf::RUNTIME - if $self->{instantiate_time} > $cf::RUNTIME; + if ($self->load_header_temp) { + $self->prepare_temp; } else { - $self->load_orig + $self->load_header_orig or return; - - $self->{load_original} = 1; - $self->{instantiate_time} = $cf::RUNTIME; - $self->last_access ($cf::RUNTIME); - $self->instantiate; + $self->prepare_orig; } 1 @@ -1340,21 +1364,26 @@ $map->load_header or return; - if ($map->should_reset) { - $cf::WAIT_FOR_TICK->wait; + if ($map->should_reset && 0) {#d#TODO# disabled, crashy (locking issue?) + # doing this can freeze the server in a sync job, obviously + #$cf::WAIT_FOR_TICK->wait; $map->reset; undef $guard; - $map = find $path - or return; + return find $path; } $cf::MAP{$path} = $map } } +sub pre_load { } +sub post_load { } + sub load { my ($self) = @_; + local $self->{deny_reset} = 1; # loading can take a long time + my $path = $self->{path}; my $guard = cf::lock_acquire "map_load:$path"; @@ -1363,6 +1392,9 @@ $self->in_memory (cf::MAP_LOADING); $self->alloc; + + $self->pre_load; + $self->_load_objects ($self->{load_path}, 1) or return; @@ -1394,6 +1426,8 @@ $self->activate; } + $self->post_load; + $self->in_memory (cf::MAP_IN_MEMORY); } @@ -1443,18 +1477,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) = @_; @@ -1465,8 +1488,20 @@ 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; + }; + $MAP_PREFETCHER->prio (6); () } @@ -1700,7 +1735,7 @@ sub link_map { unless ($LINK_MAP) { $LINK_MAP = cf::map::find "{link}" - or do { warn "FATAL: unable to provide {link} map, exiting."; exit 1 }; + or cf::cleanup "FATAL: unable to provide {link} map, exiting."; $LINK_MAP->load; } @@ -1717,7 +1752,7 @@ $self->{_link_pos} ||= [$self->map->{path}, $self->x, $self->y] if $self->map; - $self->enter_map ($LINK_MAP || link_map, 20, 20); + $self->enter_map ($LINK_MAP || link_map, 10, 10); } sub cf::object::player::leave_link { @@ -1814,8 +1849,10 @@ my ($spec) = @_; my $rmp = { # defaults - xsize => -1, - ysize => -1, + xsize => (cf::rndm 15, 40), + ysize => (cf::rndm 15, 40), + symmetry => (cf::rndm 1, cf::SYMMETRY_XY), + #layout => string, }; for (split /\n/, $spec) { @@ -1830,6 +1867,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 @@ -1864,16 +1903,21 @@ return unless $self->type == cf::PLAYER; + if ($exit->slaying eq "/!") { + #TODO: this should de-fi-ni-te-ly not be a sync-job + cf::sync_job { prepare_random_map $exit }; + } + + my $slaying = cf::map::normalise $exit->slaying, $exit->map && $exit->map->path; + my $hp = $exit->stats->hp; + my $sp = $exit->stats->sp; + $self->enter_link; (async { $self->deactivate_recursive; # just to be sure unless (eval { - prepare_random_map $exit - if $exit->slaying eq "/!"; - - my $path = new_from_path cf::map $exit->slaying, $exit->map && $exit->map->path; - $self->goto ($path, $exit->stats->hp, $exit->stats->sp); + $self->goto ($slaying, $hp, $sp); 1; }) { @@ -2172,9 +2216,14 @@ undef $dirty; } - my $idle = Event->idle (min => $TICK * 2.8, max => 10, repeat => 0, data => WF_AUTOCANCEL, cb => sub { - db_sync; - }); + my $idle = Event->idle ( + reentrant => 0, + min => 10, + max => 20, + repeat => 0, + data => WF_AUTOCANCEL, + cb => \&db_sync, + ); sub db_dirty() { $dirty = 1; @@ -2240,6 +2289,8 @@ cfg_load; db_load; load_extensions; + + $TICK_WATCHER->start; Event::loop; } @@ -2250,9 +2301,11 @@ BEGIN { for my $signal (qw(INT HUP TERM)) { Event->signal ( - data => WF_AUTOCANCEL, - signal => $signal, - cb => sub { + reentrant => 0, + data => WF_AUTOCANCEL, + signal => $signal, + prio => 0, + cb => sub { cf::cleanup "SIG$signal"; }, ); @@ -2291,39 +2344,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"; @@ -2338,11 +2399,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$//; @@ -2355,32 +2416,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 ($@) { @@ -2403,21 +2470,35 @@ # 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; - }); + $RELOAD_WATCHER ||= Event->timer ( + reentrant => 0, + 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 @@ -2427,6 +2508,12 @@ $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# + # if we are delayed by four ticks or more, skip them all $NEXT_TICK = Event::time if Event::time >= $NEXT_TICK + $TICK * 4; @@ -2435,25 +2522,27 @@ }, ); -IO::AIO::max_poll_time $TICK * 0.2; +IO::AIO::max_poll_time $TICK * 0.1; +undef $Coro::AIO::WATCHER; $AIO_POLL_WATCHER = Event->io ( - fd => IO::AIO::poll_fileno, - poll => 'r', - prio => 5, - data => WF_AUTOCANCEL, - cb => \&IO::AIO::poll_cb, + reentrant => 0, + fd => IO::AIO::poll_fileno, + poll => 'r', + prio => 6, + data => WF_AUTOCANCEL, + cb => \&IO::AIO::poll_cb, ); $WRITE_RUNTIME_WATCHER = Event->timer ( - data => WF_AUTOCANCEL, - after => 0, - interval => 10, - cb => sub { - (Coro::unblock_sub { - write_runtime - or warn "ERROR: unable to write runtime file: $!"; - })->(); + 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: $!"; }, );