/*! \file cque.cpp * \brief Commands and functions for manipulating the command queue. * * This forms the upper-level command list queue, and includes timed commands * and semaphores. The lower-level task implementation is found in timer.cpp. */ #include "copyright.h" #include "autoconf.h" #include "config.h" #include "externs.h" bool break_called = false; static CLinearTimeDelta GetProcessorUsage(void) { CLinearTimeDelta ltd; #if defined(WINDOWS_PROCESSES) FILETIME ftCreate; FILETIME ftExit; FILETIME ftKernel; FILETIME ftUser; GetProcessTimes(GetCurrentProcess(), &ftCreate, &ftExit, &ftKernel, &ftUser); ltd.Set100ns(*reinterpret_cast(&ftUser)); #endif // WINDOWS_PROCESSES #if defined(UNIX_PROCESSES) #if defined(HAVE_GETRUSAGE) struct rusage usage; getrusage(RUSAGE_SELF, &usage); ltd.SetTimeValueStruct(&usage.ru_utime); #else CLinearTimeAbsolute ltaNow; ltaNow.GetLocal(); ltd = ltaNow - mudstate.start_time; #endif #endif return ltd; } // --------------------------------------------------------------------------- // add_to: Adjust an object's queue or semaphore count. // static bool add_to(const dbref executor, const int am, int attrnum, int *pnum) { int aflags; dbref aowner; LBuf atr_gotten = LBuf_Adopt(atr_get("add_to.68", executor, attrnum, &aowner, &aflags)); // int64_t, not int (#1402): semaphore counts are attribute integers. // int64_t num = mux_atoi64(atr_gotten); num += am; UTF8 buff[I64BUF_SIZE]; size_t nlen = 0; *buff = '\0'; if (num) { nlen = mux_i64toa(num, buff); } if (!atr_add_raw_LEN(executor, attrnum, buff, nlen)) { STARTLOG(LOG_PROBLEMS, "QUE", "SEMAPH"); log_printf(T("add_to(#%d/%d): failed to persist semaphore count %lld."), executor, attrnum, static_cast(num)); ENDLOG; return false; } if (pnum) { // Caller still takes int; clamp after full-width arithmetic. // if (num > INT_MAX) { *pnum = INT_MAX; } else if (num < INT_MIN) { *pnum = INT_MIN; } else { *pnum = static_cast(num); } } return true; } // Max recursive depth for process_command_list_inline (@include, // @dolist/now, and any other inline action-list expansion). Bounds // stack growth from mutual @include recursion. // static const int MAX_INCLUDE_NEST = 50; // process_command_list_inline: run a semicolon-separated command list // synchronously, honoring @break/@assert (the loop stops when // break_called fires) and ;| piping (%|), exactly like the queued // runner below. Used by @dolist/now and @include (#788). // // The list is its own pipe domain, like a fresh queue entry: the // enclosing pipe context is saved and restored, so an inline list can // itself run inside a piped segment without corrupting the outer // capture, and pipes inside the list behave as they would queued. // // parse_to() rewrites the buffer in place; callers pass a private copy. // void process_command_list_inline(dbref executor, dbref caller, dbref enactor, int eval, UTF8 *clist, const UTF8 *cargs[], int ncargs) { if (mudstate.include_nest_lev >= MAX_INCLUDE_NEST) { notify(executor, M_("Include nesting limit exceeded.")); return; } mudstate.include_nest_lev++; UTF8 *save_pout = mudstate.pout; UTF8 *save_poutnew = mudstate.poutnew; UTF8 *save_poutbufc = mudstate.poutbufc; dbref save_poutobj = mudstate.poutobj; bool save_inpipe = mudstate.inpipe; int save_nest = mudstate.pipe_nest_lev; mudstate.pout = nullptr; mudstate.poutnew = nullptr; mudstate.poutbufc = nullptr; mudstate.poutobj = NOTHING; mudstate.inpipe = false; mudstate.pipe_nest_lev = 0; while ( clist && !break_called) { UTF8 *cp = parse_to(&clist, ';', EV_STRIP_AROUND); if ( cp && *cp) { // Will this command be piped into the next? // if ( clist && *clist == '|' && mudstate.pipe_nest_lev < mudconf.ntfy_nest_lim) { clist++; mudstate.pipe_nest_lev++; mudstate.inpipe = true; mudstate.poutnew = alloc_lbuf("inline_list.pipe"); mudstate.poutbufc = mudstate.poutnew; mudstate.poutobj = executor; } else { mudstate.inpipe = false; mudstate.poutobj = NOTHING; } process_command(executor, caller, enactor, eval, false, cp, cargs, ncargs); // Transition %| value. // if (mudstate.pout) { free_lbuf(mudstate.pout); mudstate.pout = nullptr; } if (mudstate.poutnew) { *mudstate.poutbufc = '\0'; mudstate.pout = mudstate.poutnew; mudstate.poutnew = nullptr; mudstate.poutbufc = nullptr; } } } // Clean up this list's %| and restore the enclosing pipe context. // if (mudstate.pout) { free_lbuf(mudstate.pout); mudstate.pout = nullptr; } if (mudstate.poutnew) { free_lbuf(mudstate.poutnew); mudstate.poutnew = nullptr; mudstate.poutbufc = nullptr; } mudstate.pout = save_pout; mudstate.poutnew = save_poutnew; mudstate.poutbufc = save_poutbufc; mudstate.poutobj = save_poutobj; mudstate.inpipe = save_inpipe; mudstate.pipe_nest_lev = save_nest; mudstate.include_nest_lev--; } // This Task assumes that pEntry is already unlinked from any lists it may // have been related to. // static void Task_RunQueueEntry(void *pEntry, const int iUnused) { UNUSED_PARAMETER(iUnused); const auto point = static_cast(pEntry); const dbref executor = point->executor; if ( Good_obj(executor) && !Going(executor)) { giveto(executor, mudconf.waitcost); mudstate.curr_enactor = point->enactor; mudstate.curr_executor = executor; a_Queue(Owner(executor), -1); point->executor = NOTHING; if (!Halted(executor)) { // Load scratch args. // for (int i = 0; i < MAX_GLOBAL_REGS; i++) { if (mudstate.global_regs[i]) { RegRelease(mudstate.global_regs[i]); mudstate.global_regs[i] = nullptr; } mudstate.global_regs[i] = point->scr[i]; point->scr[i] = nullptr; } NamedRegsClear(mudstate.named_regs); mudstate.named_regs = point->named_scr; point->named_scr = nullptr; #if defined(STUB_SLAVE) if (nullptr != mudstate.pResultsSet) { mudstate.pResultsSet->Release(); mudstate.pResultsSet = nullptr; } mudstate.pResultsSet = point->pResultsSet; point->pResultsSet = nullptr; mudstate.iRow = point->iRow; #endif // STUB_SLAVE // Restore iter/switch context from queue entry. // bool bIterContext = (point->iter_token != nullptr); bool bSwitchContext = (point->switch_token != nullptr); const UTF8 *save_switch = nullptr; if (bIterContext) { bool bLoopInBounds = ( 0 <= mudstate.in_loop && mudstate.in_loop < MAX_ITEXT); if (bLoopInBounds) { mudstate.itext[mudstate.in_loop] = point->iter_token; mudstate.inum[mudstate.in_loop] = point->iter_number; } mudstate.in_loop++; } if (bSwitchContext) { save_switch = mudstate.switch_token; mudstate.switch_token = point->switch_token; } UTF8 *command = point->comm; mux_assert(!mudstate.inpipe); mux_assert(mudstate.pipe_nest_lev == 0); mux_assert(mudstate.poutobj == NOTHING); mux_assert(!mudstate.pout); break_called = false; while ( command && !break_called) { mux_assert(!mudstate.poutnew); mux_assert(!mudstate.poutbufc); UTF8 *cp = parse_to(&command, ';', EV_STRIP_AROUND); if ( cp && *cp) { // Will command be piped? // if ( command && *command == '|' && mudstate.pipe_nest_lev < mudconf.ntfy_nest_lim) { command++; mudstate.pipe_nest_lev++; mudstate.inpipe = true; mudstate.poutnew = alloc_lbuf("process_command.pipe"); mudstate.poutbufc = mudstate.poutnew; mudstate.poutobj = executor; } else { mudstate.inpipe = false; mudstate.poutobj = NOTHING; } CLinearTimeAbsolute ltaBegin; ltaBegin.GetUTC(); alarm_clock.set(mudconf.max_cmdsecs); CLinearTimeDelta ltdUsageBegin = GetProcessorUsage(); const UTF8 *log_cmdbuf = process_command(executor, point->caller, point->enactor, point->eval, false, cp, const_cast(point->env), point->nargs); CLinearTimeAbsolute ltaEnd; ltaEnd.GetUTC(); if (alarm_clock.alarmed) { notify(executor, M_("GAME: Expensive activity abbreviated.")); // The command has already returned by this point -- // polling loops abbreviated it mid-run -- so the HALT // only quarantines future work. That is right for a // machine object, whose future work is more of the // same runaway, but wrong for a wizard player, whose // future work is the next typed command -- often the // one that fixes the problem. Don't dark the admin. // // halt_que's first arg is the owner key (matched // against Owner(entry->executor)), not the running // object. For a non-player object, halt only that // object's entries; for a player, halt all of theirs. // Good_obj before touching flags (enactor/executor // may be gone by the time the alarm fires). // bool bExemptEnactor = isPlayer(point->enactor) && Wizard(point->enactor); bool bExemptExecutor = isPlayer(executor) && Wizard(executor); if ( !bExemptEnactor && Good_obj(point->enactor)) { s_Halted(point->enactor); if (isPlayer(point->enactor)) { halt_que(point->enactor, NOTHING); } else { halt_que(Owner(point->enactor), point->enactor); } } if ( !bExemptExecutor && Good_obj(executor)) { s_Halted(executor); if (isPlayer(executor)) { halt_que(executor, NOTHING); } else { halt_que(Owner(executor), executor); } } if ( bExemptEnactor || bExemptExecutor) { STARTLOG(LOG_PROBLEMS, "CMD", "CPU"); log_name_and_loc(executor); log_text(T(" expensive activity abbreviated; wizard player exempted from HALT")); ENDLOG; } } alarm_clock.clear(); CLinearTimeDelta ltdUsageEnd = GetProcessorUsage(); CLinearTimeDelta ltd = ltdUsageEnd - ltdUsageBegin; db[executor].cpu_time_used += ltd; ltd = ltaEnd - ltaBegin; if (mudconf.rpt_cmdsecs < ltd) { STARTLOG(LOG_PROBLEMS, "CMD", "CPU"); log_name_and_loc(executor); LBuf logbuf = LBuf_Src("do_top.LOG.cpu"); mux_sprintf(logbuf, LBUF_SIZE, T(" queued command taking %s secs (enactor #%d): "), ltd.ReturnSecondsString(4), point->enactor); log_text(logbuf); log_text(log_cmdbuf); ENDLOG; } } // Transition %| value. // if (mudstate.pout) { free_lbuf(mudstate.pout); mudstate.pout = nullptr; } if (mudstate.poutnew) { *mudstate.poutbufc = '\0'; mudstate.pout = mudstate.poutnew; mudstate.poutnew = nullptr; mudstate.poutbufc = nullptr; } } // Clean up %| value. // if (mudstate.pout) { free_lbuf(mudstate.pout); mudstate.pout = nullptr; } mudstate.pipe_nest_lev = 0; mudstate.inpipe = false; mudstate.poutobj = NOTHING; // Restore iter/switch context. // if (bIterContext) { mudstate.in_loop--; bool bLoopInBounds = ( 0 <= mudstate.in_loop && mudstate.in_loop < MAX_ITEXT); if (bLoopInBounds) { mudstate.itext[mudstate.in_loop] = nullptr; mudstate.inum[mudstate.in_loop] = 0; } } if (bSwitchContext) { mudstate.switch_token = save_switch; } } } for (auto& i : point->scr) { if (i) { RegRelease(i); i = nullptr; } } NamedRegsClear(point->named_scr); for (auto& global_reg : mudstate.global_regs) { if (global_reg) { RegRelease(global_reg); global_reg = nullptr; } } NamedRegsClear(mudstate.named_regs); #if defined(STUB_SLAVE) mudstate.iRow = RS_TOP; if (nullptr != mudstate.pResultsSet) { mudstate.pResultsSet->Release(); mudstate.pResultsSet = nullptr; } #endif // STUB_SLAVE if (point->switch_token) { MEMFREE(point->switch_token); point->switch_token = nullptr; } if (point->iter_token) { MEMFREE(point->iter_token); point->iter_token = nullptr; } MEMFREE(point->text); point->text = nullptr; free_qentry(point); } // --------------------------------------------------------------------------- // que_want: Do we want this queue entry? // static bool que_want(const BQUE *entry, const dbref ptarg, dbref otarg) { if ( ptarg != NOTHING && ptarg != Owner(entry->executor)) { return false; } return ( otarg == NOTHING || otarg == entry->executor); } static void Task_SemaphoreTimeout(void *pExpired, const int iUnused) { UNUSED_PARAMETER(iUnused); // A semaphore has timed out. // const auto point = static_cast(pExpired); (void)add_to(point->u.s.sem, -1, point->u.s.attr, nullptr); point->u.s.sem = NOTHING; Task_RunQueueEntry(point, 0); } void Task_SQLTimeout(void *pExpired, const int iUnused) { UNUSED_PARAMETER(iUnused); // A SQL Query has timed out. Actually, this isn't supported. // const auto point = static_cast(pExpired); Task_RunQueueEntry(point, 0); } static dbref Halt_Player_Target; static dbref Halt_Object_Target; static int Halt_Entries; static dbref Halt_Player_Run; static dbref Halt_Entries_Run; static int CallBack_HaltQueue(const PTASK_RECORD p) { if ( p->fpTask == Task_RunQueueEntry || p->fpTask == Task_SQLTimeout || p->fpTask == Task_SemaphoreTimeout) { // This is a @wait, timed Semaphore Task, or timed SQL Query. // const auto point = static_cast(p->arg_voidptr); if (que_want(point, Halt_Player_Target, Halt_Object_Target)) { // Accounting for pennies and queue quota. // dbref dbOwner = point->executor; if (!isPlayer(dbOwner)) { dbOwner = Owner(dbOwner); } if (dbOwner != Halt_Player_Run) { if (Halt_Player_Run != NOTHING) { giveto(Halt_Player_Run, mudconf.waitcost * Halt_Entries_Run); a_Queue(Halt_Player_Run, -Halt_Entries_Run); } Halt_Player_Run = dbOwner; Halt_Entries_Run = 0; } Halt_Entries++; Halt_Entries_Run++; if (p->fpTask == Task_SemaphoreTimeout) { (void)add_to(point->u.s.sem, -1, point->u.s.attr, nullptr); } for (auto& i : point->scr) { if (i) { RegRelease(i); i = nullptr; } } NamedRegsClear(point->named_scr); if (point->switch_token) { MEMFREE(point->switch_token); point->switch_token = nullptr; } if (point->iter_token) { MEMFREE(point->iter_token); point->iter_token = nullptr; } MEMFREE(point->text); point->text = nullptr; free_qentry(point); return IU_REMOVE_TASK; } } return IU_NEXT_TASK; } // ------------------------------------------------------------------ // // halt_que: Remove all queued commands that match (executor, object). // // (NOTHING, NOTHING) matches all queue entries. // (NOTHING, ) matches only queue entries run from . // (, NOTHING) matches only queue entries owned by . // (, ) matches only queue entries run from // and owned by . // int halt_que(const dbref executor, const dbref object) { Halt_Player_Target = executor; Halt_Object_Target = object; Halt_Entries = 0; Halt_Player_Run = NOTHING; Halt_Entries_Run = 0; // Process @wait, timed semaphores, and untimed semaphores. // scheduler.TraverseUnordered(CallBack_HaltQueue); if (Halt_Player_Run != NOTHING) { giveto(Halt_Player_Run, mudconf.waitcost * Halt_Entries_Run); a_Queue(Halt_Player_Run, -Halt_Entries_Run); Halt_Player_Run = NOTHING; } return Halt_Entries; } static uint64_t Halt_Pid_Target; static dbref Halt_Pid_Executor; static int Halt_Pid_Entries; static dbref Halt_Pid_Player_Run; static int Halt_Pid_Entries_Run; static int CallBack_HaltQueueByPid(const PTASK_RECORD p) { if ( p->fpTask == Task_RunQueueEntry || p->fpTask == Task_SQLTimeout || p->fpTask == Task_SemaphoreTimeout) { if (p->m_Ticket == Halt_Pid_Target) { const auto point = static_cast(p->arg_voidptr); // Ownership check: non-wizards can only halt their own entries. // if ( !Can_Halt(Halt_Pid_Executor) && Owner(point->executor) != Owner(Halt_Pid_Executor)) { return IU_NEXT_TASK; } // Accounting for pennies and queue quota. // dbref dbOwner = point->executor; if (!isPlayer(dbOwner)) { dbOwner = Owner(dbOwner); } if (dbOwner != Halt_Pid_Player_Run) { if (Halt_Pid_Player_Run != NOTHING) { giveto(Halt_Pid_Player_Run, mudconf.waitcost * Halt_Pid_Entries_Run); a_Queue(Halt_Pid_Player_Run, -Halt_Pid_Entries_Run); } Halt_Pid_Player_Run = dbOwner; Halt_Pid_Entries_Run = 0; } Halt_Pid_Entries++; Halt_Pid_Entries_Run++; if (p->fpTask == Task_SemaphoreTimeout) { (void)add_to(point->u.s.sem, -1, point->u.s.attr, nullptr); } for (auto& i : point->scr) { if (i) { RegRelease(i); i = nullptr; } } NamedRegsClear(point->named_scr); if (point->switch_token) { MEMFREE(point->switch_token); point->switch_token = nullptr; } if (point->iter_token) { MEMFREE(point->iter_token); point->iter_token = nullptr; } MEMFREE(point->text); point->text = nullptr; free_qentry(point); return IU_REMOVE_TASK; } } return IU_NEXT_TASK; } static int halt_que_pid(const dbref executor, const uint64_t pid) { Halt_Pid_Target = pid; Halt_Pid_Executor = executor; Halt_Pid_Entries = 0; Halt_Pid_Player_Run = NOTHING; Halt_Pid_Entries_Run = 0; scheduler.TraverseUnordered(CallBack_HaltQueueByPid); if (Halt_Pid_Player_Run != NOTHING) { giveto(Halt_Pid_Player_Run, mudconf.waitcost * Halt_Pid_Entries_Run); a_Queue(Halt_Pid_Player_Run, -Halt_Pid_Entries_Run); Halt_Pid_Player_Run = NOTHING; } return Halt_Pid_Entries; } // --------------------------------------------------------------------------- // do_halt: Command interface to halt_que. // void do_halt(const dbref executor, const dbref caller, dbref enactor, const int eval, int key, UTF8 *target, const UTF8 *cargs[], int ncargs) { UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); dbref executor_targ, obj_targ; if ((key & HALT_ALL) && !Can_Halt(executor)) { notify(executor, NOPERM_MESSAGE); return; } // Halt by PID. // if (key & HALT_PID) { if (key & HALT_ALL) { notify(executor, M_("Can’t specify /pid and /all")); return; } if (!target || !*target) { notify(executor, M_("You must specify a PID.")); return; } const UTF8 *p = target; while (mux_isspace(*p)) { p++; } if ('\0' == *p || '-' == *p) { notify(executor, M_("PID must be a non-negative integer.")); return; } if ('+' == *p) { p++; } uint64_t pid = 0; bool bHaveDigits = false; while (mux_isdigit(*p)) { bHaveDigits = true; const uint64_t digit = static_cast(*p - '0'); if (pid > (UINT64_MAX - digit) / 10U) { notify(executor, M_("PID is out of range.")); return; } pid = 10U * pid + digit; p++; } while (mux_isspace(*p)) { p++; } if ( !bHaveDigits || '\0' != *p) { notify(executor, M_("PID must be a non-negative integer.")); return; } const int numhalted = halt_que_pid(executor, pid); if (!Quiet(executor)) { if (0 == numhalted) { notify(Owner(executor), M_("No queue entry with that PID was found.")); } else { // #1661 / #1622: count goes to the catalogue, not English y/ies. // notify(Owner(executor), tprintf(MN_("%d queue entry removed.", "%d queue entries removed.", numhalted), numhalted)); } } return; } // Figure out what to halt. // if (!target || !*target) { obj_targ = NOTHING; if (key & HALT_ALL) { executor_targ = NOTHING; } else { executor_targ = Owner(executor); if (!isPlayer(executor)) { obj_targ = executor; } } } else { if (Can_Halt(executor)) { obj_targ = match_thing(executor, target); } else { obj_targ = match_controlled(executor, target); } if (!Good_obj(obj_targ)) { return; } if (key & HALT_ALL) { notify(executor, M_("Can’t specify a target and /all")); return; } if (isPlayer(obj_targ)) { executor_targ = obj_targ; obj_targ = NOTHING; } else { executor_targ = NOTHING; } } const int numhalted = halt_que(executor_targ, obj_targ); if (Quiet(executor)) { return; } // #1661 / #1622: count goes to the catalogue, not English y/ies. // notify(Owner(executor), tprintf(MN_("%d queue entry removed.", "%d queue entries removed.", numhalted), numhalted)); } static int Notify_Key; static int Notify_Num_Done; static int Notify_Num_Max; static int Notify_Sem; static int Notify_Attr; // NFY_DRAIN or NFY_NFYALL // static int CallBack_NotifySemaphoreDrainOrAll(PTASK_RECORD p) { if (p->fpTask == Task_SemaphoreTimeout) { // This represents a semaphore. // const auto point = static_cast(p->arg_voidptr); if ( point->u.s.sem == Notify_Sem && ( point->u.s.attr == Notify_Attr || !Notify_Attr)) { Notify_Num_Done++; if (NFY_DRAIN == (Notify_Key & NFY_MASK)) { // Discard the command // giveto(point->executor, mudconf.waitcost); a_Queue(Owner(point->executor), -1); for (auto& i : point->scr) { if (i) { RegRelease(i); i = nullptr; } } NamedRegsClear(point->named_scr); if (point->switch_token) { MEMFREE(point->switch_token); point->switch_token = nullptr; } if (point->iter_token) { MEMFREE(point->iter_token); point->iter_token = nullptr; } MEMFREE(point->text); point->text = nullptr; free_qentry(point); return IU_REMOVE_TASK; } else { // Allow the command to run. The priority may have been // PRIORITY_SUSPEND, so we need to change it. // if (isPlayer(point->enactor)) { p->iPriority = PRIORITY_PLAYER; } else { p->iPriority = PRIORITY_OBJECT; } p->ltaWhen.GetUTC(); p->fpTask = Task_RunQueueEntry; return IU_UPDATE_TASK; } } } return IU_NEXT_TASK; } // NFY_NFY // static int CallBack_NotifySemaphoreFirst(PTASK_RECORD p) { // If we've notified enough, exit. // if ( NFY_NFY == (Notify_Key & NFY_MASK) && Notify_Num_Done >= Notify_Num_Max) { return IU_DONE; } if (p->fpTask == Task_SemaphoreTimeout) { // This represents a semaphore. // const auto point = static_cast(p->arg_voidptr); if ( point->u.s.sem == Notify_Sem && ( point->u.s.attr == Notify_Attr || !Notify_Attr)) { Notify_Num_Done++; // Allow the command to run. The priority may have been // PRIORITY_SUSPEND, so we need to change it. // if (isPlayer(point->enactor)) { p->iPriority = PRIORITY_PLAYER; } else { p->iPriority = PRIORITY_OBJECT; } p->ltaWhen.GetUTC(); p->fpTask = Task_RunQueueEntry; return IU_UPDATE_TASK; } } return IU_NEXT_TASK; } // --------------------------------------------------------------------------- // nfy_que: Notify commands from the queue and perform or discard them. int nfy_que(dbref sem, int attr, int key, int count) { int cSemaphore = 1; if (attr) { int aflags; dbref aowner; LBuf str = LBuf_Adopt(atr_get("nfy_que.562", sem, attr, &aowner, &aflags)); cSemaphore = mux_atoi64(str); } Notify_Num_Done = 0; if (0 < cSemaphore) { Notify_Key = key; Notify_Sem = sem; Notify_Attr = attr; Notify_Num_Max = count; if (NFY_NFY == (key & NFY_MASK)) { scheduler.TraverseOrdered(CallBack_NotifySemaphoreFirst); } else { scheduler.TraverseUnordered(CallBack_NotifySemaphoreDrainOrAll); } } // Update the sem waiters count. // if (NFY_NFY == (key & NFY_MASK)) { (void)add_to(sem, -count, attr, nullptr); } else { if (!atr_clr(sem, attr)) { STARTLOG(LOG_PROBLEMS, "QUE", "SEMAPH"); log_printf(T("nfy_que(#%d/%d): failed to clear semaphore count."), sem, attr); ENDLOG; } } return Notify_Num_Done; } // --------------------------------------------------------------------------- // do_notify: Command interface to nfy_que void do_notify ( dbref executor, dbref caller, dbref enactor, int eval, int key, int nargs, UTF8 *what, UTF8 *count, const UTF8 *cargs[], int ncargs ) { UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(nargs); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); const UTF8 *obj = parse_to(&what, '/', 0); init_match(executor, obj, NOTYPE); match_everything(0); const dbref thing = noisy_match_result(); if (!Good_obj(thing)) { return; } if (!Controls(executor, thing) && !Link_ok(thing)) { notify(executor, NOPERM_MESSAGE); } else { int atr = A_SEMAPHORE; if ( what && what[0] != '\0') { const UTF8 *AttributeName = what; const int i = mkattr(executor, AttributeName); if (0 < i) { atr = i; if (atr != A_SEMAPHORE) { // Do they have permission to set this attribute? // ATTR *ap = static_cast(anum_get(atr)); if (!bCanSetAttr(executor, thing, ap)) { notify_quiet(executor, NOPERM_MESSAGE); return; } } } } int loccount; if ( count && count[0] != '\0') { loccount = mux_atoi64(count); } else { loccount = 1; } if (0 < loccount) { nfy_que(thing, atr, key, loccount); if ( !Quiet(executor) && !Quiet(thing) && !(key & NFY_QUIET)) { if (NFY_DRAIN == (key & NFY_MASK)) { notify_quiet(executor, M_("Drained.")); } else { notify_quiet(executor, M_("Notified.")); } } } } } // --------------------------------------------------------------------------- // setup_que: Set up a queue entry. // static BQUE *setup_que ( dbref executor, dbref caller, dbref enactor, int eval, UTF8 *command, int nargs, const UTF8 *args[], reg_ref *sargs[], NamedRegsMap *named_sargs = nullptr ) { // Can we run commands at all? // if (Halted(executor)) { return nullptr; } // Make sure executor can afford to do it. Keep the charged amount so // later OOM can refund exactly what payfor took (waitcost, plus at most // one machinecost penny). // int cost = mudconf.waitcost; if (mudconf.machinecost && RandomINT32(0, mudconf.machinecost-1) == 0) { cost++; } if (!payfor(executor, cost)) { notify(Owner(executor), M_("Not enough money to queue command.")); return nullptr; } // Wizards and their objs may queue up to db_top+1 cmds. Players are // limited to QUEUE_QUOTA. -mnp // int a = QueueMax(Owner(executor)); if (a < a_Queue(Owner(executor), 1)) { a_Queue(Owner(executor), -1); // #1080: refund the payfor charge — OOM paths already do this. // giveto(executor, cost); notify(Owner(executor), T("Run away objects: too many commands queued. Halted.")); halt_que(Owner(executor), NOTHING); // Halt also means no command execution allowed. // s_Halted(executor); return nullptr; } // We passed all the tests. // // Calculate the length of the save string. // size_t tlen = 0; static size_t nCommand; static size_t nLenEnv[NUM_ENV_VARS]; if (command) { nCommand = strlen(reinterpret_cast(command)) + 1; tlen = nCommand; } if (NUM_ENV_VARS < nargs) { nargs = NUM_ENV_VARS; } for (a = 0; a < nargs; a++) { if (args[a]) { nLenEnv[a] = strlen(reinterpret_cast(args[a])) + 1; tlen += nLenEnv[a]; } } // Create the queue entry and load the save string. // const auto tmp = alloc_qentry("setup_que.qblock"); if (nullptr == tmp) { STARTLOG(LOG_PROBLEMS, "QUE", "MEM"); log_printf(T("setup_que: out of memory allocating queue entry.")); ENDLOG; giveto(executor, cost); a_Queue(Owner(executor), -1); return nullptr; } tmp->comm = nullptr; tmp->text = nullptr; UTF8 *tptr = tmp->text = static_cast(MEMALLOC(tlen)); if (nullptr == tptr) { STARTLOG(LOG_PROBLEMS, "QUE", "MEM"); log_printf(T("setup_que: out of memory allocating %zu bytes for queue entry."), tlen); ENDLOG; free_qentry(tmp); giveto(executor, cost); a_Queue(Owner(executor), -1); return nullptr; } if (command) { memcpy(tptr, command, nCommand); tmp->comm = tptr; tptr += nCommand; } for (a = 0; a < nargs; a++) { if (args[a]) { memcpy(tptr, args[a], nLenEnv[a]); tmp->env[a] = tptr; tptr += nLenEnv[a]; } else { tmp->env[a] = nullptr; } } for ( ; a < NUM_ENV_VARS; a++) { tmp->env[a] = nullptr; } if (sargs) { for (a = 0; a < MAX_GLOBAL_REGS; a++) { tmp->scr[a] = sargs[a]; if (sargs[a]) { RegAddRef(sargs[a]); } } tmp->named_scr = NamedRegsCopy(named_sargs ? named_sargs : mudstate.named_regs); } else { for (a = 0; a < MAX_GLOBAL_REGS; a++) { tmp->scr[a] = nullptr; } tmp->named_scr = nullptr; } #if defined(STUB_SLAVE) tmp->iRow = mudstate.iRow; tmp->pResultsSet = mudstate.pResultsSet; if (nullptr != mudstate.pResultsSet) { mudstate.pResultsSet->AddRef(); } #endif // STUB_SLAVE // Load the rest of the queue block. // tmp->executor = executor; tmp->IsTimed = false; tmp->u.s.sem = NOTHING; tmp->u.s.attr = 0; tmp->enactor = enactor; tmp->caller = caller; tmp->eval = eval; tmp->nargs = nargs; tmp->switch_token = nullptr; tmp->iter_token = nullptr; tmp->iter_number = 0; return tmp; } // --------------------------------------------------------------------------- // wait_que: Add commands to the wait or semaphore queues. // Returns true if the command was queued, false if not (CF_INTERP off, // setup_que failed, etc.). Callers that mutate state before calling // (e.g. do_wait's semaphore add_to) must reverse on false. // bool wait_que ( const dbref executor, const dbref caller, dbref enactor, const int eval, const bool bTimed, const CLinearTimeAbsolute <aWhen, const dbref sem, const int attr, UTF8 *command, const int nargs, const UTF8 *args[], reg_ref *sargs[], NamedRegsMap *named_sargs, const UTF8 *iter_token, int iter_number, const UTF8 *switch_token ) { if (!(mudconf.control_flags & CF_INTERP)) { return false; } BQUE *tmp = setup_que(executor, caller, enactor, eval, command, nargs, args, sargs, named_sargs); if (!tmp) { return false; } // Copy iter/switch context if provided. // // Exact-size clones, not LBUFs: tokens are typically a few bytes // (a list element, a switch match), but a queued fan-out holds one // per entry until dispatch — 5000 queued @dolist entries held // 5000 x 32KB = 164MB of pool LBUFs for <=5-byte strings (queue // review, 2026-07-21). if (iter_token) { tmp->iter_token = StringClone(iter_token); tmp->iter_number = iter_number; } if (switch_token) { tmp->switch_token = StringClone(switch_token); } int iPriority; if (isPlayer(tmp->enactor)) { iPriority = PRIORITY_PLAYER; } else { iPriority = PRIORITY_OBJECT; } tmp->IsTimed = bTimed; tmp->waittime = ltaWhen; tmp->u.s.sem = sem; tmp->u.s.attr = attr; bool bDeferred = false; if (sem == NOTHING) { // Not a semaphore, so let it run it immediately or put it on // the wait queue. // if (tmp->IsTimed) { bDeferred = scheduler.DeferTask(tmp->waittime, iPriority, Task_RunQueueEntry, tmp, 0); } else { bDeferred = scheduler.DeferImmediateTask(iPriority, Task_RunQueueEntry, tmp, 0); } } else { if (!tmp->IsTimed) { // In this case, the timeout task below will never run, // but it allows us to manage all semaphores together in // the same data structure. // iPriority = PRIORITY_SUSPEND; } bDeferred = scheduler.DeferTask(tmp->waittime, iPriority, Task_SemaphoreTimeout, tmp, 0); } // #1871: if the scheduler could not hold a TASK_RECORD, the command is // not queued — free the BQUE and reverse payfor/quota so a silent OOM // does not strand accounting until restart. // if (!bDeferred) { STARTLOG(LOG_PROBLEMS, "QUE", "MEM"); log_printf(T("wait_que: scheduler refused task; dropping queue entry.")); ENDLOG; giveto(executor, mudconf.waitcost); a_Queue(Owner(executor), -1); for (auto& i : tmp->scr) { if (i) { RegRelease(i); i = nullptr; } } NamedRegsClear(tmp->named_scr); #if defined(STUB_SLAVE) if (nullptr != tmp->pResultsSet) { tmp->pResultsSet->Release(); tmp->pResultsSet = nullptr; } #endif // STUB_SLAVE if (tmp->switch_token) { MEMFREE(tmp->switch_token); tmp->switch_token = nullptr; } if (tmp->iter_token) { MEMFREE(tmp->iter_token); tmp->iter_token = nullptr; } MEMFREE(tmp->text); tmp->text = nullptr; free_qentry(tmp); return false; } return true; } #if defined(STUB_SLAVE) bool QueryComplete_bDone = false; uint32_t QueryComplete_hQuery = 0; CResultsSet *QueryComplete_prsResultsSet = nullptr; static int CallBack_QueryComplete(PTASK_RECORD p) { if (QueryComplete_bDone) { return IU_DONE; } if (Task_SQLTimeout == p->fpTask) { // This represents a query. // BQUE *point = static_cast(p->arg_voidptr); if (point->u.hQuery == QueryComplete_hQuery) { p->iPriority = PRIORITY_OBJECT; p->ltaWhen.GetUTC(); p->fpTask = Task_RunQueueEntry; point->u.s.sem = NOTHING; point->u.s.attr = 0; QueryComplete_prsResultsSet->AddRef(); point->pResultsSet = QueryComplete_prsResultsSet; point->iRow = RS_TOP; QueryComplete_bDone = true; return IU_UPDATE_TASK; } } return IU_NEXT_TASK; } // This can be called as a side-effect of talking with the stubslave. // Therefore, we only want to raise the priority of the corresponding take // from SUSPENDED to OBJECT. // void query_complete(uint32_t hQuery, uint32_t iError, CResultsSet *prsResultsSet) { if (nullptr != prsResultsSet) { prsResultsSet->SetError(iError); } QueryComplete_bDone = false; QueryComplete_hQuery = hQuery; QueryComplete_prsResultsSet = prsResultsSet; scheduler.TraverseUnordered(CallBack_QueryComplete); QueryComplete_prsResultsSet = nullptr; } #endif // STUB_SLAVE // --------------------------------------------------------------------------- // sql_que: Add commands to the sql queue. // void sql_que ( const dbref executor, const dbref caller, const dbref enactor, const int eval, const dbref thing, const int attr, const UTF8 *dbname, const UTF8 *query, int nargs, const UTF8 *args[], reg_ref *sargs[] ) { static uint32_t next_handle = 0; if ( !(mudconf.control_flags & CF_INTERP) || nullptr == mudstate.pIQueryControl) { return; } ATTR *pattr = atr_num(attr); if (nullptr == pattr) { return; } UTF8 mbuf[MBUF_SIZE]; mux_sprintf(mbuf, MBUF_SIZE, T("@trigger #%d/%s"), thing, pattr->name); BQUE *tmp = setup_que(executor, caller, enactor, eval, mbuf, nargs, args, sargs); if (!tmp) { return; } const uint32_t hQuery = next_handle++; tmp->u.hQuery = hQuery; // Hold the entry until Query completes (or fails). waittime is not // used as a real SQL timeout (Task_SQLTimeout just runs the entry), // but must be initialized before DeferTask. // tmp->waittime.GetUTC(); if (!scheduler.DeferTask(tmp->waittime, PRIORITY_SUSPEND, Task_SQLTimeout, tmp, 0)) { // #1871: same rollback as wait_que / Query-reject path. // STARTLOG(LOG_PROBLEMS, "QUE", "MEM"); log_printf(T("sql_que: scheduler refused task; dropping queue entry.")); ENDLOG; giveto(executor, mudconf.waitcost); a_Queue(Owner(executor), -1); for (auto& i : tmp->scr) { if (i) { RegRelease(i); i = nullptr; } } NamedRegsClear(tmp->named_scr); #if defined(STUB_SLAVE) if (nullptr != tmp->pResultsSet) { tmp->pResultsSet->Release(); tmp->pResultsSet = nullptr; } #endif // STUB_SLAVE if (tmp->switch_token) { MEMFREE(tmp->switch_token); tmp->switch_token = nullptr; } if (tmp->iter_token) { MEMFREE(tmp->iter_token); tmp->iter_token = nullptr; } MEMFREE(tmp->text); tmp->text = nullptr; free_qentry(tmp); return; } const MUX_RESULT mr = mudstate.pIQueryControl->Query(hQuery, dbname, query); if (MUX_FAILED(mr)) { // Query rejected: cancel the hold, free the BQUE, and refund // money/quota exactly as a successful dequeue would. // scheduler.CancelTask(Task_SQLTimeout, tmp, 0); giveto(executor, mudconf.waitcost); a_Queue(Owner(executor), -1); for (auto& i : tmp->scr) { if (i) { RegRelease(i); i = nullptr; } } NamedRegsClear(tmp->named_scr); #if defined(STUB_SLAVE) if (nullptr != tmp->pResultsSet) { tmp->pResultsSet->Release(); tmp->pResultsSet = nullptr; } #endif // STUB_SLAVE if (tmp->switch_token) { MEMFREE(tmp->switch_token); tmp->switch_token = nullptr; } if (tmp->iter_token) { MEMFREE(tmp->iter_token); tmp->iter_token = nullptr; } MEMFREE(tmp->text); tmp->text = nullptr; free_qentry(tmp); } } // --------------------------------------------------------------------------- // do_wait: Command interface to wait_que // void do_wait ( const dbref executor, const dbref caller, const dbref enactor, const int eval, int key, int nargs, UTF8 *event, UTF8 *cmd, const UTF8 *cargs[], int ncargs ) { UNUSED_PARAMETER(nargs); CLinearTimeAbsolute ltaWhen; CLinearTimeDelta ltd; // If arg1 is all numeric, do simple (non-sem) timed wait. // if (is_rational(event)) { if (key & WAIT_UNTIL) { ltaWhen.SetSecondsString(event); } else { ltaWhen.GetUTC(); ltd.SetSecondsString(event); ltaWhen += ltd; } wait_que(executor, caller, enactor, eval, true, ltaWhen, NOTHING, 0, cmd, ncargs, cargs, mudstate.global_regs); return; } // Semaphore wait with optional timeout. // const UTF8 *what = parse_to(&event, '/', 0); init_match(executor, what, NOTYPE); match_everything(0); dbref thing = noisy_match_result(); if (!Good_obj(thing)) { return; } else if (!Controls(executor, thing) && !Link_ok(thing)) { notify(executor, NOPERM_MESSAGE); } else { // Get timeout, default 0. // int atr = A_SEMAPHORE; bool bTimed = false; if (event && *event) { if (is_rational(event)) { if (key & WAIT_UNTIL) { ltaWhen.SetSecondsString(event); } else { ltaWhen.GetUTC(); ltd.SetSecondsString(event); ltaWhen += ltd; } bTimed = true; } else { const UTF8 *EventAttributeName = reinterpret_cast(event); ATTR *ap = atr_str(EventAttributeName); if (!ap) { atr = mkattr(executor, EventAttributeName); if (atr <= 0) { notify_quiet(executor, M_("Invalid attribute.")); return; } ap = atr_num(atr); } else { atr = ap->number; } if (!bCanSetAttr(executor, thing, ap)) { notify_quiet(executor, NOPERM_MESSAGE); return; } } } // Don't raise the semaphore if the queue is disabled — wait_que // would silently no-op and leave the count elevated. // if (!(mudconf.control_flags & CF_INTERP)) { return; } const dbref sem = thing; int num = 0; if (!add_to(thing, 1, atr, &num)) { notify_quiet(executor, M_("Semaphore update failed.")); return; } if (num <= 0) { // Thing over-notified, run the command immediately. // thing = NOTHING; bTimed = false; } if (!wait_que(executor, caller, enactor, eval, bTimed, ltaWhen, thing, atr, cmd, ncargs, cargs, mudstate.global_regs)) { // setup_que failed after we claimed the semaphore; reverse. // (void)add_to(sem, -1, atr, nullptr); } } } // --------------------------------------------------------------------------- // do_query: Command interface to sql_que // void do_query ( const dbref executor, const dbref caller, dbref enactor, const int eval, int key, const int nargs, UTF8 *dbref_attr, UTF8 *dbname_query, const UTF8 *cargs[], int ncargs ) { UNUSED_PARAMETER(nargs); if (nullptr == mudstate.pIQueryControl) { notify_quiet(executor, M_("Query server is not available.")); return; } if (key & QUERY_SQL) { // SQL Query. // dbref thing; ATTR *pattr; if (!( parse_attrib(executor, dbref_attr, &thing, &pattr) && nullptr != pattr)) { notify_quiet(executor, M_("No match.")); return; } if (!Controls(executor, thing)) { notify_quiet(executor, T(NOPERM_MESSAGE)); return; } UTF8 *pQuery = dbname_query; const UTF8 *pDBName = parse_to(&pQuery, '/', 0); if (nullptr == pQuery) { notify(executor, M_("QUERY: No Query.")); return; } sql_que(executor, caller, enactor, eval, thing, pattr->number, pDBName, pQuery, ncargs, cargs, mudstate.global_regs); } else { notify_quiet(executor, M_("At least one query option is required.")); } } static CLinearTimeAbsolute Show_lsaNow; static int Total_SystemTasks; static int Total_RunQueueEntry; static int Shown_RunQueueEntry; static int Total_SemaphoreTimeout; static int Shown_SemaphoreTimeout; static dbref Show_Player_Target; static dbref Show_Object_Target; static int Show_Key; static dbref Show_Player; static int Show_bFirstLine; int Total_SQLTimeout; int Shown_SQLTimeout; static int CallBack_ShowDispatches(const PTASK_RECORD p) { Total_SystemTasks++; CLinearTimeDelta ltd = p->ltaWhen - Show_lsaNow; if (p->fpTask == dispatch_DatabaseDump) { notify(Show_Player, tprintf(M_("[%d]auto-@dump"), ltd.ReturnSeconds())); } else if (p->fpTask == dispatch_FreeListReconstruction) { notify(Show_Player, tprintf(M_("[%d]auto-@dbck"), ltd.ReturnSeconds())); } else if (p->fpTask == dispatch_IdleCheck) { notify(Show_Player, tprintf(M_("[%d]Check for idle players"), ltd.ReturnSeconds())); } else if (p->fpTask == dispatch_CheckEvents) { notify(Show_Player, tprintf(M_("[%d]Test for @daily time"), ltd.ReturnSeconds())); } else if (p->fpTask == dispatch_KeepAlive) { notify(Show_Player, tprintf(M_("[%d]Keep Alive"), ltd.ReturnSeconds())); } else if (p->fpTask == dispatch_CacheTick) { notify(Show_Player, tprintf(M_("[%d]Database cache tick"), ltd.ReturnSeconds())); } else if (p->fpTask == Task_ProcessCommand) { notify(Show_Player, tprintf(M_("[%d]Further command quota"), ltd.ReturnSeconds())); } else { Total_SystemTasks--; } return IU_NEXT_TASK; } static void ShowPsLine(const BQUE *tmp, const uint64_t pid) { UTF8 *bufp = unparse_object(Show_Player, tmp->executor, false); if (tmp->IsTimed && Good_obj(tmp->u.s.sem)) { CLinearTimeDelta ltd = tmp->waittime - Show_lsaNow; notify(Show_Player, tprintf(M_("[PID %llu][#%d/%d]%s:%s"), static_cast(pid), tmp->u.s.sem, ltd.ReturnSeconds(), bufp, tmp->comm)); } else if (tmp->IsTimed) { CLinearTimeDelta ltd = tmp->waittime - Show_lsaNow; notify(Show_Player, tprintf(M_("[PID %llu][%d]%s:%s"), static_cast(pid), ltd.ReturnSeconds(), bufp, tmp->comm)); } else if (Good_obj(tmp->u.s.sem)) { notify(Show_Player, tprintf(M_("[PID %llu][#%d]%s:%s"), static_cast(pid), tmp->u.s.sem, bufp, tmp->comm)); } else { notify(Show_Player, tprintf(M_("[PID %llu]%s:%s"), static_cast(pid), bufp, tmp->comm)); } UTF8 *bp = bufp; if (Show_Key == PS_LONG) { for (int i = 0; i < tmp->nargs; i++) { if (tmp->env[i] != nullptr) { safe_str(T("; Arg"), bufp, &bp); safe_chr(static_cast(i + '0'), bufp, &bp); safe_str(T("=‘"), bufp, &bp); safe_str(tmp->env[i], bufp, &bp); safe_str(T("’"), bufp, &bp); } } *bp = '\0'; bp = unparse_object(Show_Player, tmp->enactor, false); notify(Show_Player, tprintf(M_(" Enactor: %s%s"), bp, bufp)); free_lbuf(bp); } free_lbuf(bufp); } static int CallBack_ShowWait(PTASK_RECORD p) { if (p->fpTask != Task_RunQueueEntry) { return IU_NEXT_TASK; } Total_RunQueueEntry++; const auto tmp = static_cast(p->arg_voidptr); if (que_want(tmp, Show_Player_Target, Show_Object_Target)) { Shown_RunQueueEntry++; if (Show_Key == PS_SUMM) { return IU_NEXT_TASK; } if (Show_bFirstLine) { notify(Show_Player, M_("----- Wait Queue -----")); Show_bFirstLine = false; } ShowPsLine(tmp, p->m_Ticket); } return IU_NEXT_TASK; } static int CallBack_ShowSemaphore(PTASK_RECORD p) { if (p->fpTask != Task_SemaphoreTimeout) { return IU_NEXT_TASK; } Total_SemaphoreTimeout++; const auto tmp = static_cast(p->arg_voidptr); if (que_want(tmp, Show_Player_Target, Show_Object_Target)) { Shown_SemaphoreTimeout++; if (Show_Key == PS_SUMM) { return IU_NEXT_TASK; } if (Show_bFirstLine) { notify(Show_Player, M_("----- Semaphore Queue -----")); Show_bFirstLine = false; } ShowPsLine(tmp, p->m_Ticket); } return IU_NEXT_TASK; } int CallBack_ShowSQLQueries(PTASK_RECORD p) { if (p->fpTask != Task_SQLTimeout) { return IU_NEXT_TASK; } Total_SQLTimeout++; const auto tmp = static_cast(p->arg_voidptr); if (que_want(tmp, Show_Player_Target, Show_Object_Target)) { Shown_SQLTimeout++; if (Show_Key == PS_SUMM) { return IU_NEXT_TASK; } if (Show_bFirstLine) { notify(Show_Player, M_("----- SQL Queries -----")); Show_bFirstLine = false; } ShowPsLine(tmp, p->m_Ticket); } return IU_NEXT_TASK; } // --------------------------------------------------------------------------- // do_ps: tell executor what commands they have pending in the queue // void do_ps(const dbref executor, const dbref caller, const dbref enactor, const int eval, int key, UTF8 *target, const UTF8 *cargs[], const int ncargs) { UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); dbref executor_targ, obj_targ; // Figure out what to list the queue for. // if ((key & PS_ALL) && !See_Queue(executor)) { notify(executor, NOPERM_MESSAGE); return; } if (!target || !*target) { obj_targ = NOTHING; if (key & PS_ALL) { executor_targ = NOTHING; } else { executor_targ = Owner(executor); if (!isPlayer(executor)) { obj_targ = executor; } } } else { executor_targ = Owner(executor); obj_targ = match_controlled(executor, target); if (obj_targ == NOTHING) { return; } if (key & PS_ALL) { notify(executor, M_("Can’t specify a target and /all")); return; } if (isPlayer(obj_targ)) { executor_targ = obj_targ; obj_targ = NOTHING; } else { // Scope the listing to the object alone (any owner), matching // @halt; match_controlled already verified permission, so a // controller can inspect an object owned by another player. // executor_targ = NOTHING; } } key = key & ~PS_ALL; switch (key) { case PS_BRIEF: case PS_SUMM: case PS_LONG: break; default: notify(executor, M_("Illegal combination of switches.")); return; } Show_lsaNow.GetUTC(); Total_SystemTasks = 0; Total_RunQueueEntry = 0; Shown_RunQueueEntry = 0; Total_SemaphoreTimeout = 0; Shown_SemaphoreTimeout = 0; Total_SQLTimeout = 0; Shown_SQLTimeout = 0; Show_Player_Target = executor_targ; Show_Object_Target = obj_targ; Show_Key = key; Show_Player = executor; Show_bFirstLine = true; scheduler.TraverseOrdered(CallBack_ShowWait); Show_bFirstLine = true; scheduler.TraverseOrdered(CallBack_ShowSemaphore); Show_bFirstLine = true; scheduler.TraverseOrdered(CallBack_ShowSQLQueries); if (Wizard(executor)) { notify(executor, M_("----- System Queue -----")); scheduler.TraverseOrdered(CallBack_ShowDispatches); } // Display stats. // UTF8* bufp = alloc_mbuf("do_ps"); mux_sprintf(bufp, MBUF_SIZE, T("Totals: Wait Queue...%d/%d Semaphores...%d/%d SQL %d/%d"), Shown_RunQueueEntry, Total_RunQueueEntry, Shown_SemaphoreTimeout, Total_SemaphoreTimeout, Shown_SQLTimeout, Total_SQLTimeout); notify(executor, bufp); if (Wizard(executor)) { mux_sprintf(bufp, MBUF_SIZE, T(" System Tasks.....%d"), Total_SystemTasks); notify(executor, bufp); } free_mbuf(bufp); } static CLinearTimeDelta ltdWarp; static int CallBack_Warp(PTASK_RECORD p) { if ( p->fpTask == Task_RunQueueEntry || p->fpTask == Task_SQLTimeout || p->fpTask == Task_SemaphoreTimeout) { const auto point = static_cast(p->arg_voidptr); if (point->IsTimed) { point->waittime -= ltdWarp; p->ltaWhen -= ltdWarp; return IU_UPDATE_TASK; } } return IU_NEXT_TASK; } // --------------------------------------------------------------------------- // do_queue: Queue management // void do_queue(const dbref executor, const dbref caller, const dbref enactor, const int eval, const int key, UTF8 *arg, const UTF8 *cargs[], const int ncargs) { UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); if (key == QUEUE_KICK) { // Parse full width, then clamp for RunTasks(int) (#1402). // int64_t wanted = mux_atoi64(arg); int i; if (wanted > INT_MAX) { i = INT_MAX; } else if (wanted < 0) { i = 0; } else { i = static_cast(wanted); } const int save_minPriority = scheduler.GetMinPriority(); if (save_minPriority <= PRIORITY_CF_DEQUEUE_DISABLED) { notify(executor, M_("Warning: automatic dequeueing is disabled.")); scheduler.SetMinPriority(PRIORITY_CF_DEQUEUE_ENABLED); } CLinearTimeAbsolute lsaNow; lsaNow.GetUTC(); scheduler.ReadyTasks(lsaNow); const int ncmds = scheduler.RunTasks(i); scheduler.SetMinPriority(save_minPriority); if (!Quiet(executor)) { notify(executor, tprintf(M_("%d commands processed."), ncmds)); } } else if (key == QUEUE_WARP) { // SetSeconds takes int64_t (UnderlyingTickType) (#1402). // const int64_t iWarp = mux_atoi64(arg); ltdWarp.SetSeconds(iWarp); if (scheduler.GetMinPriority() <= PRIORITY_CF_DEQUEUE_DISABLED) { notify(executor, M_("Warning: automatic dequeueing is disabled.")); } scheduler.TraverseUnordered(CallBack_Warp); if (Quiet(executor)) { return; } if (0 < iWarp) { notify(executor, tprintf(M_("WaitQ timer advanced %d seconds."), iWarp)); } else if (iWarp < 0) { notify(executor, tprintf(M_("WaitQ timer set back %d seconds."), iWarp)); } else { notify(executor, M_("Object queue appended to player queue.")); } } }