/* 92/04/18 - cleaned up stylistically by Sulam@TMI */
#include "global.h"
#include <signal.h>
#include <setjmp.h>
#include <ctype.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/times.h>
#include <math.h>
#include "interpret.h"
#include "object.h"
#include "exec.h"
#include "comm.h"
#include "debug.h"
#include "dbase_efuns.h"
#include "call_out.h"
#include "backend.h"
#include "dynamic_buffer.h"
#include "simulate.h"
#include "main.h"
#ifdef LACIP
#include "lacip/op.h"
#endif
jmp_buf error_recovery_context;
int error_recovery_context_exists = 0;
/*
* The 'current_time' is updated at every heart beat.
*/
int current_time;
int heart_beat_flag = 0;
static void cycle_hb_list PROT((void));
extern struct object *command_giver, *obj_list_destruct;
extern struct object *master_ob;
extern int num_user, d_flag;
extern INLINE void make_selectmasks();
extern int process_user_command();
struct object *current_heart_beat;
void call_heart_beat();
void init_user_conn(), init_sockets(),
destruct2 PROT((struct object *));
RETSIGTYPE sigalrm_handler();
extern int t_flag;
/*
* There are global variables that must be zeroed before any execution.
* In case of errors, there will be a LONGJMP(), and the variables will
* have to be cleared explicitely. They are normally maintained by the
* code that use them.
*
* This routine must only be called from top level, not from inside
* stack machine execution (as stack will be cleared).
*/
void clear_state()
{
current_object = 0;
command_giver = 0;
current_prog = 0;
error_recovery_context_exists = 1;
reset_machine(0); /* Pop down the stack. */
}
/* All destructed objects are moved into a sperate linked list,
* and deallocated after program execution. */
INLINE void remove_destructed_objects()
{
struct object *ob, *next;
for(ob=obj_list_destruct; ob; ob = next)
{
next = ob->next_all;
destruct2(ob);
}
obj_list_destruct = 0;
}
/*
* This is the backend. We will stay here for ever (almost).
*/
int eval_cost,max_eval_cost;
extern int game_is_being_shut_down;
RETSIGTYPE sig_hup(int arg)
{
game_is_being_shut_down = 1;
}
void check_signals()
{
extern unsigned long signals;
unsigned long tmp;
if(signals)
{
struct svalue *ret;
tmp=signals;
signals=0;
push_number(tmp);
APPLY_MASTER_OB(ret=,"signal",1);
if(IS_ZERO(ret))
{
if(tmp &((1<<SIGFPE) |
(1<<SIGQUIT) |
(1<<SIGBUS) |
(1<<SIGSEGV)))
{
do_close_database();
fflush(stdout);
abort();
}
if(tmp & ((1<<SIGTERM) |
(1<<SIGINT)))
{
do_close_database();
fflush(stdout);
exit(0);
}
if(tmp & (1<<SIGUSR1))
APPLY_MASTER_OB((void),"shut",0);
if(tmp & (1<<SIGUSR2))
startshutdowngame();
}
}
}
#ifndef HAVE_UALARM
unsigned ualarm(register unsigned usecs,register unsigned reload);
#endif
extern void stralloc_gc();
void backend()
{
#ifdef LACIP
extern int refused;
#endif
extern fd_set readmask, writemask;
extern int slow_shut_down_to_do;
struct timeval timeout;
int nb;
signal(SIGHUP, sig_hup);
signal(SIGALRM, sigalrm_handler);
ualarm(HEARTBEAT_INTERVAL,0);
setjmp(error_recovery_context);
error_recovery_context_exists = 1;
while(1){
/*
* The call of clear_state() should not really have to be done
* once every loop. However, there seem to be holes where the
* state is not consistent. If these holes are removed,
* then the call of clear_state() can be moved to just before the
* while() - statement. *sigh* /Lars
*/
clear_state();
eval_cost = 0;
remove_destructed_objects();
/*
* shut down mud if game_is_being_shut_down is set.
*/
if(game_is_being_shut_down)
shutdowngame();
if(slow_shut_down_to_do)
{
APPLY_MASTER_OB((void),"shut", 0);
slow_shut_down_to_do = 0;
}
/*
* select
*/
#ifdef LACIP
if(refused) acceptmouse();
refused=0;
not_disp();
#endif
make_selectmasks();
if (heart_beat_flag) { /* use zero timeout if a heartbeat is pending. */
timeout.tv_sec = 0; /* this should avoid problems with longjmp's too */
timeout.tv_usec = 0;
} else {
/* not using infinite timeout so that we'll have insurance in the
unlikely event a heartbeat happens between now and the select().
Note that SIGALRMs (for heartbeats) do make select() drop through.
*/
timeout.tv_sec = 0;
timeout.tv_usec = 100000;
}
stralloc_gc(0);
nb = select(FD_SETSIZE,&readmask,&writemask,(fd_set *)0, &timeout);
#ifdef MALLOC_DEBUG
check_sfltable();
#endif
check_signals();
/*
* process I/O if necessary.
*/
if(nb > 0){
process_io();
}
#ifdef MALLOC_DEBUG
check_sfltable();
#endif
/*
* call heartbeat if appropriate.
*/
if(heart_beat_flag){
debug(512,("backend: HEARTBEAT\n"));
call_heart_beat();
}
do_sync_database();
#ifdef MALLOC_DEBUG
check_sfltable();
#endif
}
}
/*
* Despite the name, this routine takes care of several things.
* It will loop through all objects once every 10 minutes.
*
* If an object is found in a state of not having done reset, and the
* delay to next reset has passed, then reset() will be done.
*
* If the object has a existed more than the time limit given for swapping,
* then 'clean_up' will first be called in the object, after which it will
* be swapped out if it still exists.
*
* There are some problems if the object self-destructs in clean_up, so
* special care has to be taken of how the linked list is used.
*/
static void look_for_objects_to_swap()
{
static int next_time;
struct object * VOLATILE ob;
struct object * VOLATILE next_ob;
jmp_buf save_error_recovery_context;
int save_rec_exists;
struct object *save;
if(current_time < next_time)
return; /* Not time to look yet */
next_time = current_time + 15 * 60; /* Next time is in 15 minutes */
MEMCPY((char *) save_error_recovery_context,
(char *) error_recovery_context, sizeof error_recovery_context);
save_rec_exists = error_recovery_context_exists;
/* prevent error messages from objects going to whoever happens to
* be this_user() (usually this isn't the user of the object). */
save = command_giver;
command_giver = (struct object *)0;
/* Objects object can be destructed, which means that
* next object to investigate is saved in next_ob. If very unlucky,
* that object can be destructed too. In that case, the loop is simply
* restarted. */
ob=obj_list;
next_ob=ob->next_all;
if(setjmp(error_recovery_context))
{
extern void clear_state();
ob=next_ob;
clear_state();
debug_message("Error in look_for_objects_to_swap.\n");
}else{
error_recovery_context_exists=1;
while(ob)
{
next_ob=ob->next_all;
/* Should this object have reset(1) called ? */
if ((ob->next_reset < current_time) && !(ob->flags & O_RESET_STATE))
{
if(d_flag)
{
fprintf(stderr, "RESET %s\n", ob->prog->name);
}
reset_object(ob, 1);
}
if(TIME_TO_CLEAN_UP > 0)
{
/* Has enough time passed, to give the object a chance
* to self-destruct ? Save the O_RESET_STATE, which will be cleared.
*
* Only call clean_up in objects that has defined such a function.
*
* Only if the clean_up returns a non-zero value, will it be called
* again. */
if (current_time - ob->time_of_ref > TIME_TO_CLEAN_UP
&& (ob->flags & O_WILL_CLEAN_UP))
{
int save_reset_state = ob->flags & O_RESET_STATE;
struct svalue *svp;
if(d_flag)
fprintf(stderr, "clean up %s\n", ob->prog->name);
/* 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). */
svp = apply("clean_up", ob, 0,1);
if(ob->flags & O_DESTRUCTED)
continue;
if(!svp || (svp->type == T_NUMBER && svp->u.number == 0))
ob->flags &= ~O_WILL_CLEAN_UP;
ob->flags |= save_reset_state;
}
}
ob=next_ob;
}
}
/* restore command_giver to its previous value */
command_giver = save;
MEMCPY((char *) error_recovery_context,(char *) save_error_recovery_context,
sizeof error_recovery_context);
error_recovery_context_exists = save_rec_exists;
}
/* Call all heart_beat() functions in all objects. Also call the next reset,
* and the call out.
* We do heart beats by moving each object done to the end of the heart beat
* list before we call its function, and always using the item at the head
* of the list as our function to call. We keep calling heart beats until
* a timeout or we have done num_heart_objs calls. It is done this way so
* that objects can delete heart beating objects from the list from within
* their heart beat without truncating the current round of heart beats.
*
* Set command_giver to current_object if it is a living object. If the object
* is shadowed, check the shadowed object if living. There is no need to save
* the value of the command_giver, as the caller resets it to 0 anyway. */
struct object *hb_list = 0; /* head */
struct object *hb_tail = 0; /* for sane wrap around */
static int num_hb_objs = 0; /* so we know when to stop! */
static int num_hb_calls = 0; /* stats */
static float perc_hb_probes = 100.0; /* decaying avge of how many complete */
void call_heart_beat()
{
struct object *ob;
struct object *save_current_object = current_object;
struct object *save_command_giver = command_giver;
int num_done = 0;
heart_beat_flag = 0;
signal(SIGALRM, sigalrm_handler);
ualarm(HEARTBEAT_INTERVAL,0);
debug(256,("."));
current_time = get_current_time();
if(hb_list)
{
num_hb_calls++;
while(hb_list && !heart_beat_flag && (num_done < num_hb_objs))
{
if(!(hb_list->flags & O_HEART_BEAT) || hb_list->flags & O_DESTRUCTED)
{
ob=hb_list;
hb_list=ob->next_heart_beat;
ob->next_heart_beat=0;
continue;
}
num_done++;
cycle_hb_list();
ob = hb_tail; /* now at end */
/* move ob to end of list, do ob */
current_heart_beat = ob;
if(ob->flags & O_ENABLE_COMMANDS)
command_giver = ob;
else
command_giver = 0;
eval_cost = 0;
apply_lfun(LF_HEART_BEAT,ob,0,1);
}
if(num_hb_objs)
perc_hb_probes = 100 * (float) num_done / num_hb_objs;
else
perc_hb_probes = 100.0;
}
current_object = save_current_object;
current_heart_beat = 0;
look_for_objects_to_swap();
do_call_outs(); /* some things depend on this, even without users! */
command_giver = save_command_giver;
}
/* Take the first object off the heart beat list, place it at the end
*/
static void cycle_hb_list()
{
struct object *ob;
if(!hb_list)
fatal("Cycle heart beat list with empty list!");
if(hb_list == hb_tail)
return; /* 1 object on list */
ob = hb_list;
hb_list = hb_list->next_heart_beat;
hb_tail->next_heart_beat = ob;
hb_tail = ob;
ob->next_heart_beat = 0;
}
int set_heart_beat(struct object *ob,int to)
{
if(ob->flags & O_DESTRUCTED) return 0;
if((!!to) ^ !(ob->flags & O_HEART_BEAT)) return 0; /* no change */
if(to)
{
ob->flags |= O_HEART_BEAT;
if(!ob->next_heart_beat) /* hasn't been unlinked yet */
{
ob->next_heart_beat = hb_list;
hb_list = ob;
}
if(!hb_tail) hb_tail = ob;
num_hb_objs++;
}else{
/* let call_heart_beat unlink it */
ob->flags &= ~O_HEART_BEAT;
num_hb_objs--;
}
return 1;
}
char *heart_beat_status(int verbose)
{
char b[100];
char buf[100];
init_buf();
my_strcat("\nHeart beat information:\n");
my_strcat("-----------------------\n");
sprintf(b,",Number of objects with heart beat: %d, starts: %d\n",
num_hb_objs, num_hb_calls);
my_strcat(b);
sprintf(buf, "%.2f", perc_hb_probes);
sprintf(b,"Percentage of HB calls completed last time: %s\n", buf);
my_strcat(b);
return free_buf();
}
static double load_av = 0.0;
void update_load_av()
{
extern double consts[];
static int last_time;
int n;
double c;
static int acc = 0;
acc++;
if(current_time == last_time)
return;
n = current_time - last_time;
if(n < NUM_CONSTS)
c = consts[n];
else
c = exp(- n / 900.0);
load_av = c * load_av + acc * (1 - c) / n;
last_time = current_time;
acc = 0;
}
static double compile_av = 0.0;
void update_compile_av(lines)
int lines;
{
extern double consts[];
static int last_time;
int n;
double c;
static int acc = 0;
acc += lines;
if(current_time == last_time)
return;
n = current_time - last_time;
if(n < NUM_CONSTS)
c = consts[n];
else
c = exp(- n / 900.0);
compile_av = c * compile_av + acc * (1 - c) / n;
last_time = current_time;
acc = 0;
}
char *query_load_av()
{
static char buff[100];
sprintf(buff, "%.2f cmds/s, %.2f comp lines/s", load_av, compile_av);
return(buff);
}