/*--------------------------------------------------------------------------- * Gamedriver Callout handling. * *--------------------------------------------------------------------------- * Callouts are delayed calls to (non-static) functions, with delays * measured in seconds. The minimal resolution is of course the duration * a backend cycle. The command_giver is saved over the delay. * * As a simplistic measure against 'rabbits' (think fork-bombs using * callouts), the callouts of one user can use only MAX_EVAL_COST at * one time altogether. * * Pending call outs are held in a list, sorted in ascending order of * remaining delaytime. The actual delay values stored are deltas: the * delay this callout is to be scheduled after its predecessor in * the list. * * TODO: It would be nice if the callout would store from where the * TODO:: callout originated and fake a control-stack entry for a proper * TODO:: traceback. However, this has to take swapping into account. *--------------------------------------------------------------------------- */ #include "driver.h" #include "typedefs.h" #include "call_out.h" #include "actions.h" #include "array.h" #include "backend.h" #include "closure.h" #include "comm.h" #include "exec.h" #include "gcollect.h" #include "interpret.h" #include "main.h" #include "mstrings.h" #include "object.h" #include "simulate.h" #include "stdstrings.h" #include "strfuns.h" #include "svalue.h" #ifdef USE_SWAP #include "swap.h" #endif #include "wiz_list.h" #include "xalloc.h" #include "../mudlib/sys/debug_info.h" /*-------------------------------------------------------------------------*/ /* The description of one callout. * * The function to call be either given by object:name or as closure. */ struct call { struct call *next; /* link to next structure */ /* TODO: p_int or mp_int */ int delta; /* Delay in relation to the previous structure */ callback_t fun; object_t *command_giver; /* the saved command_giver */ }; static struct call *call_list = NULL; /* The list of pending call_outs, sorted in ascending order of delay. */ static long num_callouts = 0; /* Number of active callouts. */ /*-------------------------------------------------------------------------*/ static INLINE void free_call (struct call *cop) /* Deallocate all resources bound to and put into the free list. * This can be used for used and unused callouts alike. */ { free_callback(&(cop->fun)); if (cop->command_giver) free_object(cop->command_giver, "free_call"); pfree(cop); } /* free_call() */ /*-------------------------------------------------------------------------*/ static void insert_call (struct call *cop, int delay) /* Inser the call_out structure with the into the callout * list. */ { struct call **copp; /* Auxiliary pointers for list insertion */ struct call *cop2; num_callouts++; for (copp = &call_list; NULL != (cop2 = *copp); copp = &cop2->next) { int delta; if ((delta = cop2->delta) >= delay) { cop2->delta -= delay; cop->delta = delay; cop->next = *copp; *copp = cop; return; } delay -= (delta >= 0 ? delta : 0); /* Especially when called from within a call_out, delta may be * negative. */ } *copp = cop; cop->delta = delay; cop->next = NULL; } /* insert_call() */ /*-------------------------------------------------------------------------*/ svalue_t * v_call_out (svalue_t *sp, int num_arg) /* EFUN: call_out() * * void call_out(string fun, int delay, mixed arg, ...) * void call_out(closure cl, int delay, mixed arg, ...) * * Set up a call to function fun or closure cl in the current * object. The call will take place in delay seconds, with the * remaining argument list provided. References in the argument list * will be passed as number 0, though. */ { svalue_t *arg; /* Pointer to efun arguments */ int delay; struct call *cop; /* New callout structure */ int error_index; arg = sp - num_arg + 1; /* If the current object is destructed, free everything on the stack * and return. */ if (current_object->flags & O_DESTRUCTED) { do { free_svalue(sp--); } while (--num_arg); return sp; } if (max_callouts && max_callouts <= num_callouts) { errorf("Too many callouts at once (max. %ld).\n", (long)max_callouts); /* NOTREACHED */ } /* Get a new call structure. * Note: it is not useful to pool these allocations, as muds tend * to have spikes of high callout usage, but a low longterm average * usage. Any overhead savings by a pool are more than made up * by the unused structures sitting around. */ cop = pxalloc(sizeof (struct call)); /* Get the function designation from the stack */ if (arg[0].type == T_STRING) { error_index = setup_function_callback(&(cop->fun), current_object , arg[0].u.str , num_arg-2, arg+2 , MY_TRUE ); free_string_svalue(arg); } else error_index = setup_closure_callback(&(cop->fun), arg , num_arg-2, arg+2 , MY_TRUE ); if (error_index >= 0) { pfree(cop); /* The callback structure was invalidated automatically. */ vefun_bad_arg(error_index+2, arg-1); /* NOTREACHED */ return arg-1; } /* We can do the callout. */ cop->command_giver = command_giver; /* save current player context */ if (command_giver) ref_object(command_giver, "f_call_out"); /* Bump its ref */ /* Adjust the stack and get the delay */ sp = arg - 1; delay = arg[1].u.number; if (delay < 0) delay = 0; /* Insert the new structure at its proper place in the list */ insert_call(cop, delay); return sp; } /* v_call_out() */ /*-------------------------------------------------------------------------*/ void call_out (void) /* Check if there is any callout due to be called. If yes, do so. * This function is called from the heart_beat handling in the backend.c. * It sets up its own error recovery context so that errors during an * execution won't disturb the rest of the game. */ { static int last_time; /* Last time this function was called */ static struct call *current_call_out; /* Current callout, static so that longjmp() won't clobber it. */ static object_t *called_object; /* Object last called, static so that longjmp() won't clobber it */ struct error_recovery_info error_recovery_info; /* No calls pending: just update the last_time and return */ if (call_list == NULL) { last_time = current_time; return; } /* If not set yet, initialize last_time on the first call */ if (last_time == 0) last_time = current_time; /* Update the first .delta in the list (so it won't happen * twice in case of an error. */ call_list->delta -= current_time - last_time; last_time = current_time; current_interactive = NULL; /* Activate the local error recovery context */ 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)) { /* An error occured: recover and delete the guilty callout */ struct call *cop; object_t *ob; wiz_list_t *user; mark_end_evaluation(); clear_state(); debug_message("%s Error in call out.\n", time_stamp()); cop = current_call_out; ob = called_object; if (ob) { /* Disable the user for this call_out cycle. This is mainly * meant to stop runaway call_outs causing too-long-evaluations. */ user = ob->user; user->call_out_cost = -1; } free_call(cop); } /* (Re)initialize stack and tracing */ tracedepth = 0; /* Loop over the call list until it is empty or until all * due callouts are processed. */ while (call_list && call_list->delta <= 0) { object_t *ob; struct call *cop; wiz_list_t *user; /* Move the first callout out of the chain. */ cop = call_list; call_list = cop->next; current_call_out = cop; num_callouts--; /* A special case: * If a lot of time has passed, so that current call out was missed, * then it will have a negative delta. This negative delta implies * that the next call out in the list has to be adjusted. */ if (cop->delta < 0 && call_list) call_list->delta += cop->delta; /* Get the object for the function call and make sure it's valid */ ob = callback_object(&(cop->fun)); if (!ob) { /* Nothing to call */ free_call(cop); continue; } #ifdef USE_SWAP if (O_PROG_SWAPPED(ob) && load_ob_from_swap(ob) < 0) { debug_message("%s Error in call_out: out of memory: " "unswap object '%s'.\n", time_stamp() , get_txt(ob->name)); free_call(cop); continue; } #endif /* Check if the user has exceeded its eval limit for this cycle. * If yes, reschedule the call_out for one second later. */ user = ob->user; if (user->last_call_out == current_time && user->call_out_cost < 0 ) { debug_message("%s call_out: user '%s' had an error: " "rescheduling call_out.\n" , time_stamp() , user->name ? get_txt(user->name) : ""); insert_call(cop, 1); continue; } /* Determine the command_giver for the call. * If a command_giver is given in the callout structure, use that one. * Else test the object to be called or the object it's shadowing for * being a command_giver. * Remember that a now-destructed commandgiver is different from having * no commandgiver to begin with. */ if (cop->command_giver) { if (!(cop->command_giver->flags & O_DESTRUCTED)) { command_giver = cop->command_giver; if (O_IS_INTERACTIVE(command_giver)) trace_level = O_GET_INTERACTIVE(command_giver)->trace_level; } else command_giver = NULL; } #ifdef USE_SHADOWING else if (ob->flags & O_SHADOW) { /* Look at the object which is at the end of the shadow chain. */ shadow_t *shadow_sent; object_t *sob; sob = ob; while ((shadow_sent = O_GET_SHADOW(sob)), shadow_sent->shadowing) sob = shadow_sent->shadowing; if (sob->flags & O_ENABLE_COMMANDS) { command_giver = sob; if (shadow_sent->ip) trace_level = shadow_sent->ip->trace_level; else trace_level = 0; } else { command_giver = NULL; trace_level = 0; } } #endif else { /* If at all, this object must be the command_giver */ if (ob->flags & O_ENABLE_COMMANDS) command_giver = ob; else command_giver = NULL; trace_level = 0; } /* Finally, call the function (unless the object was destructed). */ called_object = current_object = ob; if (user->last_call_out != current_time) { user->last_call_out = current_time; CLEAR_EVAL_COST; } else assigned_eval_cost = eval_cost = user->call_out_cost; mark_start_evaluation(); (void)backend_callback(&(cop->fun), 0); user->call_out_cost = eval_cost; mark_end_evaluation(); /* The function call used up all the arguments, now free * the rest */ free_call(cop); } /* while (callouts pending) */ rt_context = error_recovery_info.rt.last; } /* call_out() */ /*-------------------------------------------------------------------------*/ static void find_call_out (object_t *ob, svalue_t *fun, Bool do_free_call) /* Find the (first) callout for / (or if it is a closure). * If is true, the found callout is removed. * * In either case, * is modified into a NUMBER holding the time left * for the found/removed callout. If no callout was found, -1 is returned. */ { struct call **copp, *cop; int delay = 0; string_t *fun_name; /* Find callout by closure */ if (fun->type != T_STRING) { if (fun->type == T_CLOSURE) { for (copp = &call_list; NULL != (cop = *copp); copp = &cop->next) { delay += cop->delta; if (cop->fun.is_lambda && closure_eq(&(cop->fun.function.lambda), fun) ) { goto found; } } /* Not found */ free_svalue(fun); put_number(fun, -1); return; found: free_svalue(fun); if (do_free_call) { if (cop->next) cop->next->delta += cop->delta; *copp = cop->next; free_call(cop); num_callouts--; } /* It is possible to have delay < 0 if we are * called from inside call_out() . */ if (delay < 0) delay = 0; put_number(fun, delay); return; } fatal("find_call_out() got %s, expected string/closure.\n" , typename(fun->type)); /* NOTREACHED */ } /* Find callout by object/name */ fun_name = find_tabled(fun->u.str); if (fun_name != NULL) for (copp = &call_list; NULL != (cop = *copp); copp = &cop->next) { delay += cop->delta; if (cop->fun.function.named.ob == ob && cop->fun.function.named.name == fun_name && !cop->fun.is_lambda) { if (do_free_call) { if (cop->next) cop->next->delta += cop->delta; *copp = cop->next; free_call(cop); num_callouts--; } if (delay < 0) delay = 0; free_svalue(fun); put_number(fun, delay); return; } } /* if()for() */ /* Not found */ free_svalue(fun); put_number(fun, -1); } /* find_call_out() */ /*-------------------------------------------------------------------------*/ size_t call_out_status (strbuf_t *sbuf, Bool verbose) /* Compute and return the amount of memory used by callouts. * If is true, write detailed statistics to the current user. */ { remove_stale_call_outs(); if (verbose) { strbuf_add(sbuf, "\nCall out information:\n"); strbuf_add(sbuf,"---------------------\n"); strbuf_addf(sbuf, "Number of call outs: %8ld, %8ld bytes\n", num_callouts, num_callouts * sizeof (struct call)); } else { strbuf_addf(sbuf, "call out:\t\t\t%8ld %9ld\n" , num_callouts, num_callouts * sizeof (struct call)); } return num_callouts * sizeof (struct call); } /* call_out_status() */ /*-------------------------------------------------------------------------*/ void callout_dinfo_status (svalue_t *svp, int value) /* Return the callout information for debug_info(DINFO_DATA, DID_STATUS). * points to the svalue block for the result, this function fills in * the spots for the object table. * If is -1, points indeed to a value block; other it is * the index of the desired value and points to a single svalue. */ { #define ST_NUMBER(which,code) \ if (value == -1) svp[which].u.number = code; \ else if (value == which) svp->u.number = code remove_stale_call_outs(); ST_NUMBER(DID_ST_CALLOUTS, num_callouts); ST_NUMBER(DID_ST_CALLOUT_SIZE, num_callouts * sizeof(struct call)); #undef ST_NUMBER } /* callout_dinfo_status() */ /*-------------------------------------------------------------------------*/ #ifdef USE_PARANOIA void count_extra_ref_from_call_outs (void) /* Used to debug refcounts: count all refcounts in the callout handling. */ { struct call *cop; for (cop = call_list; cop; cop = cop->next) { count_callback_extra_refs(&(cop->fun)); if (cop->command_giver) count_extra_ref_in_object(cop->command_giver); } } #endif /*-------------------------------------------------------------------------*/ void remove_stale_call_outs (void) /* GC and statistics support: Remove all callouts referencing destructed * objects. */ { struct call **copp, *cop; for (copp = &call_list; NULL != (cop = *copp); ) { object_t *ob; ob = callback_object(&(cop->fun)); if (!ob) { num_callouts--; if (cop->next) cop->next->delta += cop->delta; *copp = cop->next; free_call(cop); continue; } copp = &cop->next; } } /* remove_stale_call_outs() */ #ifdef GC_SUPPORT /*-------------------------------------------------------------------------*/ void clear_ref_from_call_outs (void) /* GC Support: Clear all refs from the callout handling. */ { struct call *cop; for (cop = call_list; cop; cop = cop->next) { object_t *ob; clear_ref_in_callback(&(cop->fun)); if (NULL != (ob = cop->command_giver)) clear_object_ref(ob); } } /* clear_ref_from_call_outs() */ /*-------------------------------------------------------------------------*/ void count_ref_from_call_outs (void) /* GC Support: Clear all refs from the callout handling. */ { struct call *cop; object_t *ob; for (cop = call_list; cop; cop = cop->next) { count_ref_in_callback(&(cop->fun)); if ( NULL != (ob = cop->command_giver) ) { if (ob->flags & O_DESTRUCTED) { reference_destructed_object(ob); cop->command_giver = NULL; } else { ob->ref++; } } } } /* count_ref_from_call_outs() */ #endif /* GC_SUPPORT */ /*-------------------------------------------------------------------------*/ static vector_t * get_all_call_outs (void) /* Construct an array of all pending call_outs (whose object is not * destructed). Every item in the array is itself an array of 4 or * more entries: * 0: The object (only if the function is a string). * 1: The function (string or closure). * 2: The delay. * 3..: The argument(s). */ { int i, next_time; struct call *cop; vector_t *v; /* Count the number of pending callouts and allocate * the result array. */ for (i = 0, cop = call_list; cop; cop = cop->next) { if (!callback_object(&(cop->fun))) continue; i++; } v = allocate_array(i); /* assume that all elements are inited to 0 */ /* Create the result array contents. */ next_time = 0; for (i=0, cop = call_list; cop; cop = cop->next) { vector_t *vv; object_t *ob; next_time += cop->delta; ob = callback_object(&(cop->fun)); if (!ob) continue; /* Get the subarray */ vv = allocate_array(3 + cop->fun.num_arg); if (cop->fun.is_lambda) { if (cop->fun.function.lambda.x.closure_type == CLOSURE_LFUN) put_ref_object( vv->item , cop->fun.function.lambda.u.lambda->function.lfun.ob , "get_all_call_outs"); else put_ref_object(vv->item, ob, "get_all_call_outs"); assign_svalue_no_free(&vv->item[1], &cop->fun.function.lambda); } else { put_ref_object(vv->item, ob, "get_all_call_outs"); put_ref_string(vv->item + 1, cop->fun.function.named.name); } vv->item[2].u.number = next_time; if (cop->fun.num_arg > 0) { svalue_t *source, *dest; int nargs; nargs = cop->fun.num_arg; if (nargs > 1) source = cop->fun.arg.u.lvalue; else source = &(cop->fun.arg); dest = &vv->item[3]; do { assign_svalue_no_free(dest++, source++); } while (--nargs); } put_array(v->item + i, vv); i++; } return v; } /* get_all_call_outs() */ /*-------------------------------------------------------------------------*/ svalue_t * f_call_out_info (svalue_t *sp) /* EFUN call_out_info() * * mixed *call_out_info(void) * * Get information about all pending call outs. The result is an * array in which every entry is itself an array describing one * call_out. * * The efun causes the the privilege violation ("call_out_info", * this_object()). If it is not satisfied, the result will be * the empty array. */ { if (privilege_violation(STR_CALL_OUT_INFO, &const0, sp)) { push_array(sp, get_all_call_outs()); } else { push_ref_array(sp, &null_vector); } return sp; } /* f_call_out_info() */ /*-------------------------------------------------------------------------*/ svalue_t * f_find_call_out (svalue_t *sp) /* EFUN find_call_out() * * int find_call_out(string func) * int find_call_out(closure func) * * Find the first call-out due to be executed for function func * in the current object, and return the time left. If no call-out * is found return -1. */ { find_call_out(current_object, sp, MY_FALSE); return sp; } /* f_find_call_out() */ /*-------------------------------------------------------------------------*/ svalue_t * f_remove_call_out (svalue_t *sp) /* EFUN remove_call_out() * * int remove_call_out(string fun) * int remove_call_out(closure fun) * * Remove next pending call-out for function fun in this object. * The time left is returned. * * -1 is returned if there were no call-outs pending to this * function. */ { find_call_out(current_object, sp, MY_TRUE); return sp; } /* f_remove_call_out() */ /***************************************************************************/