--- deliantra/server/lib/cf.pm 2008/04/11 13:59:06 1.416 +++ deliantra/server/lib/cf.pm 2009/10/21 00:44:39 1.488 @@ -1,47 +1,53 @@ -# +# # This file is part of Deliantra, the Roguelike Realtime MMORPG. # # Copyright (©) 2006,2007,2008 Marc Alexander Lehmann / Robin Redeker / the Deliantra team # -# Deliantra is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# Deliantra is free software: you can redistribute it and/or modify it under +# the terms of the Affero GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . +# You should have received a copy of the Affero GNU General Public License +# and the GNU General Public License along with this program. If not, see +# . # # The authors can be reached via e-mail to -# +# package cf; +use 5.10.0; use utf8; -use strict; +use strict qw(vars subs); use Symbol; use List::Util; use Socket; -use EV 3.2; +use EV; use Opcode; use Safe; use Safe::Hole; use Storable (); -use Coro 4.50 (); +use Guard (); +use Coro (); use Coro::State; use Coro::Handle; use Coro::EV; +use Coro::AnyEvent; use Coro::Timer; use Coro::Signal; use Coro::Semaphore; +use Coro::SemaphoreSet; +use Coro::AnyEvent; use Coro::AIO; -use Coro::BDB; +use Coro::BDB 1.6; use Coro::Storable; use Coro::Util (); @@ -50,20 +56,28 @@ use Data::Dumper; use Digest::MD5; use Fcntl; -use YAML (); -use IO::AIO 2.51 (); +use YAML::XS (); +use IO::AIO (); use Time::HiRes; use Compress::LZF; use Digest::MD5 (); +AnyEvent::detect; + # configure various modules to our taste # $Storable::canonical = 1; # reduce rsync transfers Coro::State::cctx_stacksize 256000; # 1-2MB stack, for deep recursions in maze generator -Compress::LZF::sfreeze_cr { }; # prime Compress::LZF so it does not use require later $Coro::main->prio (Coro::PRIO_MAX); # run main coroutine ("the server") with very high priority +# make sure c-lzf reinitialises itself +Compress::LZF::set_serializer "Storable", "Storable::net_mstore", "Storable::mretrieve"; +Compress::LZF::sfreeze_cr { }; # prime Compress::LZF so it does not use require later + +# strictly for debugging +$SIG{QUIT} = sub { Carp::cluck "SIGQUIT" }; + sub WF_AUTOCANCEL () { 1 } # automatically cancel this watcher on reload our %COMMAND = (); @@ -75,34 +89,39 @@ 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 @REFLECT; # set by XS +our %REFLECT; # set by us + +our $CONFDIR = confdir; +our $DATADIR = datadir; +our $LIBDIR = "$DATADIR/ext"; +our $PODDIR = "$DATADIR/pod"; +our $MAPDIR = "$DATADIR/" . mapdir; +our $LOCALDIR = localdir; +our $TMPDIR = "$LOCALDIR/" . tmpdir; +our $UNIQUEDIR = "$LOCALDIR/" . uniquedir; +our $PLAYERDIR = "$LOCALDIR/" . playerdir; +our $RANDOMDIR = "$LOCALDIR/random"; +our $BDBDIR = "$LOCALDIR/db"; +our $PIDFILE = "$LOCALDIR/pid"; +our $RUNTIMEFILE = "$LOCALDIR/runtime"; -our $CONFDIR = confdir; -our $DATADIR = datadir; -our $LIBDIR = "$DATADIR/ext"; -our $PODDIR = "$DATADIR/pod"; -our $MAPDIR = "$DATADIR/" . mapdir; -our $LOCALDIR = localdir; -our $TMPDIR = "$LOCALDIR/" . tmpdir; -our $UNIQUEDIR = "$LOCALDIR/" . uniquedir; -our $PLAYERDIR = "$LOCALDIR/" . playerdir; -our $RANDOMDIR = "$LOCALDIR/random"; -our $BDBDIR = "$LOCALDIR/db"; our %RESOURCE; our $TICK = MAX_TIME * 1e-6; # this is a CONSTANT(!) -our $AIO_POLL_WATCHER; our $NEXT_RUNTIME_WRITE; # when should the runtime file be written our $NEXT_TICK; -our $USE_FSYNC = 1; # use fsync to write maps - default off +our $USE_FSYNC = 1; # use fsync to write maps - default on -our $BDB_POLL_WATCHER; our $BDB_DEADLOCK_WATCHER; our $BDB_CHECKPOINT_WATCHER; our $BDB_TRICKLE_WATCHER; our $DB_ENV; +our @EXTRA_MODULES = qw(pod match mapscript); + our %CFG; our $UPTIME; $UPTIME ||= time; @@ -121,16 +140,23 @@ our $JITTER; # average jitter our $TICK_START; # for load detecting purposes +our @POST_INIT; + +our $REATTACH_ON_RELOAD; # set to true to force object reattach on reload (slow) +our $REALLY_UNLOOP; # never set to true, please :) + binmode STDOUT; binmode STDERR; # read virtual server time, if available -unless ($RUNTIME || !-e "$LOCALDIR/runtime") { - open my $fh, "<", "$LOCALDIR/runtime" - or die "unable to read runtime file: $!"; +unless ($RUNTIME || !-e $RUNTIMEFILE) { + open my $fh, "<", $RUNTIMEFILE + or die "unable to read $RUNTIMEFILE file: $!"; $RUNTIME = <$fh> + 0.; } +eval "sub TICK() { $TICK } 1" or die; + mkdir $_ for $LOCALDIR, $TMPDIR, $UNIQUEDIR, $PLAYERDIR, $RANDOMDIR, $BDBDIR; @@ -140,6 +166,17 @@ ############################################################################# +%REFLECT = (); +for (@REFLECT) { + my $reflect = JSON::XS::decode_json $_; + $REFLECT{$reflect->{class}} = $reflect; +} + +# this is decidedly evil +$REFLECT{object}{flags} = { map +($_ => undef), grep $_, map /^FLAG_([A-Z0-9_]+)$/ && lc $1, keys %{"cf::"} }; + +############################################################################# + =head2 GLOBAL VARIABLES =over 4 @@ -193,42 +230,45 @@ =item @cf::INVOKE_RESULTS -This array contains the results of the last C call. When +This array contains the results of the last C call. When C is called C<@cf::INVOKE_RESULTS> is set to the parameters of that call. +=item %cf::REFLECT + +Contains, for each (C++) class name, a hash reference with information +about object members (methods, scalars, arrays and flags) and other +metadata, which is useful for introspection. + =back =cut -BEGIN { - *CORE::GLOBAL::warn = sub { - my $msg = join "", @_; +$Coro::State::WARNHOOK = sub { + my $msg = join "", @_; - $msg .= "\n" - unless $msg =~ /\n$/; + $msg .= "\n" + unless $msg =~ /\n$/; - $msg =~ s/([\x00-\x08\x0b-\x1f])/sprintf "\\x%02x", ord $1/ge; + $msg =~ s/([\x00-\x08\x0b-\x1f])/sprintf "\\x%02x", ord $1/ge; - LOG llevError, $msg; - }; -} + LOG llevError, $msg; +}; $Coro::State::DIEHOOK = sub { return unless $^S eq 0; # "eq", not "==" + warn Carp::longmess $_[0]; + if ($Coro::current == $Coro::main) {#d# warn "DIEHOOK called in main context, Coro bug?\n";#d# return;#d# }#d# # kill coroutine otherwise - warn Carp::longmess $_[0]; Coro::terminate }; -$SIG{__DIE__} = sub { }; #d#? - @safe::cf::global::ISA = @cf::global::ISA = 'cf::attachable'; @safe::cf::object::ISA = @cf::object::ISA = 'cf::attachable'; @safe::cf::player::ISA = @cf::player::ISA = 'cf::attachable'; @@ -244,9 +284,9 @@ cf::object cf::object::player cf::client cf::player cf::arch cf::living - cf::map cf::party cf::region + cf::map cf::mapspace + cf::party cf::region )) { - no strict 'refs'; @{"safe::$pkg\::wrap::ISA"} = @{"$pkg\::wrap::ISA"} = $pkg; } @@ -298,6 +338,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. @@ -305,7 +359,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. @@ -321,50 +375,24 @@ =cut -our %LOCK; -our %LOCKER;#d# +our $LOCKS = new Coro::SemaphoreSet; sub lock_wait($) { - my ($key) = @_; - - if ($LOCKER{$key} == $Coro::current) {#d# - Carp::cluck "lock_wait($key) for already-acquired lock";#d# - return;#d# - }#d# - - # wait for lock, if any - while ($LOCK{$key}) { - push @{ $LOCK{$key} }, $Coro::current; - Coro::schedule; - } + $LOCKS->wait ($_[0]); } sub lock_acquire($) { - my ($key) = @_; - - # wait, to be sure we are not locked - lock_wait $key; - - $LOCK{$key} = []; - $LOCKER{$key} = $Coro::current;#d# - - Coro::guard { - delete $LOCKER{$key};#d# - # wake up all waiters, to be on the safe side - $_->ready for @{ delete $LOCK{$key} }; - } + $LOCKS->guard ($_[0]) } sub lock_active($) { - my ($key) = @_; - - ! ! $LOCK{$key} + $LOCKS->count ($_[0]) < 1 } sub freeze_mainloop { tick_inhibit_inc; - Coro::guard \&tick_inhibit_dec; + &Guard::guard (\&tick_inhibit_dec); } =item cf::periodic $interval, $cb @@ -429,6 +457,8 @@ }; sub get_slot($;$$) { + return if tick_inhibit || $Coro::current == $Coro::main; + my ($time, $pri, $name) = @_; $time = $TICK * .6 if $time > $TICK * .6; @@ -724,7 +754,7 @@ =head2 ATTACHABLE OBJECTS -Many objects in crossfire are so-called attachable objects. That means you can +Many objects in deliantra are so-called attachable objects. That means you can attach callbacks/event handlers (a collection of which is called an "attachment") to it. All such attachable objects support the following methods. @@ -784,7 +814,7 @@ Register an attachment by C<$name> through which attachable objects of the given CLASS can refer to this attachment. -Some classes such as crossfire maps and objects can specify attachments +Some classes such as deliantra maps and objects can specify attachments that are attached at load/instantiate time, thus the need for a name. These calls expect any number of the following handler/hook descriptions: @@ -1085,7 +1115,8 @@ # basically do the same as instantiate, without calling instantiate my ($obj) = @_; - bless $obj, ref $obj; # re-bless in case extensions have been reloaded + # no longer needed after getting rid of delete_package? + #bless $obj, ref $obj; # re-bless in case extensions have been reloaded my $registry = $obj->registry; @@ -1141,18 +1172,24 @@ $decname, length $$rdata, scalar @$objs; if (my $fh = aio_open "$filename~", O_WRONLY | O_CREAT, 0600) { - chmod SAVE_MODE, $fh; + aio_chmod $fh, SAVE_MODE; aio_write $fh, 0, (length $$rdata), $$rdata, 0; - aio_fsync $fh if $cf::USE_FSYNC; - close $fh; + if ($cf::USE_FSYNC) { + aio_sync_file_range $fh, 0, 0, IO::AIO::SYNC_FILE_RANGE_WAIT_BEFORE | IO::AIO::SYNC_FILE_RANGE_WRITE | IO::AIO::SYNC_FILE_RANGE_WAIT_AFTER; + aio_fsync $fh; + } + aio_close $fh; if (@$objs) { if (my $fh = aio_open "$filename.pst~", O_WRONLY | O_CREAT, 0600) { - chmod SAVE_MODE, $fh; + aio_chmod $fh, SAVE_MODE; my $data = Coro::Storable::nfreeze { version => 1, objs => $objs }; aio_write $fh, 0, (length $data), $data, 0; - aio_fsync $fh if $cf::USE_FSYNC; - close $fh; + if ($cf::USE_FSYNC) { + aio_sync_file_range $fh, 0, 0, IO::AIO::SYNC_FILE_RANGE_WAIT_BEFORE | IO::AIO::SYNC_FILE_RANGE_WRITE | IO::AIO::SYNC_FILE_RANGE_WAIT_AFTER; + aio_fsync $fh; + } + aio_close $fh; aio_rename "$filename.pst~", "$filename.pst"; } } else { @@ -1160,8 +1197,11 @@ } aio_rename "$filename~", $filename; + + $filename =~ s%/[^/]+$%%; + aio_pathsync $filename if $cf::USE_FSYNC; } else { - warn "FATAL: $filename~: $!\n"; + warn "unable to save objects: $filename~: $!\n"; } } else { aio_unlink $filename; @@ -1262,6 +1302,8 @@ $EXTICMD{$name} = $cb; } +use File::Glob (); + cf::player->attach ( on_command => sub { my ($pl, $name, $params) = @_; @@ -1303,6 +1345,19 @@ }, ); +# "readahead" all extensions +sub cache_extensions { + my $grp = IO::AIO::aio_group; + + add $grp IO::AIO::aio_readdirx $LIBDIR, IO::AIO::READDIR_STAT_ORDER, sub { + for (grep /\.ext$/, @{$_[0]}) { + add $grp IO::AIO::aio_load "$LIBDIR/$_", my $data; + } + }; + + $grp +} + sub load_extensions { cf::sync_job { my %todo; @@ -1331,7 +1386,7 @@ if $source =~ /\A#!.*?perl.*?#\s*(.*)$/m; $ext{source} = - "package $pkg; use strict; use utf8;\n" + "package $pkg; use 5.10.0; use strict 'vars', 'subs'; use utf8;\n" . "#line 1 \"$path\"\n{\n" . $source . "\n};\n1"; @@ -1381,7 +1436,7 @@ =head2 CORE EXTENSIONS -Functions and methods that extend core crossfire objects. +Functions and methods that extend core deliantra objects. =cut @@ -1434,7 +1489,7 @@ my ($login) = @_; $cf::PLAYER{$login} - or cf::sync_job { !aio_stat path $login } + or !aio_stat path $login } sub find($) { @@ -1456,6 +1511,7 @@ my $pl = cf::player::load_pl $f or return; + local $cf::PLAYER_LOADING{$login} = $pl; $f->resolve_delayed_derefs; $cf::PLAYER{$login} = $pl @@ -1476,6 +1532,8 @@ aio_mkdir playerdir $pl, 0770; $pl->{last_save} = $cf::RUNTIME; + cf::get_slot 0.01; + $pl->save_pl ($path); cf::cede_to_tick; } @@ -1516,10 +1574,12 @@ my $name = $pl->ob->name; $pl->{deny_save} = 1; - $pl->password ("*"); # this should lock out the player until we nuked the dir + $pl->password ("*"); # this should lock out the player until we have nuked the dir $pl->invoke (cf::EVENT_PLAYER_LOGOUT, 1) if $pl->active; $pl->deactivate; + my $killer = cf::arch::get "killer_quit"; $pl->killer ($killer); $killer->destroy; + $pl->ob->check_score; $pl->invoke (cf::EVENT_PLAYER_QUIT); $pl->ns->destroy if $pl->ns; @@ -1568,7 +1628,8 @@ my $path = path $login; # a .pst is a dead give-away for a valid player - unless (-e "$path.pst") { + # if no pst file found, open and chekc for blocked users + if (aio_stat "$path.pst") { my $fh = aio_open $path, Fcntl::O_RDONLY, 0 or next; aio_read $fh, 0, 512, my $buf, 0 or next; $buf !~ /^password -------------$/m or next; # official not-valid tag @@ -1609,98 +1670,9 @@ \@paths } -=item $protocol_xml = $player->expand_cfpod ($crossfire_pod) +=item $protocol_xml = $player->expand_cfpod ($cfpod) -Expand crossfire pod fragments into protocol xml. - -=cut - -use re 'eval'; - -my $group; -my $interior; $interior = qr{ - # match a pod interior sequence sans C<< >> - (?: - \ (.*?)\ (?{ $group = $^N }) - | < (??{$interior}) > - ) -}x; - -sub expand_cfpod { - my ($self, $pod) = @_; - - my $xml; - - while () { - if ($pod =~ /\G( (?: [^BCGHITU]+ | .(?!<) )+ )/xgcs) { - $group = $1; - - $group =~ s/&/&/g; - $group =~ s/]*) (?{ $group = $^N }) - | < $interior > - ) - > - %gcsx - ) { - my ($code, $data) = ($1, $group); - - if ($code eq "B") { - $xml .= "" . expand_cfpod ($self, $data) . ""; - } elsif ($code eq "I") { - $xml .= "" . expand_cfpod ($self, $data) . ""; - } elsif ($code eq "U") { - $xml .= "" . expand_cfpod ($self, $data) . ""; - } elsif ($code eq "C") { - $xml .= "" . expand_cfpod ($self, $data) . ""; - } elsif ($code eq "T") { - $xml .= "" . expand_cfpod ($self, $data) . ""; - } elsif ($code eq "G") { - my ($male, $female) = split /\|/, $data; - $data = $self->gender ? $female : $male; - $xml .= expand_cfpod ($self, $data); - } elsif ($code eq "H") { - $xml .= ("[" . expand_cfpod ($self, $data) . " (Use hintmode to suppress hints)]", - "[Hint suppressed, see hintmode]", - "") - [$self->{hintmode}]; - } else { - $xml .= "error processing '$code($data)' directive"; - } - } else { - if ($pod =~ /\G(.+)/) { - warn "parse error while expanding $pod (at $1)"; - } - last; - } - } - - for ($xml) { - # create single paragraphs (very hackish) - s/(?<=\S)\n(?=\w)/ /g; - - # compress some whitespace - s/\s+\n/\n/g; # ws line-ends - s/\n\n+/\n/g; # double lines - s/^\n+//; # beginning lines - s/\n+$//; # ending lines - } - - $xml -} - -no re 'eval'; - -sub hintmode { - $_[0]{hintmode} = $_[1] if @_ > 1; - $_[0]{hintmode} -} +Expand deliantra pod fragments into protocol xml. =item $player->ext_reply ($msgid, @msg) @@ -1777,6 +1749,9 @@ sub generate_random_map { my ($self, $rmp) = @_; + + my $lock = cf::lock_acquire "generate_random_map"; # the random map generator is NOT reentrant ATM + # mit "rum" bekleckern, nicht $self->_create_random_map ( $rmp->{wallstyle}, $rmp->{wall_name}, $rmp->{floorstyle}, $rmp->{monsterstyle}, @@ -1908,7 +1883,7 @@ sub save_path { my ($self) = @_; - (my $path = $_[0]{path}) =~ s/\//$PATH_SEP/g; + (my $path = $_[0]{path}) =~ s/\//$PATH_SEP/go; "$TMPDIR/$path.map" } @@ -1916,19 +1891,10 @@ sub uniq_path { my ($self) = @_; - (my $path = $_[0]{path}) =~ s/\//$PATH_SEP/g; + (my $path = $_[0]{path}) =~ s/\//$PATH_SEP/go; "$UNIQUEDIR/$path" } -# and all this just because we cannot iterate over -# all maps in C++... -sub change_all_map_light { - my ($change) = @_; - - $_->change_map_light ($change) - for grep $_->outdoor, values %cf::MAP; -} - sub decay_objects { my ($self) = @_; @@ -2020,13 +1986,10 @@ $path = normalise $path, $origin && $origin->path; - cf::lock_wait "map_data:$path";#d#remove - cf::lock_wait "map_find:$path"; + my $guard1 = cf::lock_acquire "map_data:$path";#d#remove + my $guard2 = cf::lock_acquire "map_find:$path"; $cf::MAP{$path} || do { - my $guard1 = cf::lock_acquire "map_find:$path"; - my $guard2 = cf::lock_acquire "map_data:$path"; # just for the fun of it - my $map = new_from_path cf::map $path or return; @@ -2038,8 +2001,8 @@ if ($map->should_reset) {#d#TODO# disabled, crashy (locking issue?) # doing this can freeze the server in a sync job, obviously #$cf::WAIT_FOR_TICK->wait; - undef $guard1; undef $guard2; + undef $guard1; $map->reset; return find $path; } @@ -2076,7 +2039,7 @@ $self->_load_objects ($f) or return; - $self->set_object_flag (cf::FLAG_OBJ_ORIGINAL, 1) + $self->post_load_original if delete $self->{load_original}; if (my $uniq = $self->uniq_path) { @@ -2104,15 +2067,13 @@ $self->fix_auto_apply; $self->update_buttons; cf::cede_to_tick; - $self->set_darkness_map; - cf::cede_to_tick; $self->activate; } $self->{last_save} = $cf::RUNTIME; $self->last_access ($cf::RUNTIME); - $self->in_memory (cf::MAP_IN_MEMORY); + $self->in_memory (cf::MAP_ACTIVE); } $self->post_load; @@ -2180,7 +2141,7 @@ $path = normalise $path, $origin && $origin->{path}; if (my $map = $cf::MAP{$path}) { - return $map if !$load || $map->in_memory == cf::MAP_IN_MEMORY; + return $map if !$load || $map->in_memory == cf::MAP_ACTIVE; } $MAP_PREFETCH{$path} |= $load; @@ -2227,6 +2188,8 @@ $_->contr->save for $self->players; }; + cf::get_slot 0.02; + if ($uniq) { $self->_save_objects ($save, cf::IO_HEADER | cf::IO_OBJECTS); $self->_save_objects ($uniq, cf::IO_UNIQUES); @@ -2244,7 +2207,7 @@ my $lock = cf::lock_acquire "map_data:$self->{path}"; return if $self->players; - return if $self->in_memory != cf::MAP_IN_MEMORY; + return if $self->in_memory != cf::MAP_ACTIVE; return if $self->{deny_save}; $self->in_memory (cf::MAP_SWAPPED); @@ -2279,7 +2242,7 @@ return if $self->players; - warn "resetting map ", $self->path; + warn "resetting map ", $self->path, "\n"; $self->in_memory (cf::MAP_SWAPPED); @@ -2372,9 +2335,10 @@ [ map { utf8::decode $_; - /\.map$/ - ? normalise $_ - : () + s/\.map$//; # TODO future compatibility hack + /\.pst$/ || !/^$PATH_SEP/o # TODO unique maps apparebntly lack the .map suffix :/ + ? () + : normalise $_ } @{ aio_readdir $UNIQUEDIR or [] } ] } @@ -2391,7 +2355,8 @@ =item $ob->inv_recursive -Returns the inventory of the object _and_ their inventories, recursively. +Returns the inventory of the object I their inventories, recursively, +but I the object itself. =cut @@ -2406,7 +2371,7 @@ =item $ref = $ob->ref -creates and returns a persistent reference to an objetc that can be stored as a string. +Creates and returns a persistent reference to an object that can be stored as a string. =item $ob = cf::object::deref ($refstring) @@ -2448,6 +2413,20 @@ =cut +our $SAY_CHANNEL = { + id => "say", + title => "Map", + reply => "say ", + tooltip => "Things said to and replied from NPCs near you and other players on the same map only.", +}; + +our $CHAT_CHANNEL = { + id => "chat", + title => "Chat", + reply => "chat ", + tooltip => "Player chat and shouts, global to the server.", +}; + # rough implementation of a future "reply" method that works # with dialog boxes. #TODO: the first argument must go, split into a $npc->reply_to ( method @@ -2468,7 +2447,7 @@ } else { $msg = $npc->name . " says: $msg" if $npc; - $self->message ($msg, $flags); + $self->send_msg ($SAY_CHANNEL => $msg, $flags); } } } @@ -2503,8 +2482,10 @@ Freezes the player and moves him/her to a special map (C<{link}>). -The player should be reasonably safe there for short amounts of time. You -I call C as soon as possible, though. +The player should be reasonably safe there for short amounts of time (e.g. +for loading a map). You I call C as soon as possible, +though, as the palyer cannot control the character while it is on the link +map. Will never block. @@ -2572,10 +2553,12 @@ $map->load_neighbours; 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); + + # only activate afterwards, to support waiting in hooks + $self->activate_recursive; } =item $player_object->goto ($path, $x, $y[, $check->($map)[, $done->()]]) @@ -2586,6 +2569,9 @@ the new (success) or old (failed) map position. In either case, $done will be called at the end of this process. +Note that $check will be called with a potentially non-loaded map, so if +it needs a loaded map it has to call C<< ->load >>. + =cut our $GOTOGEN; @@ -2673,8 +2659,6 @@ 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 @@ -2686,11 +2670,13 @@ $rmp->{origin_map} = $exit->map->path; $rmp->{origin_x} = $exit->x; $rmp->{origin_y} = $exit->y; + + $exit->map->touch; } $rmp->{random_seed} ||= $exit->random_seed; - my $data = cf::encode_json $rmp; + my $data = JSON::XS->new->utf8->pretty->canonical->encode ($rmp); my $md5 = Digest::MD5::md5_hex $data; my $meta = "$RANDOMDIR/$md5.meta"; @@ -2699,8 +2685,12 @@ undef $fh; aio_rename "$meta~", $meta; - $exit->slaying ("?random/$md5"); - $exit->msg (undef); + my $slaying = "?random/$md5"; + + if ($exit->valid) { + $exit->slaying ("?random/$md5"); + $exit->msg (undef); + } } } @@ -2709,33 +2699,35 @@ return unless $self->type == cf::PLAYER; - if ($exit->slaying eq "/!") { - #TODO: this should de-fi-ni-te-ly not be a sync-job - # the problem is that $exit might not survive long enough - # so it needs to be done right now, right here - 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; - # if exit is damned, update players death & WoR home-position - $self->contr->savebed ($slaying, $hp, $sp) - if $exit->flag (FLAG_DAMNED); - (async { - $Coro::current->{desc} = "enter_exit $slaying $hp $sp"; + $Coro::current->{desc} = "enter_exit"; - $self->deactivate_recursive; # just to be sure unless (eval { - $self->goto ($slaying, $hp, $sp); + $self->deactivate_recursive; # just to be sure + + # random map handling + { + my $guard = cf::lock_acquire "exit_prepare:$exit"; + + prepare_random_map $exit + if $exit->slaying eq "/!"; + } - 1; + my $map = cf::map::normalise $exit->slaying, $exit->map && $exit->map->path; + my $x = $exit->stats->hp; + my $y = $exit->stats->sp; + + $self->goto ($map, $x, $y); + + # if exit is damned, update players death & WoR home-position + $self->contr->savebed ($map, $x, $y) + if $exit->flag (cf::FLAG_DAMNED); + + 1 }) { - $self->message ("Something went wrong deep within the crossfire server. " + $self->message ("Something went wrong deep within the deliantra server. " . "I'll try to bring you back to the map you were before. " . "Please report this to the dungeon master!", cf::NDI_UNIQUE | cf::NDI_RED); @@ -2775,6 +2767,12 @@ # non-persistent channels (usually the info channel) our %CHANNEL = ( + "c/motd" => { + id => "infobox", + title => "MOTD", + reply => undef, + tooltip => "The message of the day", + }, "c/identify" => { id => "infobox", title => "Identify", @@ -2787,6 +2785,12 @@ reply => undef, tooltip => "Signs and other items you examined", }, + "c/shopinfo" => { + id => "infobox", + title => "Shop Info", + reply => undef, + tooltip => "What your bargaining skill tells you about the shop", + }, "c/book" => { id => "infobox", title => "Book", @@ -2811,6 +2815,42 @@ reply => undef, tooltip => "Shows which body parts you posess and are available", }, + "c/statistics" => { + id => "infobox", + title => "Statistics", + reply => undef, + tooltip => "Shows your primary statistics", + }, + "c/skills" => { + id => "infobox", + title => "Skills", + reply => undef, + tooltip => "Shows your experience per skill and item power", + }, + "c/shopitems" => { + id => "infobox", + title => "Shop Items", + reply => undef, + tooltip => "Shows the items currently for sale in this shop", + }, + "c/resistances" => { + id => "infobox", + title => "Resistances", + reply => undef, + tooltip => "Shows your resistances", + }, + "c/pets" => { + id => "infobox", + title => "Pets", + reply => undef, + tooltip => "Shows information abotu your pets/a specific pet", + }, + "c/perceiveself" => { + id => "infobox", + title => "Perceive Self", + reply => undef, + tooltip => "You gained detailed knowledge about yourself", + }, "c/uptime" => { id => "infobox", title => "Uptime", @@ -2823,12 +2863,27 @@ reply => undef, tooltip => "Information related to the maps", }, + "c/party" => { + id => "party", + title => "Party", + reply => "gsay ", + tooltip => "Messages and chat related to your party", + }, + "c/death" => { + id => "death", + title => "Death", + reply => undef, + tooltip => "Reason for and more info about your most recent death", + }, + "c/say" => $SAY_CHANNEL, + "c/chat" => $CHAT_CHANNEL, ); sub cf::client::send_msg { my ($self, $channel, $msg, $color, @extra) = @_; - $msg = $self->pl->expand_cfpod ($msg); + $msg = $self->pl->expand_cfpod ($msg) + unless $color & cf::NDI_VERBATIM; $color &= cf::NDI_CLIENT_MASK; # just in case... @@ -2836,17 +2891,14 @@ if ($CHANNEL{$channel}) { $channel = $CHANNEL{$channel}; - $self->ext_msg (channel_info => $channel) - if $self->can_msg; - + $self->ext_msg (channel_info => $channel); $channel = $channel->{id}; } elsif (ref $channel) { # send meta info to client, if not yet sent unless (exists $self->{channel}{$channel->{id}}) { $self->{channel}{$channel->{id}} = $channel; - $self->ext_msg (channel_info => $channel) - if $self->can_msg; + $self->ext_msg (channel_info => $channel); } $channel = $channel->{id}; @@ -2854,38 +2906,26 @@ return unless @extra || length $msg; - if ($self->can_msg) { - # default colour, mask it out - $color &= ~(cf::NDI_COLOR_MASK | cf::NDI_DEF) - if $color & cf::NDI_DEF; - - $self->send_packet ("msg " . $self->{json_coder}->encode ( - [$color & cf::NDI_CLIENT_MASK, $channel, $msg, @extra])); - } else { - if ($color >= 0) { - # replace some tags by gcfclient-compatible ones - for ($msg) { - 1 while - s/([^<]*)<\/b>/[b]${1}[\/b]/ - || s/([^<]*)<\/i>/[i]${1}[\/i]/ - || s/([^<]*)<\/u>/[ul]${1}[\/ul]/ - || s/([^<]*)<\/tt>/[fixed]${1}[\/fixed]/ - || s/([^<]*)<\/fg>/[color=$1]${2}[\/color]/; - } - - $color &= cf::NDI_COLOR_MASK; - - utf8::encode $msg; - - if (0 && $msg =~ /\[/) { - # COMMAND/INFO - $self->send_packet ("drawextinfo $color 10 8 $msg") - } else { - $msg =~ s/\[\/?(?:b|i|u|fixed|color)[^\]]*\]//g; - $self->send_packet ("drawinfo $color $msg") - } - } + # default colour, mask it out + $color &= ~(cf::NDI_COLOR_MASK | cf::NDI_DEF) + if $color & cf::NDI_DEF; + + my $pkt = "msg " + . $self->{json_coder}->encode ( + [$color & cf::NDI_CLIENT_MASK, $channel, $msg, @extra] + ); + + # try lzf for large packets + $pkt = "lzf " . Compress::LZF::compress $pkt + if 1024 <= length $pkt and $self->{can_lzf}; + + # split very large packets + if (8192 < length $pkt and $self->{can_lzf}) { + $self->send_packet ("frag $_") for unpack "(a8192)*", $pkt; + $pkt = "frag"; } + + $self->send_packet ($pkt); } =item $client->ext_msg ($type, @msg) @@ -3069,7 +3109,8 @@ cf::object contr pay_amount pay_player map x y force_find force_add destroy - insert remove name archname title slaying race decrease_ob_nr + insert remove name archname title slaying race decrease split + value cf::object::player player @@ -3084,10 +3125,10 @@ for ( ["cf::object" => qw(contr pay_amount pay_player map force_find force_add x y - insert remove inv name archname title slaying race - decrease_ob_nr destroy)], + insert remove inv nrof name archname title slaying race + decrease split destroy change_exp value msg lore send_msg)], ["cf::object::player" => qw(player)], - ["cf::player" => qw(peaceful)], + ["cf::player" => qw(peaceful send_msg)], ["cf::map" => qw(trigger)], ) { no strict 'refs'; @@ -3115,6 +3156,8 @@ $qcode =~ s/"/‟/g; # not allowed in #line filenames $qcode =~ s/\n/\\n/g; + %vars = (_dummy => 0) unless %vars; + local $_; local @safe::cf::_safe_eval_args = values %vars; @@ -3303,6 +3346,16 @@ } }; +sub load_resource_file($) { + my $guard = lock_acquire "load_resource_file"; + + my $status = load_resource_file_ $_[0]; + get_slot 0.1, 100; + cf::arch::commit_load; + + $status +} + sub reload_regions { # HACK to clear player env face cache, we need some signal framework # for this (global event?) @@ -3325,11 +3378,6 @@ sub reload_archetypes { load_resource_file "$DATADIR/archetypes" or die "unable to load archetypes\n"; - #d# NEED to laod twice to resolve forward references - # this really needs to be done in an extra post-pass - # (which needs to be synchronous, so solve it differently) - load_resource_file "$DATADIR/archetypes" - or die "unable to load archetypes\n"; } sub reload_treasures { @@ -3340,25 +3388,22 @@ sub reload_resources { warn "reloading resource files...\n"; - reload_regions; reload_facedata; - #reload_archetypes;#d# reload_archetypes; + reload_regions; reload_treasures; warn "finished reloading resource files\n"; } -sub init { - reload_resources; -} - sub reload_config { + warn "reloading config file...\n"; + open my $fh, "<:utf8", "$CONFDIR/config" or return; local $/; - *CFG = YAML::Load <$fh>; + *CFG = YAML::XS::Load scalar <$fh>; $EMERGENCY_POSITION = $CFG{emergency_position} || ["/world/world_105_115", 5, 37]; @@ -3372,9 +3417,53 @@ }; warn $@ if $@; } + + warn "finished reloading resource files\n"; +} + +sub pidfile() { + sysopen my $fh, $PIDFILE, O_RDWR | O_CREAT + or die "$PIDFILE: $!"; + flock $fh, &Fcntl::LOCK_EX + or die "$PIDFILE: flock: $!"; + $fh +} + +# make sure only one server instance is running at any one time +sub atomic { + my $fh = pidfile; + + my $pid = <$fh>; + kill 9, $pid if $pid > 0; + + seek $fh, 0, 0; + print $fh $$; +} + +sub main_loop { + warn "EV::loop starting\n"; + if (1) { + EV::loop; + } + warn "EV::loop returned\n"; + goto &main_loop unless $REALLY_UNLOOP; } sub main { + 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; + + $Coro::current->prio (Coro::PRIO_MAX); # give the main loop max. priority + # 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# @@ -3384,13 +3473,36 @@ })->prio (Coro::PRIO_MAX); }; - reload_config; - db_init; - load_extensions; - - $Coro::current->prio (Coro::PRIO_MAX); # give the main loop max. priority evthread_start IO::AIO::poll_fileno; - EV::loop; + + 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; + + utime time, time, $RUNTIMEFILE; + + # no (long-running) fork's whatsoever before this point(!) + use POSIX (); + POSIX::close delete $ENV{LOCKUTIL_LOCK_FD} if exists $ENV{LOCKUTIL_LOCK_FD}; + + (pop @POST_INIT)->(0) while @POST_INIT; + }; + + main_loop; } ############################################################################# @@ -3406,17 +3518,15 @@ } } -sub write_runtime { - my $runtime = "$LOCALDIR/runtime"; - +sub write_runtime_sync { # first touch the runtime file to show we are still running: # the fsync below can take a very very long time. - IO::AIO::aio_utime $runtime, undef, undef; + IO::AIO::aio_utime $RUNTIMEFILE, undef, undef; my $guard = cf::lock_acquire "write_runtime"; - my $fh = aio_open "$runtime~", O_WRONLY | O_CREAT, 0644 + my $fh = aio_open "$RUNTIMEFILE~", O_WRONLY | O_CREAT, 0644 or return; my $value = $cf::RUNTIME + 90 + 10; @@ -3436,7 +3546,7 @@ close $fh or return; - aio_rename "$runtime~", $runtime + aio_rename "$RUNTIMEFILE~", $RUNTIMEFILE and return; warn "runtime file written.\n"; @@ -3458,7 +3568,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 @@ -3490,39 +3607,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 "emergency_perl_save: starting sync()\n"; + IO::AIO::aio_sync sub { + warn "emergency_perl_save: finished sync()\n"; }; - warn "leave emergency perl save\n"; + warn "emergency_perl_save: leave\n"; } sub post_cleanup { @@ -3530,6 +3657,35 @@ warn Carp::longmess "post_cleanup backtrace" if $make_core; + + my $fh = pidfile; + unlink $PIDFILE if <$fh> == $$; +} + +# a safer delete_package, copied from Symbol +sub clear_package($) { + my $pkg = shift; + + # expand to full symbol table name if needed + unless ($pkg =~ /^main::.*::$/) { + $pkg = "main$pkg" if $pkg =~ /^::/; + $pkg = "main::$pkg" unless $pkg =~ /^main::/; + $pkg .= '::' unless $pkg =~ /::$/; + } + + my($stem, $leaf) = $pkg =~ m/(.*::)(\w+::)$/; + my $stem_symtab = *{$stem}{HASH}; + + defined $stem_symtab and exists $stem_symtab->{$leaf} + or return; + + # clear all symbols + my $leaf_symtab = *{$stem_symtab->{$leaf}}{HASH}; + for my $name (keys %$leaf_symtab) { + _gv_clear *{"$pkg$name"}; +# use PApp::Util; PApp::Util::sv_dump *{"$pkg$name"}; + } + warn "cleared package $pkg\n";#d# } sub do_reload_perl() { @@ -3539,114 +3695,123 @@ return; } - warn "reloading..."; + return if $RELOAD++; - warn "entering sync_job"; + my $t1 = EV::time; - cf::sync_job { - cf::write_runtime; # external watchdog should not bark - cf::emergency_save; - cf::write_runtime; # external watchdog should not bark + while ($RELOAD) { + warn "reloading..."; - warn "syncing database to disk"; - BDB::db_env_txn_checkpoint $DB_ENV; + warn "entering sync_job"; - # if anything goes wrong in here, we should simply crash as we already saved + cf::sync_job { + cf::write_runtime_sync; # external watchdog should not bark + cf::emergency_save; + cf::write_runtime_sync; # external watchdog should not bark - warn "flushing outstanding aio requests"; - for (;;) { - BDB::flush; - IO::AIO::flush; - Coro::cede_notself; - last unless IO::AIO::nreqs || BDB::nreqs; - warn "iterate..."; - } + warn "syncing database to disk"; + BDB::db_env_txn_checkpoint $DB_ENV; - ++$RELOAD; + # if anything goes wrong in here, we should simply crash as we already saved - warn "cancelling all extension coros"; - $_->cancel for values %EXT_CORO; - %EXT_CORO = (); + warn "flushing outstanding aio requests"; + while (IO::AIO::nreqs || BDB::nreqs) { + Coro::EV::timer_once 0.01; # let the sync_job do it's thing + } + + warn "cancelling all extension coros"; + $_->cancel for values %EXT_CORO; + %EXT_CORO = (); - warn "removing commands"; - %COMMAND = (); + warn "removing commands"; + %COMMAND = (); - warn "removing ext/exti commands"; - %EXTCMD = (); - %EXTICMD = (); + warn "removing ext/exti commands"; + %EXTCMD = (); + %EXTICMD = (); - warn "unloading/nuking all extensions"; - for my $pkg (@EXTS) { - warn "... unloading $pkg"; + warn "unloading/nuking all extensions"; + for my $pkg (@EXTS) { + warn "... unloading $pkg"; + + if (my $cb = $pkg->can ("unload")) { + eval { + $cb->($pkg); + 1 + } or warn "$pkg unloaded, but with errors: $@"; + } - if (my $cb = $pkg->can ("unload")) { - eval { - $cb->($pkg); - 1 - } or warn "$pkg unloaded, but with errors: $@"; + warn "... clearing $pkg"; + clear_package $pkg; } - warn "... nuking $pkg"; - Symbol::delete_package $pkg; - } + warn "unloading all perl modules loaded from $LIBDIR"; + while (my ($k, $v) = each %INC) { + next unless $v =~ /^\Q$LIBDIR\E\/.*\.pm$/; - warn "unloading all perl modules loaded from $LIBDIR"; - while (my ($k, $v) = each %INC) { - next unless $v =~ /^\Q$LIBDIR\E\/.*\.pm$/; + warn "... unloading $k"; + delete $INC{$k}; - warn "... unloading $k"; - delete $INC{$k}; + $k =~ s/\.pm$//; + $k =~ s/\//::/g; - $k =~ s/\.pm$//; - $k =~ s/\//::/g; + if (my $cb = $k->can ("unload_module")) { + $cb->(); + } - if (my $cb = $k->can ("unload_module")) { - $cb->(); + clear_package $k; } - Symbol::delete_package $k; - } + warn "getting rid of safe::, as good as possible"; + clear_package "safe::$_" + for qw(cf::attachable cf::object cf::object::player cf::client cf::player cf::map cf::party cf::region); - 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 "unloading cf.pm \"a bit\""; + delete $INC{"cf.pm"}; + delete $INC{"cf/$_.pm"} for @EXTRA_MODULES; - warn "unloading cf.pm \"a bit\""; - delete $INC{"cf.pm"}; - delete $INC{"cf/pod.pm"}; + # don't, removes xs symbols, too, + # and global variables created in xs + #clear_package __PACKAGE__; - # don't, removes xs symbols, too, - # and global variables created in xs - #Symbol::delete_package __PACKAGE__; + warn "unload completed, starting to reload now"; - warn "unload completed, starting to reload now"; + warn "reloading cf.pm"; + require cf; + cf::_connect_to_perl_1; - warn "reloading cf.pm"; - require cf; - cf::_connect_to_perl; # nominally unnecessary, but cannot hurt + warn "loading config and database again"; + cf::reload_config; - warn "loading config and database again"; - cf::reload_config; + warn "loading extensions"; + cf::load_extensions; - warn "loading extensions"; - cf::load_extensions; + 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 "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"; + warn "leaving sync_job"; - 1 - } or do { - warn $@; - cf::cleanup "error while reloading, exiting."; - }; + 1 + } or do { + warn $@; + cf::cleanup "error while reloading, exiting."; + }; - warn "reloaded"; + warn "reloaded"; + --$RELOAD; + } + + $t1 = EV::time - $t1; + warn "reload completed in ${t1}s\n"; }; our $RELOAD_WATCHER; # used only during reload @@ -3655,9 +3820,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; + }; }; } @@ -3681,8 +3850,7 @@ our @WAIT_FOR_TICK_BEGIN; sub wait_for_tick { - return if tick_inhibit; - return if $Coro::current == $Coro::main; + return if tick_inhibit || $Coro::current == $Coro::main; my $signal = new Coro::Signal; push @WAIT_FOR_TICK, $signal; @@ -3690,8 +3858,7 @@ } sub wait_for_tick_begin { - return if tick_inhibit; - return if $Coro::current == $Coro::main; + return if tick_inhibit || $Coro::current == $Coro::main; my $signal = new Coro::Signal; push @WAIT_FOR_TICK_BEGIN, $signal; @@ -3711,7 +3878,7 @@ $NEXT_RUNTIME_WRITE = List::Util::max $NEXT_RUNTIME_WRITE + 10, $NOW + 5.; Coro::async_pool { $Coro::current->{desc} = "runtime saver"; - write_runtime + write_runtime_sync or warn "ERROR: unable to write runtime file: $!"; }; } @@ -3740,12 +3907,13 @@ BDB::min_parallel 8; BDB::max_poll_reqs $TICK * 0.1; - $Coro::BDB::WATCHER->priority (1); + $AnyEvent::BDB::WATCHER->priority (1); unless ($DB_ENV) { $DB_ENV = BDB::db_env_create; - $DB_ENV->set_flags (BDB::AUTO_COMMIT | BDB::REGION_INIT | BDB::TXN_NOSYNC - | BDB::LOG_AUTOREMOVE, 1); + $DB_ENV->set_flags (BDB::AUTO_COMMIT | BDB::REGION_INIT); + $DB_ENV->set_flags (&BDB::LOG_AUTOREMOVE ) if BDB::VERSION v0, v4.7; + $DB_ENV->log_set_config (&BDB::LOG_AUTO_REMOVE) if BDB::VERSION v4.7; $DB_ENV->set_timeout (30, BDB::SET_TXN_TIMEOUT); $DB_ENV->set_timeout (30, BDB::SET_LOCK_TIMEOUT); @@ -3781,7 +3949,7 @@ IO::AIO::min_parallel 8; IO::AIO::max_poll_time $TICK * 0.1; - $Coro::AIO::WATCHER->priority (1); + undef $AnyEvent::AIO::WATCHER; } my $_log_backtrace; @@ -3794,6 +3962,7 @@ # limit the # of concurrent backtraces if ($_log_backtrace < 2) { ++$_log_backtrace; + my $perl_bt = Carp::longmess $msg; async { $Coro::current->{desc} = "abt $msg"; @@ -3814,7 +3983,8 @@ @funcs }; - LOG llevInfo, "[ABT] $msg\n"; + LOG llevInfo, "[ABT] $perl_bt\n"; + LOG llevInfo, "[ABT] --- C backtrace follows ---\n"; LOG llevInfo, "[ABT] $_\n" for @bt; --$_log_backtrace; }; @@ -3825,7 +3995,8 @@ } # load additional modules -use cf::pod; +require "cf/$_.pm" for @EXTRA_MODULES; +cf::_connect_to_perl_2; END { cf::emergency_save }