mirror of
git://git.psyced.org/git/psyclpc
synced 2024-08-15 03:20:16 +00:00
1553 lines
48 KiB
C
1553 lines
48 KiB
C
|
/*---------------------------------------------------------------------------
|
||
|
* Gamedriver Backend.
|
||
|
*
|
||
|
*---------------------------------------------------------------------------
|
||
|
* This module holds the backend loop, which polls the connected sockets
|
||
|
* for new input, passes the text read to the command parser (and thus
|
||
|
* executes the commands), and keeps track of regular house-keeping chores
|
||
|
* like garbage collection, swapping, heart beats and the triggers of
|
||
|
* call outs and mapping compaction.
|
||
|
*
|
||
|
* Timing is implemented using a one-shot 2 second alarm(). When the
|
||
|
* alarm is triggered, the handler sets the variable comm_time_to_call_heart-
|
||
|
* _beat which is monitored by various functions.
|
||
|
*
|
||
|
* One backend cycle begins with starting the alarm(). Then the pending
|
||
|
* heartbeats are evaluated, but no longer until the time runs out. Any
|
||
|
* heartbeat remaining will be evaluated in the next cycle. After the
|
||
|
* heartbeat, the call_outs are evaluated. Callouts have no time limit,
|
||
|
* but are bound by the eval_cost limit. Next, the object list is scanned
|
||
|
* for objects in need of a reset, cleanup or swap. The driver will
|
||
|
* (due objects given) perform at least one of each operation, but only
|
||
|
* as many as it can before the time runs out. Last, player commands are
|
||
|
* retrieved with get_message(). The semantic is so that all players are
|
||
|
* considered once before get_message() checks for a timeout. If a timeout
|
||
|
* is detected, get_message() select()s, but returns immediately with the
|
||
|
* variable time_to_call_heart_beat set, else it selects() in one second
|
||
|
* intervals until either commands come in or the time runs out.
|
||
|
*---------------------------------------------------------------------------
|
||
|
*/
|
||
|
|
||
|
#include "driver.h"
|
||
|
#include "typedefs.h"
|
||
|
|
||
|
#include "my-alloca.h"
|
||
|
#include <stddef.h>
|
||
|
#include <ctype.h>
|
||
|
#include <limits.h>
|
||
|
#include <stdio.h>
|
||
|
#include <sys/types.h>
|
||
|
#include <sys/time.h>
|
||
|
#include <signal.h>
|
||
|
#include <sys/times.h>
|
||
|
#include <sys/stat.h>
|
||
|
#include <fcntl.h>
|
||
|
#include <time.h>
|
||
|
#include <math.h>
|
||
|
|
||
|
#include "backend.h"
|
||
|
#include "actions.h"
|
||
|
#include "array.h"
|
||
|
#include "call_out.h"
|
||
|
#include "closure.h"
|
||
|
#include "comm.h"
|
||
|
#include "ed.h"
|
||
|
#include "exec.h"
|
||
|
#include "filestat.h"
|
||
|
#include "gcollect.h"
|
||
|
#include "heartbeat.h"
|
||
|
#include "interpret.h"
|
||
|
#include "lex.h"
|
||
|
#include "main.h"
|
||
|
#include "mapping.h"
|
||
|
#include "mregex.h"
|
||
|
#include "mstrings.h"
|
||
|
#include "object.h"
|
||
|
#include "otable.h"
|
||
|
#include "random.h"
|
||
|
#include "simulate.h"
|
||
|
#include "stdstrings.h"
|
||
|
#include "svalue.h"
|
||
|
#ifdef USE_SWAP
|
||
|
#include "swap.h"
|
||
|
#endif
|
||
|
#include "wiz_list.h"
|
||
|
#include "xalloc.h"
|
||
|
|
||
|
#include "../mudlib/sys/driver_hook.h"
|
||
|
#include "../mudlib/sys/debug_message.h"
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
|
||
|
mp_int current_time;
|
||
|
/* The current time, updated every heart beat.
|
||
|
* TODO: Should be time_t if it is given that time_t is always a skalar.
|
||
|
*/
|
||
|
|
||
|
mp_int time_of_last_hb = 0;
|
||
|
/* For synchronous heart beats: the time of the last beat.
|
||
|
*/
|
||
|
|
||
|
Bool time_to_call_heart_beat;
|
||
|
/* True: It's time to call the heart beat. Set by comm.c when it recognizes
|
||
|
* an alarm(). */
|
||
|
|
||
|
volatile mp_int alarm_called = MY_FALSE;
|
||
|
/* The alarm() handler sets this to TRUE whenever it is called,
|
||
|
* to allow check_alarm() to verify that the alarm is still alive.
|
||
|
*/
|
||
|
|
||
|
volatile Bool comm_time_to_call_heart_beat = MY_FALSE;
|
||
|
/* True: An heart beat alarm() happened. Set from the alarm handler, this
|
||
|
* causes comm.c to set time_to_call_heart_beat.
|
||
|
*/
|
||
|
|
||
|
volatile mp_int total_alarms = 0;
|
||
|
/* The total number of alarm()s so far, incremented from the alarm handler.
|
||
|
*/
|
||
|
|
||
|
uint32 total_player_commands = 0;
|
||
|
/* Total number of player commands so far.
|
||
|
*/
|
||
|
|
||
|
uint num_listed_objs = 0;
|
||
|
/* Number of objects in the object list.
|
||
|
*/
|
||
|
|
||
|
uint num_last_processed = 0;
|
||
|
/* Number of object processed in last process_objects().
|
||
|
*/
|
||
|
|
||
|
uint num_last_data_cleaned = 0;
|
||
|
/* Number of object data-cleaned in last process_objects().
|
||
|
*/
|
||
|
|
||
|
statistic_t stat_last_processed = { 0 };
|
||
|
statistic_t stat_last_data_cleaned = { 0 };
|
||
|
statistic_t stat_in_list = { 0 };
|
||
|
/* Decaying average number of objects processed and objects in the list.
|
||
|
*/
|
||
|
|
||
|
Bool extra_jobs_to_do = MY_FALSE;
|
||
|
/* True: the backend has other things to do in this cycle than just
|
||
|
* parsing commands or calling the heart_beat.
|
||
|
*/
|
||
|
|
||
|
GC_Request gc_request = gcDont;
|
||
|
/* gcDont: No garbage collection is due.
|
||
|
* gcMalloc: The mallocator requested a gc (requires extra_jobs_to_do).
|
||
|
* gcEfun: GC requested by efun (requires extra_jobs_to_do).
|
||
|
*/
|
||
|
|
||
|
/* TODO: all the 'extra jobs to do' should be collected here, in a nice
|
||
|
* TODO:: struct.
|
||
|
*/
|
||
|
|
||
|
#ifdef USE_TRASH
|
||
|
Bool mud_is_up = MY_FALSE;
|
||
|
/* True: the driver is finished with the initial processing
|
||
|
* and has entered the main loop. This flag is currently not
|
||
|
* used by the driver, but can be useful for printf()-style debugging.
|
||
|
*/
|
||
|
#endif
|
||
|
|
||
|
statistic_t stat_load = { 0 };
|
||
|
/* The load average (player commands/second), weighted over the
|
||
|
* last period of time.
|
||
|
*/
|
||
|
|
||
|
statistic_t stat_compile = { 0 };
|
||
|
/* The average of compiled lines/second, weighted over the last period
|
||
|
* of time.
|
||
|
*/
|
||
|
|
||
|
static time_t time_last_slow_shut = 0;
|
||
|
/* Time of the last call to slow_shut_down(), to avoid repeated
|
||
|
* calls while the previous ones are still working.
|
||
|
*/
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
|
||
|
/* --- Forward declarations --- */
|
||
|
|
||
|
static void process_objects(void);
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
void
|
||
|
update_statistic (statistic_t * pStat, long number)
|
||
|
|
||
|
/* Add the <number> to the statistics in <pStat> and update the weighted
|
||
|
* average over the last period of time.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
mp_int n;
|
||
|
double c;
|
||
|
|
||
|
pStat->sum += number;
|
||
|
if (current_time == pStat->last_time)
|
||
|
return;
|
||
|
n = current_time - pStat->last_time;
|
||
|
if (n < (int) (sizeof avg_consts / sizeof avg_consts[0]) )
|
||
|
c = avg_consts[n];
|
||
|
else
|
||
|
c = exp(- n / 900.0);
|
||
|
pStat->weighted_avg = c * pStat->weighted_avg + pStat->sum * (1 - c) / n;
|
||
|
pStat->last_time = current_time;
|
||
|
pStat->sum = 0;
|
||
|
} /* update_statistic() */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
void
|
||
|
update_statistic_avg (statistic_t * pStat, long number)
|
||
|
|
||
|
/* Add the <number> to the statistics in <pStat> and update the weighted
|
||
|
* average by degrading the previous value.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
double c;
|
||
|
|
||
|
pStat->sum += number;
|
||
|
c = avg_consts[2];
|
||
|
pStat->weighted_avg = (1 - c) * pStat->weighted_avg + number * c;
|
||
|
} /* update_statistic_avg() */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
double
|
||
|
relate_statistics (statistic_t sStat, statistic_t sRef)
|
||
|
|
||
|
/* Express the <sStat>.weighted_avg as a multiple of <sRef>.weighted_avg
|
||
|
* and return the factor.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
if (sRef.weighted_avg == 0.0)
|
||
|
return 0.0;
|
||
|
|
||
|
return sStat.weighted_avg / sRef.weighted_avg;
|
||
|
} /* relate_statistics() */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
void
|
||
|
update_compile_av (int lines)
|
||
|
|
||
|
/* Compute the average of compiled lines/second, weighted over the
|
||
|
* last period of time.
|
||
|
*
|
||
|
* The function is called after every compilation and basically sums up
|
||
|
* the number of compiled lines in one backend loop.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
update_statistic(&stat_compile, lines);
|
||
|
} /* update_compile_av() */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
void
|
||
|
clear_state (void)
|
||
|
|
||
|
/* Clear the global variables of the virtual machine. This is necessary
|
||
|
* after errors, which return directly to the backend, thus skipping the
|
||
|
* clean-up code in the VM itself.
|
||
|
*
|
||
|
* This routine must only be called from top level, not from inside
|
||
|
* stack machine execution (as the stack will be cleared).
|
||
|
* TODO: This too belongs into interpret.c
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
current_loc.file = NULL;
|
||
|
current_object = NULL;
|
||
|
command_giver = NULL;
|
||
|
current_interactive = NULL;
|
||
|
previous_ob = NULL;
|
||
|
current_prog = NULL;
|
||
|
reset_machine(MY_FALSE); /* Pop down the stack. */
|
||
|
num_warning = 0;
|
||
|
} /* clear_state() */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
#ifdef USE_PARANOIA
|
||
|
|
||
|
static void
|
||
|
do_state_check (int minlvl, const char *where)
|
||
|
|
||
|
/* Perform the simplistic interpret::check_state() if the check_state_lvl
|
||
|
* is at least <minlvl>. If an inconsistency is detected, the message
|
||
|
* "<timestamp> Inconsistency <where>" and a trace of the last instructions
|
||
|
* is logged, then the state is cleared.
|
||
|
*
|
||
|
* Make sure the call to check_state()/clear_state() is
|
||
|
* not too costly, as it is done in every loop.
|
||
|
*/
|
||
|
{
|
||
|
if (check_state_level >= minlvl && check_state() )
|
||
|
{
|
||
|
debug_message("%s Inconsistency %s\n", time_stamp(), where);
|
||
|
#ifdef VERBOSE
|
||
|
printf("%s Inconsistency %s\n", time_stamp(), where);
|
||
|
#endif
|
||
|
(void)dump_trace(MY_TRUE, NULL);
|
||
|
#ifdef TRACE_CODE
|
||
|
last_instructions(TOTAL_TRACE_LENGTH, 1, 0);
|
||
|
#endif
|
||
|
clear_state();
|
||
|
}
|
||
|
} /* do_state_check() */
|
||
|
|
||
|
#else
|
||
|
|
||
|
#define do_state_check(minlvl, where)
|
||
|
|
||
|
#endif
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
static RETSIGTYPE
|
||
|
handle_hup (int sig UNUSED)
|
||
|
|
||
|
/* SIGHUP handler: request a game shutdown.
|
||
|
*/
|
||
|
{
|
||
|
#ifdef __MWERKS__
|
||
|
# pragma unused(sig)
|
||
|
#endif
|
||
|
extra_jobs_to_do = MY_TRUE;
|
||
|
game_is_being_shut_down = MY_TRUE;
|
||
|
#ifndef RETSIGTYPE_VOID
|
||
|
return 0;
|
||
|
#endif
|
||
|
} /* handle_hup() */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
static RETSIGTYPE
|
||
|
handle_usr1 (int sig UNUSED)
|
||
|
|
||
|
/* SIGUSR1 handler: request a master update.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
#ifdef __MWERKS__
|
||
|
# pragma unused(sig)
|
||
|
#endif
|
||
|
extra_jobs_to_do = MY_TRUE;
|
||
|
master_will_be_updated = MY_TRUE;
|
||
|
eval_cost += max_eval_cost >> 3;
|
||
|
(void)signal(SIGUSR1, handle_usr1);
|
||
|
#ifndef RETSIGTYPE_VOID
|
||
|
return 0;
|
||
|
#endif
|
||
|
} /* handle_usr1() */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
static RETSIGTYPE
|
||
|
handle_usr2 (int sig UNUSED)
|
||
|
|
||
|
/* SIGUSR2 handler: reopen the debug.log file.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
#ifdef __MWERKS__
|
||
|
# pragma unused(sig)
|
||
|
#endif
|
||
|
reopen_debug_log = MY_TRUE;
|
||
|
(void)signal(SIGUSR2, handle_usr2);
|
||
|
#ifndef RETSIGTYPE_VOID
|
||
|
return 0;
|
||
|
#endif
|
||
|
} /* handle_usr2() */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
static INLINE void
|
||
|
cleanup_stuff (void)
|
||
|
|
||
|
/* Perform a number of clean up operations: replacing programs, freeing
|
||
|
* driver hooks, and handling newly destructed objects.
|
||
|
* They are collected in one function so that they can be easily called from
|
||
|
* various places (backend loop and the process_objects loops); especially
|
||
|
* since it is not advisable to remove destructed objects before replacing
|
||
|
* the programs.
|
||
|
*
|
||
|
* This function does not call remove_destructed_objects() as this
|
||
|
* call might become costly, and it is also sufficient to call it in the
|
||
|
* context of process_objects() every couple of seconds.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
/* Reset the VM to avoid dangling references to invalid objects */
|
||
|
clear_state();
|
||
|
|
||
|
/* Replace programs */
|
||
|
if (obj_list_replace)
|
||
|
{
|
||
|
replace_programs();
|
||
|
}
|
||
|
|
||
|
/* Rebind all bindable closures back to the master */
|
||
|
for (i = 0; i < NUM_DRIVER_HOOKS; i++)
|
||
|
{
|
||
|
if (driver_hook[i].type == T_CLOSURE
|
||
|
&& driver_hook[i].x.closure_type == CLOSURE_LAMBDA)
|
||
|
{
|
||
|
lambda_t *l;
|
||
|
|
||
|
l = driver_hook[i].u.lambda;
|
||
|
if (l->ob != master_ob)
|
||
|
{
|
||
|
free_object(l->ob, "backend");
|
||
|
l->ob = ref_object(master_ob, "backend");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Finish up all newly destructed objects.
|
||
|
*/
|
||
|
handle_newly_destructed_objects();
|
||
|
} /* cleanup_stuff() */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
void
|
||
|
backend (void)
|
||
|
|
||
|
/* The backend loop, the backbone of the driver's operations.
|
||
|
* It only returns when the game has to be shut down.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
char buff[MAX_TEXT+4];
|
||
|
size_t len;
|
||
|
/* Note that the size of buff[] is determined by MAX_TEXT, which
|
||
|
* is the max size of the network receive buffer. IOW: no
|
||
|
* buffer overruns are possible. len is fippos new variable to
|
||
|
* handle binary data. Cheers to fippo.
|
||
|
*/
|
||
|
Bool prevent_object_cleanup;
|
||
|
/* Implement a low/high water mark handling for the call to
|
||
|
* cleanup_all_objects(), as it turns out that a single
|
||
|
* cleanup doesn't always remove enough destructed objects.
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
* Set up.
|
||
|
*/
|
||
|
|
||
|
prepare_ipc();
|
||
|
|
||
|
(void)signal(SIGHUP, handle_hup);
|
||
|
(void)signal(SIGUSR1, handle_usr1);
|
||
|
(void)signal(SIGUSR2, handle_usr2);
|
||
|
if (!t_flag) {
|
||
|
/* Start the first alarm */
|
||
|
ALARM_HANDLER_FIRST_CALL(catch_alarm);
|
||
|
current_time = get_current_time();
|
||
|
comm_time_to_call_heart_beat = MY_FALSE;
|
||
|
time_to_call_heart_beat = MY_FALSE;
|
||
|
alarm(alarm_time);
|
||
|
}
|
||
|
|
||
|
#ifdef VERBOSE
|
||
|
printf("%s " PROGNAME " ready for users.\n", time_stamp());
|
||
|
fflush(stdout);
|
||
|
#endif
|
||
|
debug_message("%s " PROGNAME " ready for users.\n", time_stamp());
|
||
|
|
||
|
toplevel_context.rt.type = ERROR_RECOVERY_BACKEND;
|
||
|
setjmp(toplevel_context.con.text);
|
||
|
|
||
|
/*
|
||
|
* We come here after errors, and have to clear some global variables.
|
||
|
*/
|
||
|
clear_state();
|
||
|
flush_all_player_mess();
|
||
|
prevent_object_cleanup = MY_FALSE;
|
||
|
|
||
|
#ifdef USE_TRASH
|
||
|
/* omg i can't believe i just found this completely useless line
|
||
|
* in the backend loop! first i move it out, second i put it into
|
||
|
* the trash. jeeesus. doesn't anybody read the code?? --lynX 2007
|
||
|
*/
|
||
|
mud_is_up = MY_TRUE;
|
||
|
#endif
|
||
|
|
||
|
/*
|
||
|
* The Loop.
|
||
|
*/
|
||
|
while(1)
|
||
|
{
|
||
|
do_state_check(1, "in main loop");
|
||
|
|
||
|
#if 0
|
||
|
/* loop watchin' */
|
||
|
putchar('_'); fflush(stdout);
|
||
|
#endif
|
||
|
|
||
|
/* interestingly on linux this doesn't change anything
|
||
|
* you can comment it out and everything still works *scratch*
|
||
|
*/
|
||
|
check_alarm();
|
||
|
|
||
|
RESET_LIMITS;
|
||
|
CLEAR_EVAL_COST;
|
||
|
|
||
|
#ifdef C_ALLOCA
|
||
|
/* Execute pending deallocations */
|
||
|
alloca(0); /* free alloca'd values from deeper levels of nesting */
|
||
|
#endif
|
||
|
|
||
|
/* Replace programs, remove destructed objects, and similar stuff */
|
||
|
cleanup_stuff();
|
||
|
|
||
|
#ifdef USE_PARANOIA
|
||
|
if (check_a_lot_ref_counts_flag)
|
||
|
check_a_lot_ref_counts(NULL);
|
||
|
/* after removing destructed objects! */
|
||
|
#endif
|
||
|
|
||
|
/*
|
||
|
* Do the extra jobs, if any.
|
||
|
*/
|
||
|
|
||
|
if (prevent_object_cleanup)
|
||
|
{
|
||
|
if (num_listed_objs >= num_destructed/2)
|
||
|
prevent_object_cleanup = MY_FALSE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (num_listed_objs <= num_destructed)
|
||
|
{
|
||
|
cleanup_all_objects();
|
||
|
prevent_object_cleanup = MY_TRUE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (extra_jobs_to_do) {
|
||
|
|
||
|
current_interactive = NULL;
|
||
|
if (game_is_being_shut_down)
|
||
|
{
|
||
|
command_giver = NULL;
|
||
|
current_object = NULL;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (master_will_be_updated) {
|
||
|
deep_destruct(master_ob);
|
||
|
master_will_be_updated = MY_FALSE;
|
||
|
command_giver = NULL;
|
||
|
current_object = &dummy_current_object_for_loads;
|
||
|
callback_master(STR_EXT_RELOAD, 0);
|
||
|
current_object = NULL;
|
||
|
}
|
||
|
|
||
|
if (gc_request != gcDont) {
|
||
|
time_t time_now = time(NULL);
|
||
|
char buf[120];
|
||
|
|
||
|
if (gc_request == gcEfun
|
||
|
|| time_now - time_last_gc >= 60)
|
||
|
{
|
||
|
sprintf(buf, "%s Garbage collection req by %s "
|
||
|
"(slow_shut to do: %d, "
|
||
|
"time since last gc: %ld\n"
|
||
|
, time_stamp()
|
||
|
, gc_request == gcEfun ? "efun" : "allocator"
|
||
|
, slow_shut_down_to_do
|
||
|
, (long)(time_now - time_last_gc)
|
||
|
);
|
||
|
write(1, buf, strlen(buf));
|
||
|
command_giver = NULL;
|
||
|
current_object = NULL;
|
||
|
garbage_collection();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
sprintf(buf, "%s Garbage collection req by %s refused "
|
||
|
"(slow_shut to do: %d, "
|
||
|
"time since last gc: %ld)\n"
|
||
|
, time_stamp()
|
||
|
, gc_request == gcEfun ? "efun" : "allocator"
|
||
|
, slow_shut_down_to_do
|
||
|
, (long)(time_now - time_last_gc));
|
||
|
write(1, buf, strlen(buf));
|
||
|
reallocate_reserved_areas();
|
||
|
}
|
||
|
|
||
|
gc_request = gcDont;
|
||
|
|
||
|
if (slow_shut_down_to_do)
|
||
|
{
|
||
|
if (time_now - time_last_slow_shut
|
||
|
>= slow_shut_down_to_do * 60
|
||
|
)
|
||
|
{
|
||
|
int minutes = slow_shut_down_to_do;
|
||
|
char shut_msg[90];
|
||
|
|
||
|
slow_shut_down_to_do = 0;
|
||
|
time_last_slow_shut = time_now;
|
||
|
malloc_privilege = MALLOC_MASTER;
|
||
|
sprintf(shut_msg, "%s slow_shut_down(%d)\n", time_stamp(), minutes);
|
||
|
write(1, shut_msg, strlen(shut_msg));
|
||
|
|
||
|
previous_ob = NULL;
|
||
|
command_giver = NULL;
|
||
|
current_interactive = NULL;
|
||
|
|
||
|
push_number(inter_sp, minutes);
|
||
|
callback_master(STR_SLOW_SHUT, 1);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
sprintf(buf, "%s Last slow_shut_down() still pending.\n"
|
||
|
, time_stamp()
|
||
|
);
|
||
|
write(1, buf, strlen(buf));
|
||
|
}
|
||
|
}
|
||
|
malloc_privilege = MALLOC_USER;
|
||
|
}
|
||
|
|
||
|
if (num_pending_outconns) {
|
||
|
/* puts("Handling outgoing connections."); */
|
||
|
check_for_out_connections();
|
||
|
} else
|
||
|
extra_jobs_to_do = MY_FALSE;
|
||
|
|
||
|
} /* if (extra_jobs_to_do */
|
||
|
|
||
|
do_state_check(2, "before get_message()");
|
||
|
|
||
|
/*
|
||
|
* Call comm.c and wait for player input, or until the next
|
||
|
* heart beat is due.
|
||
|
*/
|
||
|
|
||
|
len = 0;
|
||
|
if (get_message(buff, &len)) {
|
||
|
interactive_t *ip;
|
||
|
|
||
|
/* Create the new time_stamp string in the function's local
|
||
|
* buffer.
|
||
|
*/
|
||
|
(void)time_stamp();
|
||
|
|
||
|
total_player_commands++;
|
||
|
update_statistic(&stat_load, 1);
|
||
|
#ifdef MALLOC_EXT_STATISTICS
|
||
|
mem_update_stats();
|
||
|
#endif /* MALLOC_EXT_STATISTICS */
|
||
|
|
||
|
/*
|
||
|
* Now we have a string from the player. This string can go to
|
||
|
* one of several places. If it is prepended with input escape
|
||
|
* char, then it is an escape from the 'ed' editor, so we send it
|
||
|
* as a command to the parser.
|
||
|
* If any object function is waiting for an input string, then
|
||
|
* send it there.
|
||
|
* Otherwise, send the string to the parser.
|
||
|
* The command_parser will find that current_object is 0, and
|
||
|
* then set current_object to point to the object that defines
|
||
|
* the command. This will enable such functions to be static.
|
||
|
*/
|
||
|
current_object = NULL;
|
||
|
current_interactive = command_giver;
|
||
|
|
||
|
(void)O_SET_INTERACTIVE(ip, command_giver);
|
||
|
#ifdef DEBUG
|
||
|
if (!ip)
|
||
|
{
|
||
|
fatal("Non interactive player in main loop !\n");
|
||
|
/* NOTREACHED */
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
ip->set_input_to = MY_FALSE;
|
||
|
tracedepth = 0;
|
||
|
|
||
|
mark_start_evaluation();
|
||
|
|
||
|
if (len >= 2 && buff[0] == input_escape
|
||
|
&& buff[1] != '\0'
|
||
|
#ifdef USE_INVENTORIES
|
||
|
&& command_giver->super
|
||
|
#endif
|
||
|
)
|
||
|
{
|
||
|
if(!call_function_interactive(ip, buff, len)) {
|
||
|
/* We got a bang-input, but no input context wants
|
||
|
* to handle it - treat it as a normal command.
|
||
|
*/
|
||
|
if (ip->noecho & NOECHO)
|
||
|
{
|
||
|
/* !message while in NOECHO - simulate the
|
||
|
* echo by sending the (remaining) raw data we got.
|
||
|
*/
|
||
|
add_message("%s\n", buff + ip->chars_ready);
|
||
|
ip->chars_ready = 0;
|
||
|
}
|
||
|
execute_command(buff+1, command_giver);
|
||
|
}
|
||
|
}
|
||
|
#ifdef USE_BUILTIN_EDITOR
|
||
|
else if (O_GET_EDBUFFER(command_giver))
|
||
|
ed_cmd(buff);
|
||
|
#endif
|
||
|
else if (call_function_interactive(ip, buff, len))
|
||
|
NOOP;
|
||
|
else
|
||
|
execute_command(buff, command_giver);
|
||
|
|
||
|
mark_end_evaluation();
|
||
|
|
||
|
/* ip might be invalid again here */
|
||
|
|
||
|
/*
|
||
|
* Print a prompt if player is still here.
|
||
|
*/
|
||
|
if (command_giver)
|
||
|
{
|
||
|
if (O_SET_INTERACTIVE(ip, command_giver)
|
||
|
&& !ip->do_close)
|
||
|
{
|
||
|
print_prompt();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
do_state_check(2, "after handling message");
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
/* No new message, just reate the new time_stamp string in
|
||
|
* the function's local buffer.
|
||
|
*/
|
||
|
(void)time_stamp();
|
||
|
} /* if (get_message()) */
|
||
|
|
||
|
/* Do the periodic functions if it's time.
|
||
|
*/
|
||
|
if (time_to_call_heart_beat)
|
||
|
{
|
||
|
current_time = get_current_time();
|
||
|
|
||
|
current_object = NULL;
|
||
|
|
||
|
/* Start the next alarm */
|
||
|
comm_time_to_call_heart_beat = MY_FALSE;
|
||
|
time_to_call_heart_beat = MY_FALSE;
|
||
|
#if 1
|
||
|
/* skipping this means stopping the heartbeat..
|
||
|
* the driver will keep on working, but all
|
||
|
* callouts etc will be delayed...
|
||
|
*/
|
||
|
alarm(alarm_time);
|
||
|
#endif
|
||
|
|
||
|
/* Do the timed events */
|
||
|
if (!synch_heart_beats
|
||
|
|| time_of_last_hb + heart_beat_interval <= current_time)
|
||
|
{
|
||
|
do_state_check(2, "before heartbeat");
|
||
|
call_heart_beat();
|
||
|
time_of_last_hb = current_time;
|
||
|
}
|
||
|
|
||
|
do_state_check(2, "after heartbeat");
|
||
|
call_out();
|
||
|
do_state_check(2, "after call_out");
|
||
|
|
||
|
/* Reset/cleanup/swap objects.
|
||
|
*/
|
||
|
remove_destructed_objects(MY_FALSE);
|
||
|
process_objects();
|
||
|
do_state_check(2, "after swap/cleanup/reset");
|
||
|
|
||
|
/* Other periodic processing */
|
||
|
command_giver = NULL;
|
||
|
trace_level = 0;
|
||
|
wiz_decay();
|
||
|
comm_cleanup_interactives();
|
||
|
|
||
|
mem_consolidate(MY_FALSE);
|
||
|
}
|
||
|
|
||
|
} /* end of main loop */
|
||
|
|
||
|
/* NOTREACHED */
|
||
|
} /* backend() */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
|
||
|
/*
|
||
|
* catch alarm()
|
||
|
*
|
||
|
* Set flag for comms code and heart_beat to catch.
|
||
|
* comms code then sets time_to_call_heart_beat for the backend when
|
||
|
* it has completed the current round of player commands.
|
||
|
*/
|
||
|
|
||
|
#ifdef ALARM_HANDLER
|
||
|
|
||
|
ALARM_HANDLER(catch_alarm, alarm_called = MY_TRUE; comm_time_to_call_heart_beat = MY_TRUE; total_alarms++; )
|
||
|
|
||
|
#else
|
||
|
|
||
|
RETSIGTYPE
|
||
|
catch_alarm (int dummy UNUSED)
|
||
|
{
|
||
|
#ifdef __MWERKS__
|
||
|
# pragma unused(dummy)
|
||
|
#endif
|
||
|
(void)signal(SIGALRM, (RETSIGTYPE(*)(int))catch_alarm);
|
||
|
alarm_called = MY_TRUE;
|
||
|
comm_time_to_call_heart_beat = MY_TRUE;
|
||
|
total_alarms++;
|
||
|
#ifndef RETSIGTYPE_VOID
|
||
|
return 0;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
void check_alarm (void)
|
||
|
|
||
|
/* Check the time since the last recorded call to the alarm handler.
|
||
|
* If it is longer than a limit, assume that the alarm died and restart it.
|
||
|
*
|
||
|
* This function is necessary especially for Cygwin on Windows, where it
|
||
|
* is not unusual that the driver process receives so few cycles that it
|
||
|
* loses its alarm.
|
||
|
* TODO: It should be possible to get rid of alarms altogether if all
|
||
|
* TODO:: timebound methods check the time since the last checkpoint.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
static mp_int last_alarm_time = 0;
|
||
|
mp_int curtime = get_current_time();
|
||
|
|
||
|
if (t_flag) /* Timing turned off? */
|
||
|
return;
|
||
|
|
||
|
if (!last_alarm_time) /* initialize it */
|
||
|
last_alarm_time = curtime;
|
||
|
|
||
|
if (alarm_called)
|
||
|
{
|
||
|
/* We got an alarm - restart the timer */
|
||
|
last_alarm_time = curtime;
|
||
|
}
|
||
|
else if (curtime - last_alarm_time > 15)
|
||
|
{
|
||
|
debug_message("%s Last alarm was %ld seconds ago - restarting it.\n"
|
||
|
, time_stamp(), curtime - last_alarm_time);
|
||
|
|
||
|
alarm(0); /* stop alarm in case it is still alive, but just slow */
|
||
|
comm_time_to_call_heart_beat = MY_TRUE;
|
||
|
time_to_call_heart_beat = MY_TRUE;
|
||
|
(void)signal(SIGALRM, (RETSIGTYPE(*)(int))catch_alarm);
|
||
|
alarm(alarm_time);
|
||
|
|
||
|
last_alarm_time = curtime; /* Since we just restarted it */
|
||
|
}
|
||
|
|
||
|
alarm_called = MY_FALSE;
|
||
|
} /* check_alarm() */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
static void
|
||
|
process_objects (void)
|
||
|
|
||
|
/* This function performs almost all of the less important tasks in the
|
||
|
* mud: resetting, cleaning and swapping objects.
|
||
|
*
|
||
|
* It is called from the backend loop in every new cycle before
|
||
|
* player commands are retrieved, but after heartbeats and callouts
|
||
|
* have been evaluated. It tries to process as many objects as possible
|
||
|
* before the current timeslot runs out (as registered by comm_time_to-
|
||
|
* _call_heart_beat), but will do at least one cleanup/swap and reset.
|
||
|
*
|
||
|
* Objects are moved to the end of the list before they are processed,
|
||
|
* so that a the next object to handle is always the first object
|
||
|
* in the list. Even arbitrary object destruction doesn't change this.
|
||
|
*
|
||
|
* The functions in detail:
|
||
|
*
|
||
|
* - An object will be reset if it is found in a state of not being reset
|
||
|
* and the delay to the next reset has passed.
|
||
|
*
|
||
|
* - An object's clean_up() lfun will be called if the object has not
|
||
|
* been used for quite some time, and if it was not reset in this
|
||
|
* very round.
|
||
|
*
|
||
|
* - If the object hasn't been data-cleaned for a sufficient time, it
|
||
|
* will be.
|
||
|
*
|
||
|
* - An object's program and/or its variables will be swapped if it exists
|
||
|
* for at least time_to_swap(_variables) seconds since the last reference.
|
||
|
* Since swapping of variables is costly, care is taken that variables
|
||
|
* are not swapped out right before the next reset which in that case
|
||
|
* would cause a swap in/swap out-yoyo. Instead, such variable swapping
|
||
|
* is delayed until after the reset occured.
|
||
|
*
|
||
|
* To disable swapping, set the swapping times (either in config.h or per
|
||
|
* commandline option) to a value <= 0.
|
||
|
*
|
||
|
* The function maintains its own error recovery info so that errors
|
||
|
* in reset() or clean_up() won't mess up the handling.
|
||
|
*
|
||
|
* TODO: The objlist should be split into resets and cleanup/swaps, which
|
||
|
* TODO:: are then sorted by due times. This would make this function
|
||
|
* TODO:: much more efficient.
|
||
|
* TODO: It might be a good idea to distinguish between the time_of_ref
|
||
|
* TODO:: (when the object was last used/called) and the time_of_swap,
|
||
|
* TODO:: when it was last swapped in or out. Then, maybe not.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
static Bool did_reset;
|
||
|
static Bool did_swap;
|
||
|
/* True if one reset call or cleanup/swap was performed.
|
||
|
* static so that errors won't clobber it.
|
||
|
*
|
||
|
* The flags are used to make sure that at least one object is successfully
|
||
|
* processed per call, even if there is no time left to begin with.
|
||
|
*/
|
||
|
|
||
|
object_t *obj; /* Current object worked on */
|
||
|
long limit_data_clean; /* Max number of objects to dataclean */
|
||
|
|
||
|
struct error_recovery_info error_recovery_info;
|
||
|
/* Local error recovery info */
|
||
|
|
||
|
/* Housekeeping */
|
||
|
|
||
|
num_last_processed = 0;
|
||
|
num_last_data_cleaned = 0;
|
||
|
did_reset = MY_FALSE;
|
||
|
did_swap = MY_FALSE;
|
||
|
|
||
|
error_recovery_info.rt.last = rt_context;
|
||
|
error_recovery_info.rt.type = ERROR_RECOVERY_BACKEND;
|
||
|
rt_context = (rt_context_t *)&error_recovery_info;
|
||
|
|
||
|
if (setjmp(error_recovery_info.con.text))
|
||
|
{
|
||
|
mark_end_evaluation();
|
||
|
clear_state();
|
||
|
debug_message("%s Error in process_objects().\n", time_stamp());
|
||
|
}
|
||
|
|
||
|
/* Don't attempt to cleanup the fixed driver structures if we're
|
||
|
* already out of time. But do do it before the regular loop
|
||
|
* to ensure that they get cleaned up at least occasionally.
|
||
|
*/
|
||
|
if (!comm_time_to_call_heart_beat)
|
||
|
cleanup_driver_structures();
|
||
|
|
||
|
/* Determine how many objects to data-clean at max in this cycle, since
|
||
|
* sometimes hundreds of objects can become eligible at a time (despite
|
||
|
* the random factor in the timing). The number is determined so that all
|
||
|
* objects are cleaned within half of the configured cleanup period,
|
||
|
* but at least the number of recently destructed objects in this cycle.
|
||
|
*/
|
||
|
limit_data_clean = 2 * num_listed_objs / time_to_data_cleanup;
|
||
|
if (limit_data_clean < 2)
|
||
|
limit_data_clean = 2;
|
||
|
if (limit_data_clean < num_newly_destructed)
|
||
|
limit_data_clean = num_newly_destructed;
|
||
|
|
||
|
/* The processing loop, runs until either time or objects
|
||
|
* run short.
|
||
|
*
|
||
|
* To be safe against errors and destruction of objects,
|
||
|
* the object processed is moved immediately to the end
|
||
|
* of the object list, so that obj_list always points to
|
||
|
* the next object to process.
|
||
|
*
|
||
|
* The loop ends (apart from timeouts) when num_last_processed
|
||
|
* exceeds num_listed_objs.
|
||
|
*/
|
||
|
while (NULL != (obj = obj_list)
|
||
|
&& (!did_reset || !did_swap || !comm_time_to_call_heart_beat)
|
||
|
&& num_last_processed < num_listed_objs
|
||
|
)
|
||
|
{
|
||
|
mp_int time_since_ref; /* Time since last reference */
|
||
|
#ifdef USE_SWAP
|
||
|
mp_int min_time_to_swap; /* Variable swap exclusion time before reset */
|
||
|
#endif
|
||
|
Bool bResetCalled; /* TRUE: reset() called */
|
||
|
|
||
|
clear_state();
|
||
|
|
||
|
num_last_processed++;
|
||
|
|
||
|
/* Move obj to the end of the list */
|
||
|
if (obj != obj_list_end)
|
||
|
{
|
||
|
obj_list = obj->next_all;
|
||
|
obj_list->prev_all = NULL;
|
||
|
obj->next_all = NULL;
|
||
|
obj->prev_all = obj_list_end;
|
||
|
obj_list_end->next_all = obj;
|
||
|
obj_list_end = obj;
|
||
|
}
|
||
|
|
||
|
/* Set Variables */
|
||
|
time_since_ref = current_time - obj->time_of_ref;
|
||
|
bResetCalled = MY_FALSE;
|
||
|
|
||
|
#ifdef USE_SWAP
|
||
|
/* Variables won't be swapped if a reset is due shortly.
|
||
|
* "shortly" means half the var swap interval, but at max 5 minutes.
|
||
|
*/
|
||
|
min_time_to_swap = 5 * 60;
|
||
|
if (time_to_swap_variables / 2 < min_time_to_swap)
|
||
|
min_time_to_swap = time_to_swap_variables/2;
|
||
|
#endif
|
||
|
|
||
|
/* ------ Reset ------ */
|
||
|
|
||
|
/* Check if a reset() is due. Objects which have not been touched
|
||
|
* since the last reset just get a new due time set.
|
||
|
* It is tempting to skip the reset handling for objects which
|
||
|
* are swapped out, but then swapper would have to call reset_object()
|
||
|
* for due objects on swap-in (just setting a new due-time is not
|
||
|
* sufficient).
|
||
|
* TODO: Do exactly that?
|
||
|
*/
|
||
|
|
||
|
if (time_to_reset > 0
|
||
|
&& obj->time_reset
|
||
|
&& obj->time_reset < current_time)
|
||
|
{
|
||
|
if (obj->flags & O_RESET_STATE)
|
||
|
{
|
||
|
#ifdef DEBUG
|
||
|
if (d_flag)
|
||
|
fprintf(stderr, "%s RESET (virtual) %s\n", time_stamp(), get_txt(obj->name));
|
||
|
#endif
|
||
|
obj->time_reset = current_time+time_to_reset/2
|
||
|
+(mp_int)random_number((uint32)time_to_reset/2);
|
||
|
}
|
||
|
else if (!did_reset || !comm_time_to_call_heart_beat)
|
||
|
{
|
||
|
#ifdef DEBUG
|
||
|
if (d_flag)
|
||
|
fprintf(stderr, "%s RESET %s\n", time_stamp(), get_txt(obj->name));
|
||
|
#endif
|
||
|
mark_start_evaluation();
|
||
|
#ifdef USE_SWAP
|
||
|
if (obj->flags & O_SWAPPED
|
||
|
&& load_ob_from_swap(obj) < 0)
|
||
|
continue;
|
||
|
#endif
|
||
|
bResetCalled = MY_TRUE;
|
||
|
did_reset = MY_TRUE;
|
||
|
RESET_LIMITS;
|
||
|
CLEAR_EVAL_COST;
|
||
|
command_giver = 0;
|
||
|
previous_ob = NULL;
|
||
|
trace_level = 0;
|
||
|
reset_object(obj, H_RESET);
|
||
|
mark_end_evaluation();
|
||
|
if (obj->flags & O_DESTRUCTED)
|
||
|
continue;
|
||
|
|
||
|
#ifdef USE_SWAP
|
||
|
if (time_to_swap > 0 || time_to_swap_variables > 0)
|
||
|
{
|
||
|
/* Restore old time_of_ref. This might result in a quick
|
||
|
* swap-in/swap-out yoyo if this object was swapped out
|
||
|
* in the first place. To make this less costly, variables
|
||
|
* are not swapped out short before a reset (see below).
|
||
|
*/
|
||
|
obj->time_of_ref = current_time - time_since_ref;
|
||
|
}
|
||
|
#endif
|
||
|
} /* if (call reset or not) */
|
||
|
} /* if (needs reset?) */
|
||
|
|
||
|
|
||
|
/* ------ Clean Up ------ */
|
||
|
|
||
|
/* If enough time has passed, give the object a chance to self-
|
||
|
* destruct. The O_RESET_STATE is saved over the call to clean_up().
|
||
|
*
|
||
|
* Only call clean_up in objects that have defined such a function.
|
||
|
* Only if the clean_up returns a non-zero value, it will be called
|
||
|
* again.
|
||
|
*
|
||
|
* The cleanup is not called if the object was actively reset
|
||
|
* just before.
|
||
|
*/
|
||
|
if (time_to_cleanup > 0
|
||
|
&& obj->flags & O_WILL_CLEAN_UP
|
||
|
&& time_since_ref > time_to_cleanup
|
||
|
&& !bResetCalled
|
||
|
&& (!did_swap || !comm_time_to_call_heart_beat))
|
||
|
{
|
||
|
int save_reset_state = obj->flags & O_RESET_STATE;
|
||
|
svalue_t *svp;
|
||
|
|
||
|
#ifdef DEBUG
|
||
|
if (d_flag)
|
||
|
fprintf(stderr, "%s CLEANUP %s\n", time_stamp(), get_txt(obj->name));
|
||
|
#endif
|
||
|
|
||
|
did_swap = MY_TRUE;
|
||
|
|
||
|
/* Remove all pending destructed objects, to get a true refcount.
|
||
|
* But make sure that we don't clobber anything else while
|
||
|
* doing so.
|
||
|
*/
|
||
|
cleanup_stuff();
|
||
|
remove_destructed_objects(MY_FALSE);
|
||
|
|
||
|
/* Supply a flag to the object that says if this program
|
||
|
* is inherited by other objects. Cloned objects might as well
|
||
|
* believe they are not inherited. Swapped objects will not
|
||
|
* have a ref count > 1 (and will have an invalid ob->prog
|
||
|
* pointer). If the object is a blueprint, the extra reference
|
||
|
* from the program will not be counted.
|
||
|
*/
|
||
|
if (obj->flags & (O_CLONE|O_REPLACED))
|
||
|
push_number(inter_sp, 0);
|
||
|
#ifdef USE_SWAP
|
||
|
else if (O_PROG_SWAPPED(obj))
|
||
|
push_number(inter_sp, 1);
|
||
|
#endif
|
||
|
else if (obj->prog->blueprint == obj)
|
||
|
push_number(inter_sp, obj->prog->ref - 1);
|
||
|
else
|
||
|
push_number(inter_sp, obj->prog->ref);
|
||
|
|
||
|
RESET_LIMITS;
|
||
|
CLEAR_EVAL_COST;
|
||
|
command_giver = NULL;
|
||
|
previous_ob = NULL;
|
||
|
trace_level = 0;
|
||
|
if (driver_hook[H_CLEAN_UP].type == T_CLOSURE)
|
||
|
{
|
||
|
lambda_t *l;
|
||
|
|
||
|
mark_start_evaluation();
|
||
|
l = driver_hook[H_CLEAN_UP].u.lambda;
|
||
|
if (driver_hook[H_CLEAN_UP].x.closure_type == CLOSURE_LAMBDA)
|
||
|
{
|
||
|
free_object(l->ob, "clean_up");
|
||
|
l->ob = ref_object(obj, "clean_up");
|
||
|
}
|
||
|
push_ref_object(inter_sp, obj, "clean up");
|
||
|
call_lambda(&driver_hook[H_CLEAN_UP], 2);
|
||
|
svp = inter_sp;
|
||
|
pop_stack();
|
||
|
mark_end_evaluation();
|
||
|
}
|
||
|
else if (driver_hook[H_CLEAN_UP].type == T_STRING)
|
||
|
{
|
||
|
mark_start_evaluation();
|
||
|
svp = apply(driver_hook[H_CLEAN_UP].u.str, obj, 1);
|
||
|
mark_end_evaluation();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pop_stack();
|
||
|
goto no_clean_up;
|
||
|
}
|
||
|
if (obj->flags & O_DESTRUCTED)
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (!svp
|
||
|
|| (svp->type == T_NUMBER && svp->u.number == 0)
|
||
|
)
|
||
|
obj->flags &= ~O_WILL_CLEAN_UP;
|
||
|
obj->flags |= save_reset_state;
|
||
|
|
||
|
no_clean_up:
|
||
|
obj->time_of_ref = current_time;
|
||
|
/* in case the hook didn't update it */
|
||
|
}
|
||
|
|
||
|
|
||
|
/* ------ Data Cleanup ------ */
|
||
|
|
||
|
/* Objects are processed at intervals determined by their
|
||
|
* time to clean up (or DEFAULT_CLEANUP_TIME if the stored time
|
||
|
* is 0).
|
||
|
*/
|
||
|
if ( (num_last_data_cleaned == 0 || !comm_time_to_call_heart_beat)
|
||
|
&& (unsigned long)obj->time_cleanup < (unsigned long)current_time
|
||
|
&& num_last_data_cleaned < limit_data_clean
|
||
|
)
|
||
|
{
|
||
|
#ifdef DEBUG
|
||
|
if (d_flag)
|
||
|
fprintf(stderr, "%s DATA CLEANUP %s\n"
|
||
|
, time_stamp(), get_txt(obj->name));
|
||
|
#endif
|
||
|
|
||
|
cleanup_object(obj);
|
||
|
num_last_data_cleaned++;
|
||
|
}
|
||
|
|
||
|
#ifdef USE_SWAP
|
||
|
/* ------ Swapping ------ */
|
||
|
|
||
|
/* At last, there is a possibility that the object can be swapped
|
||
|
* out.
|
||
|
*
|
||
|
* Variables are swapped after time_to_swap_variables has elapsed
|
||
|
* since the last ref, and if the object is either still reset or
|
||
|
* the next reset is at least min(5 minutes, time_to_swap_variables/2)
|
||
|
* in the future. When a reset is due, this second condition delays the
|
||
|
* costly variable swapping until after the reset.
|
||
|
*
|
||
|
* Programs are swapped after time_to_swap has elapsed, and if
|
||
|
* they have only one reference, ie are not cloned or inherited.
|
||
|
* Since program swapping is relatively cheap, no care is
|
||
|
* taken of resets.
|
||
|
*/
|
||
|
|
||
|
if ( (time_to_swap > 0 || time_to_swap_variables > 0)
|
||
|
&& !(obj->flags & O_HEART_BEAT)
|
||
|
&& (!did_swap || !comm_time_to_call_heart_beat))
|
||
|
{
|
||
|
/* Swap the variables, if possible */
|
||
|
if (!O_VAR_SWAPPED(obj)
|
||
|
&& time_since_ref >= time_to_swap_variables
|
||
|
&& time_to_swap_variables > 0
|
||
|
&& obj->variables
|
||
|
&& ( obj->flags & O_RESET_STATE
|
||
|
|| !obj->time_reset
|
||
|
|| (obj->time_reset - current_time > min_time_to_swap)))
|
||
|
{
|
||
|
#ifdef DEBUG
|
||
|
if (d_flag)
|
||
|
fprintf(stderr, "%s swap vars of %s\n", time_stamp(), get_txt(obj->name));
|
||
|
#endif
|
||
|
swap_variables(obj);
|
||
|
if (O_VAR_SWAPPED(obj))
|
||
|
did_swap = MY_TRUE;
|
||
|
}
|
||
|
|
||
|
/* Swap the program, if possible */
|
||
|
if (!O_PROG_SWAPPED(obj)
|
||
|
&& obj->prog->ref == 1
|
||
|
&& time_since_ref >= time_to_swap
|
||
|
&& time_to_swap > 0)
|
||
|
{
|
||
|
#ifdef DEBUG
|
||
|
if (d_flag)
|
||
|
fprintf(stderr, "%s swap %s\n", time_stamp(), get_txt(obj->name));
|
||
|
#endif
|
||
|
swap_program(obj);
|
||
|
if (O_PROG_SWAPPED(obj))
|
||
|
did_swap = MY_TRUE;
|
||
|
}
|
||
|
} /* if (obj can be swapped) */
|
||
|
#endif
|
||
|
|
||
|
} /* End of loop */
|
||
|
|
||
|
/* Update the processing averages
|
||
|
*/
|
||
|
update_statistic(&stat_last_processed, num_last_processed);
|
||
|
update_statistic(&stat_last_data_cleaned, num_last_data_cleaned);
|
||
|
update_statistic(&stat_in_list, num_listed_objs);
|
||
|
|
||
|
/* Restore the error recovery context */
|
||
|
rt_context = error_recovery_info.rt.last;
|
||
|
} /* process_objects() */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
void
|
||
|
preload_objects (int eflag)
|
||
|
|
||
|
/* Perform some initialisation of the mudlib before the sockets are opened
|
||
|
* for users. Traditionally, back in 2.4.5 times, this was used to preload
|
||
|
* the wizard castles, but you can do everything else here.
|
||
|
*
|
||
|
* <eflag> is the number of times the '-e' ('--no-preload') option was
|
||
|
* given on the command line. Traditionally, a non-zero value disabled
|
||
|
* any preloading.
|
||
|
*
|
||
|
* The preloading is a two-step process. First, the lfun epilog() in the
|
||
|
* master object is called with the value of <eflag> as argument. The
|
||
|
* result is expected to be an array of strings (filenames). These strings
|
||
|
* (or at least those array elements which are strings) are then fed as
|
||
|
* argument to the master object lfun preload().
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
vector_t *prefiles;
|
||
|
svalue_t *ret;
|
||
|
static mp_int ix0;
|
||
|
static size_t num_prefiles;
|
||
|
mp_int ix;
|
||
|
|
||
|
/* Call master->epilog(<eflag>)
|
||
|
*/
|
||
|
push_number(inter_sp, eflag);
|
||
|
ret = callback_master(STR_EPILOG, 1);
|
||
|
|
||
|
if ((ret == 0) || (ret->type != T_POINTER))
|
||
|
return;
|
||
|
else
|
||
|
prefiles = ret->u.vec;
|
||
|
|
||
|
if ((prefiles == 0) || ((num_prefiles = VEC_SIZE(prefiles)) < 1))
|
||
|
return;
|
||
|
|
||
|
ref_array(prefiles);
|
||
|
/* Without this, the next apply call would free the array prefiles.
|
||
|
*/
|
||
|
|
||
|
ix0 = -1;
|
||
|
|
||
|
/* In case of errors, return here and simply continue with
|
||
|
* the loop.
|
||
|
*/
|
||
|
toplevel_context.rt.type = ERROR_RECOVERY_BACKEND;
|
||
|
if (setjmp(toplevel_context.con.text)) {
|
||
|
clear_state();
|
||
|
add_message("Anomaly in the fabric of world space.\n");
|
||
|
}
|
||
|
|
||
|
/* Loop through the prefiles array, calling master->preload()
|
||
|
* for every string in it.
|
||
|
*/
|
||
|
while ((size_t)(ix = ++ix0) < num_prefiles) {
|
||
|
if (prefiles->item[ix].type != T_STRING)
|
||
|
continue;
|
||
|
|
||
|
RESET_LIMITS;
|
||
|
CLEAR_EVAL_COST;
|
||
|
push_ref_string(inter_sp, prefiles->item[ix].u.str);
|
||
|
(void)apply_master(STR_PRELOAD, 1);
|
||
|
|
||
|
}
|
||
|
|
||
|
free_array(prefiles);
|
||
|
toplevel_context.rt.type = ERROR_RECOVERY_NONE;
|
||
|
} /* preload_objects() */
|
||
|
|
||
|
/*=========================================================================*/
|
||
|
|
||
|
/* EFUNS */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
svalue_t *
|
||
|
v_garbage_collection (svalue_t *sp, int num_arg)
|
||
|
|
||
|
/* EFUN garbage_collection()
|
||
|
*
|
||
|
* void garbage_collection ()
|
||
|
* void garbage_collection (string filename)
|
||
|
* void garbage_collection (string filename, int flag)
|
||
|
*
|
||
|
* Tell the parser to initiate a garbage collection after the
|
||
|
* current execution ended.
|
||
|
*
|
||
|
* With the <filename> argument a log file for the GC output
|
||
|
* different from the default log file can be specified.
|
||
|
*
|
||
|
* If <flag> is not given or 0, the output from the next
|
||
|
* and only the next GC will go into the log file.
|
||
|
* If <flag> is 1, the <filename> will be used as the new
|
||
|
* default log file for all following GCs.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
if (num_arg > 0)
|
||
|
{
|
||
|
#ifdef GC_SUPPORT
|
||
|
Bool change_default = MY_FALSE;
|
||
|
svalue_t * argp = sp - num_arg + 1;
|
||
|
int fd;
|
||
|
string_t * path;
|
||
|
|
||
|
if (num_arg > 1)
|
||
|
{
|
||
|
change_default = argp[1].u.number != 0;
|
||
|
}
|
||
|
|
||
|
restore_default_gc_log();
|
||
|
|
||
|
path = check_valid_path( argp[0].u.str, current_object
|
||
|
, STR_GARBAGE_COLLECTION, MY_TRUE);
|
||
|
if (path == NULL)
|
||
|
{
|
||
|
if (change_default)
|
||
|
{
|
||
|
errorf("Illegal use of garbage_collection(): "
|
||
|
"No privilege to use file '%s' as default log.\n"
|
||
|
, get_txt(argp[0].u.str));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
errorf("Illegal use of garbage_collection(): "
|
||
|
"No privilege to use file '%s' as default log.\n"
|
||
|
, get_txt(argp[0].u.str));
|
||
|
}
|
||
|
/* NOTREACHED */
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
if (change_default)
|
||
|
fd = ixopen3(get_txt(path), O_CREAT|O_TRUNC|O_WRONLY, 0640);
|
||
|
else
|
||
|
fd = ixopen3(get_txt(path), O_CREAT|O_APPEND|O_WRONLY, 0640);
|
||
|
|
||
|
if (fd < 0)
|
||
|
{
|
||
|
/* Store the <path> on the stack so the error handling
|
||
|
* can free it.
|
||
|
*/
|
||
|
inter_sp = ++sp; put_string(sp, path);
|
||
|
errorf("Can't open GC log file '%s': errno %d\n"
|
||
|
, get_txt(path), errno);
|
||
|
/* NOTREACHED */
|
||
|
return sp;
|
||
|
}
|
||
|
|
||
|
if (change_default)
|
||
|
new_default_gc_log(fd);
|
||
|
else
|
||
|
gcollect_outfd = fd;
|
||
|
|
||
|
/* Clean up */
|
||
|
free_mstring(path);
|
||
|
#endif
|
||
|
sp = pop_n_elems(num_arg, sp);
|
||
|
}
|
||
|
|
||
|
extra_jobs_to_do = MY_TRUE;
|
||
|
gc_request = gcEfun;
|
||
|
|
||
|
return sp;
|
||
|
} /* v_garbage_collection() */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
svalue_t *
|
||
|
f_query_load_average (svalue_t *sp)
|
||
|
|
||
|
/* EFUN query_load_average()
|
||
|
*
|
||
|
* Return a string with the current load_av and compile_av.
|
||
|
* The string returned points to a local static buffer.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
char buff[100];
|
||
|
|
||
|
#if defined(__MWERKS__) && !defined(WARN_ALL)
|
||
|
# pragma warn_largeargs off
|
||
|
#endif
|
||
|
sprintf(buff, "%.2f cmds/s, %.2f comp lines/s"
|
||
|
, stat_load.weighted_avg
|
||
|
, stat_compile.weighted_avg
|
||
|
);
|
||
|
#if defined(__MWERKS__)
|
||
|
# pragma warn_largeargs reset
|
||
|
#endif
|
||
|
push_c_string(sp, buff);
|
||
|
return sp;
|
||
|
} /* f_query_load_average() */
|
||
|
|
||
|
/*-------------------------------------------------------------------------*/
|
||
|
svalue_t *
|
||
|
f_debug_message (svalue_t *sp)
|
||
|
|
||
|
/* EFUN debug_message()
|
||
|
*
|
||
|
* debug_message(string text)
|
||
|
* debug_message(string text, int flags)
|
||
|
*
|
||
|
* Print the <text> to stdout, stderr, and/or the <host>.debug.log file.
|
||
|
* The <text> may contain embedded \0 characters, which are ignored.
|
||
|
*
|
||
|
* The parameter <flags> is a combination of bitflags determining the
|
||
|
* target and the mode of writing.
|
||
|
*
|
||
|
* The target flags are: DMSG_STDOUT, DMSG_STDERR and DMSG_LOGFILE.
|
||
|
* If the flag DMSG_STAMP is given, the message is prepended with the
|
||
|
* current date and time in the format 'YYYY.MM.DD HH:MM:SS '.
|
||
|
*
|
||
|
* If <flags> is given as 0, left out, or contains no target
|
||
|
* definition, debug_message() will print to stdout and to the logfile.
|
||
|
*/
|
||
|
|
||
|
{
|
||
|
mp_int slen;
|
||
|
char *pTxt;
|
||
|
|
||
|
if ((sp->u.number & ~(DMSG_STDOUT|DMSG_STDERR|DMSG_LOGFILE|DMSG_STAMP)) != 0)
|
||
|
errorf("Argument 2 to debug_message() out of range: %ld, expected 0..15\n"
|
||
|
, (long)sp->u.number);
|
||
|
|
||
|
slen = (mp_int)mstrsize((sp-1)->u.str);
|
||
|
pTxt = get_txt((sp-1)->u.str);
|
||
|
|
||
|
if (sp->u.number & DMSG_STAMP)
|
||
|
{
|
||
|
if (sp->u.number & DMSG_STDERR)
|
||
|
fprintf(stderr, "%s ", time_stamp());
|
||
|
#ifdef USE_DEBUG_LOG
|
||
|
if (!(sp->u.number & DMSG_TARGET) || (sp->u.number & DMSG_STDOUT))
|
||
|
printf("%s ", time_stamp());
|
||
|
if (sp->u.number & DMSG_LOGFILE)
|
||
|
debug_message("%s ", time_stamp());
|
||
|
#else
|
||
|
if (!(sp->u.number & DMSG_TARGET)
|
||
|
|| (sp->u.number & DMSG_STDOUT)
|
||
|
|| (sp->u.number & DMSG_LOGFILE))
|
||
|
printf("%s ", time_stamp());
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
while (slen > 0)
|
||
|
{
|
||
|
while (slen > 0 && *pTxt == '\0')
|
||
|
{
|
||
|
pTxt++;
|
||
|
slen--;
|
||
|
}
|
||
|
if (slen > 0)
|
||
|
{
|
||
|
if (sp->u.number & DMSG_STDERR)
|
||
|
fputs(pTxt, stderr);
|
||
|
#ifdef USE_DEBUG_LOG
|
||
|
if (!(sp->u.number & DMSG_TARGET) || (sp->u.number & DMSG_STDOUT))
|
||
|
fputs(pTxt, stdout);
|
||
|
if (sp->u.number & DMSG_LOGFILE)
|
||
|
debug_message("%s", pTxt);
|
||
|
#else
|
||
|
/* when debug log is turned off, there is no
|
||
|
difference between STDOUT and LOGFILE
|
||
|
so we make sure we don't output things twice
|
||
|
--lynX */
|
||
|
if (!(sp->u.number & DMSG_TARGET)
|
||
|
|| (sp->u.number & DMSG_STDOUT)
|
||
|
|| (sp->u.number & DMSG_LOGFILE))
|
||
|
fputs(pTxt, stdout);
|
||
|
#endif
|
||
|
|
||
|
slen -= strlen(pTxt);
|
||
|
pTxt += strlen(pTxt);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
free_svalue(sp);
|
||
|
free_svalue(sp-1);
|
||
|
return sp - 2;
|
||
|
} /* f_debug_message() */
|
||
|
|
||
|
/***************************************************************************/
|
||
|
|