/* 92/04/18 - cleaned up stylistically by Sulam@TMI */ #include "std.h" #include "lpc_incl.h" #include "backend.h" #include "comm.h" #include "replace_program.h" #include "socket_efuns.h" #include "call_out.h" #include "port.h" #include "master.h" #include "eval.h" #ifdef PACKAGE_ASYNC #include "packages/async.h" #endif #ifdef WIN32 #include void CDECL alarm_loop (void *); #endif error_context_t *current_error_context = 0; /* * The 'current_time' is updated in the call_out cycles */ long current_time; object_t *current_heart_beat; static void look_for_objects_to_swap (void); void call_heart_beat (void); #if 0 static void report_holes (void); #endif /* * 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; set_command_giver(0); current_interactive = 0; previous_ob = 0; current_prog = 0; caller_type = 0; reset_machine(0); /* Pop down the stack. */ } /* clear_state() */ #if 0 static void report_holes() { if (current_object && current_object->name) debug_message("current_object is /%s\n", current_object->name); if (command_giver && command_giver->name) debug_message("command_giver is /%s\n", command_giver->name); if (current_interactive && current_interactive->name) debug_message("current_interactive is /%s\n", current_interactive->name); if (previous_ob && previous_ob->name) debug_message("previous_ob is /%s\n", previous_ob->name); if (current_prog && current_prog->name) debug_message("current_prog is /%s\n", current_prog->name); if (caller_type) debug_message("caller_type is %s\n", caller_type); } #endif void logon (object_t * ob) { if(ob->flags & O_DESTRUCTED){ return; } /* current_object no longer set */ apply(APPLY_LOGON, ob, 0, ORIGIN_DRIVER); /* function not existing is no longer fatal */ } /* * This is the backend. We will stay here for ever (almost). */ extern int max_fd; void backend() { struct timeval timeout; int i, nb; volatile int first_call = 1; int there_is_a_port = 0; error_context_t econ; debug_message("Initializations complete.\n\n"); for (i = 0; i < 5; i++) { if (external_port[i].port) { debug_message("Accepting connections on port %d.\n", external_port[i].port); there_is_a_port = 1; } } if (!there_is_a_port) debug_message("No external ports specified.\n"); init_user_conn(); /* initialize user connection socket */ #ifdef SIGHUP signal(SIGHUP, startshutdownMudOS); #endif clear_state(); save_context(&econ); if (SETJMP(econ.context)) restore_context(&econ); clear_state(); if (!t_flag && first_call) { first_call = 0; call_heart_beat(); } while (1) { /* Has to be cleared if we jumped out of process_user_command() */ current_interactive = 0; set_eval(max_cost); if (obj_list_replace || obj_list_destruct) remove_destructed_objects(); /* * shut down MudOS if MudOS_is_being_shut_down is set. */ if (MudOS_is_being_shut_down) shutdownMudOS(0); if (slow_shut_down_to_do) { int tmp = slow_shut_down_to_do; slow_shut_down_to_do = 0; slow_shut_down(tmp); } /* * select */ make_selectmasks(); timeout.tv_sec = 1; timeout.tv_usec = 0; #ifndef hpux nb = select(max_fd + 1, &readmask, &writemask, (fd_set *) 0, &timeout); #else nb = select(max_fd + 1, (int *) &readmask, (int *) &writemask, (int *) 0, &timeout); #endif /* * process I/O if necessary. */ if (nb > 0) { process_io(); } /* * process user commands. */ for (i = 0; process_user_command() && i < max_users; i++) ; /* * call outs */ call_out(); #ifdef PACKAGE_ASYNC check_reqs(); #endif } } /* backend() */ /* * Despite the name, this routine takes care of several things. * It will run once every 15 minutes. * * . It will attempt to reconnect to the address server if the connection has * been lost. * . It will loop through all objects. * * . 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 * * 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; #ifndef NO_IP_DEMON extern int no_ip_demon; static int next_server_time; #endif object_t *ob; VOLATILE object_t *next_ob, *last_good_ob; error_context_t econ; #ifndef NO_IP_DEMON if (current_time >= next_server_time) { /* initialize the address server. if it is already initialized, then * this is a nop. this will cause the driver to reattempt connecting * to the address server once every 15 minutes in the event that it * has gone down. */ if (!no_ip_demon && next_server_time) init_addr_server(ADDR_SERVER_IP, ADDR_SERVER_PORT); next_server_time = current_time + 15 * 60; } #endif if (current_time < next_time) return; /* Not time to look yet */ next_time = current_time + 5 * 60; /* Next time is in 5 minutes */ /* * 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. */ next_ob = obj_list; last_good_ob = obj_list; save_context(&econ); if (SETJMP(econ.context)) restore_context(&econ); while ((ob = (object_t *)next_ob)) { int ready_for_clean_up = 0; if (ob->flags & O_DESTRUCTED){ if(last_good_ob->flags & O_DESTRUCTED) ob = obj_list; /* restart */ else ob = (object_t *)last_good_ob; } next_ob = ob->next_all; /* * Check reference time before reset() is called. */ if (current_time - ob->time_of_ref > time_to_clean_up) ready_for_clean_up = 1; #if !defined(NO_RESETS) && !defined(LAZY_RESETS) /* * Should this object have reset(1) called ? */ if ((ob->flags & O_WILL_RESET) && (ob->next_reset < current_time) && !(ob->flags & O_RESET_STATE)) { debug(d_flag, ("RESET /%s\n", ob->obname)); set_eval(max_cost); reset_object(ob); if(ob->flags & O_DESTRUCTED) continue; } #endif 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 (ready_for_clean_up && (ob->flags & O_WILL_CLEAN_UP)) { int save_reset_state = ob->flags & O_RESET_STATE; svalue_t *svp; debug(d_flag, ("clean up /%s\n", ob->obname)); /* * 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). * * Note that if it is in the apply_low cache, it will also * get a flag of 1, which may cause the mudlib not to clean * up the object. This isn't bad because: * (1) one expects it is rare for objects that have untouched * long enough to clean_up to still be in the cache, especially * on busy MUDs. * (2) the ones that are are the more heavily used ones, so * keeping them around seems justified. */ push_number(ob->flags & (O_CLONE) ? 0 : ob->prog->ref); set_eval(max_cost); svp = apply(APPLY_CLEAN_UP, ob, 1, ORIGIN_DRIVER); 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; } } last_good_ob = ob; } pop_context(&econ); } /* look_for_objects_to_swap() */ /* 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. */ typedef struct { object_t *ob; short heart_beat_ticks; short time_to_heart_beat; } heart_beat_t; static heart_beat_t *heart_beats = 0; static int max_heart_beats = 0; static int heart_beat_index = 0; static int num_hb_objs = 0; static int num_hb_to_do = 0; static int num_hb_calls = 0; /* starts */ static float perc_hb_probes = 100.0; /* decaying avge of how many complete */ void call_heart_beat() { object_t *ob; heart_beat_t *curr_hb; error_context_t econ; current_interactive = 0; if ((num_hb_to_do = num_hb_objs)) { num_hb_calls++; heart_beat_index = 0; save_context(&econ); while (1) { ob = (curr_hb = &heart_beats[heart_beat_index])->ob; DEBUG_CHECK(!(ob->flags & O_HEART_BEAT), "Heartbeat not set in object on heartbeat list!"); /* is it time to do a heart beat ? */ curr_hb->heart_beat_ticks--; if (ob->prog->heart_beat != 0) { if (curr_hb->heart_beat_ticks < 1) { object_t *new_command_giver; curr_hb->heart_beat_ticks = curr_hb->time_to_heart_beat; current_heart_beat = ob; new_command_giver = ob; #ifndef NO_SHADOWS while (new_command_giver->shadowing) new_command_giver = new_command_giver->shadowing; #endif #ifndef NO_ADD_ACTION if (!(new_command_giver->flags & O_ENABLE_COMMANDS)) new_command_giver = 0; #endif #ifdef PACKAGE_MUDLIB_STATS add_heart_beats(&ob->stats, 1); #endif set_eval(max_cost); if (SETJMP(econ.context)) { restore_context(&econ); } else { save_command_giver(new_command_giver); call_direct(ob, ob->prog->heart_beat - 1, ORIGIN_DRIVER, 0); pop_stack(); /* pop the return value */ restore_command_giver(); } current_object = 0; } } if (++heart_beat_index == num_hb_to_do) break; } pop_context(&econ); if (heart_beat_index < num_hb_to_do) perc_hb_probes = 100 * (float) heart_beat_index / num_hb_to_do; else perc_hb_probes = 100.0; heart_beat_index = num_hb_to_do = 0; } current_prog = 0; current_heart_beat = 0; look_for_objects_to_swap(); #ifdef PACKAGE_MUDLIB_STATS mudlib_stats_decay(); #endif } /* call_heart_beat() */ int query_heart_beat (object_t * ob) { int index; if (!(ob->flags & O_HEART_BEAT)) return 0; index = num_hb_objs; while (index--) { if (heart_beats[index].ob == ob) return heart_beats[index].time_to_heart_beat; } return 0; } /* query_heart_beat() */ /* add or remove an object from the heart beat list; does the major check... * If an object removes something from the list from within a heart beat, * various pointers in call_heart_beat could be stuffed, so we must * check current_heart_beat and adjust pointers. */ int set_heart_beat (object_t * ob, int to) { int index; if (ob->flags & O_DESTRUCTED) return 0; if (!to) { int num; index = num_hb_objs; while (index--) { if (heart_beats[index].ob == ob) break; } if (index < 0) return 0; if (num_hb_to_do) { if (index <= heart_beat_index) heart_beat_index--; if (index < num_hb_to_do) num_hb_to_do--; } if ((num = (num_hb_objs - (index + 1)))) memmove(heart_beats + index, heart_beats + (index + 1), num * sizeof(heart_beat_t)); num_hb_objs--; ob->flags &= ~O_HEART_BEAT; return 1; } if (ob->flags & O_HEART_BEAT) { if (to < 0) return 0; index = num_hb_objs; while (index--) { if (heart_beats[index].ob == ob) { heart_beats[index].time_to_heart_beat = heart_beats[index].heart_beat_ticks = to; break; } } DEBUG_CHECK(index < 0, "Couldn't find enabled object in heart_beat list!\n"); } else { heart_beat_t *hb; if (!max_heart_beats) heart_beats = CALLOCATE(max_heart_beats = HEART_BEAT_CHUNK, heart_beat_t, TAG_HEART_BEAT, "set_heart_beat: 1"); else if (num_hb_objs == max_heart_beats) { max_heart_beats += HEART_BEAT_CHUNK; heart_beats = RESIZE(heart_beats, max_heart_beats, heart_beat_t, TAG_HEART_BEAT, "set_heart_beat: 1"); } hb = &heart_beats[num_hb_objs++]; hb->ob = ob; if (to < 0) to = 1; hb->time_to_heart_beat = to; hb->heart_beat_ticks = to; ob->flags |= O_HEART_BEAT; } return 1; } int heart_beat_status (outbuffer_t * ob, int verbose) { char buf[20]; if (verbose == 1) { outbuf_add(ob, "Heart beat information:\n"); outbuf_add(ob, "-----------------------\n"); outbuf_addv(ob, "Number of objects with heart beat: %d, starts: %d\n", num_hb_objs, num_hb_calls); /* passing floats to varargs isn't highly portable so let sprintf handle it */ sprintf(buf, "%.2f", perc_hb_probes); outbuf_addv(ob, "Percentage of HB calls completed last time: %s\n", buf); } return (0); } /* heart_beat_status() */ /* New version used when not in -o mode. The epilog() in master.c is * supposed to return an array of files (castles in 2.4.5) to load. The array * returned by apply() will be freed at next call of apply(), which means that * the ref count has to be incremented to protect against deallocation. * * The master object is asked to do the actual loading. */ void preload_objects (int eflag) { VOLATILE array_t *prefiles; svalue_t *ret; VOLATILE int ix; error_context_t econ; save_context(&econ); if (SETJMP(econ.context)) { restore_context(&econ); pop_context(&econ); return; } push_number(eflag); ret = apply_master_ob(APPLY_EPILOG, 1); pop_context(&econ); if ((ret == 0) || (ret == (svalue_t *)-1) || (ret->type != T_ARRAY)) return; else prefiles = ret->u.arr; if ((prefiles == 0) || (prefiles->size < 1)) return; debug_message("\nLoading preloaded files ...\n"); prefiles->ref++; ix = 0; /* in case of an error, effectively do a 'continue' */ save_context(&econ); if (SETJMP(econ.context)) { restore_context(&econ); ix++; } for ( ; ix < prefiles->size; ix++) { if (prefiles->item[ix].type != T_STRING) continue; set_eval(max_cost); push_svalue(((array_t *)prefiles)->item + ix); (void) apply_master_ob(APPLY_PRELOAD, 1); } free_array((array_t *)prefiles); pop_context(&econ); } /* preload_objects() */ /* All destructed objects are moved into a sperate linked list, * and deallocated after program execution. */ INLINE void remove_destructed_objects() { object_t *ob, *next; if (obj_list_replace) replace_programs(); for (ob = obj_list_destruct; ob; ob = next) { next = ob->next_all; destruct2(ob); } obj_list_destruct = 0; } /* remove_destructed_objects() */ static double load_av = 0.0; void update_load_av() { 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; } /* update_load_av() */ static double compile_av = 0.0; void update_compile_av (int lines) { 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; } /* update_compile_av() */ char *query_load_av() { static char buff[100]; sprintf(buff, "%.2f cmds/s, %.2f comp lines/s", load_av, compile_av); return (buff); } /* query_load_av() */ #ifdef F_HEART_BEATS array_t *get_heart_beats() { int nob = 0, n = num_hb_objs; heart_beat_t *hb = heart_beats; object_t **obtab; array_t *arr; #ifdef F_SET_HIDE int apply_valid_hide = 1, display_hidden = 0; #endif if(n) obtab = CALLOCATE(n, object_t *, TAG_TEMPORARY, "heart_beats"); else obtab = NULL; while (n--) { #ifdef F_SET_HIDE if (hb->ob->flags & O_HIDDEN) { if (apply_valid_hide) { apply_valid_hide = 0; display_hidden = valid_hide(current_object); } if (!display_hidden) continue; } #endif obtab[nob++] = (hb++)->ob; } arr = allocate_empty_array(nob); while (nob--) { arr->item[nob].type = T_OBJECT; arr->item[nob].u.ob = obtab[nob]; add_ref(arr->item[nob].u.ob, "get_heart_beats"); } if(obtab) FREE(obtab); return arr; } #endif