tinymux/mux/modules/engine/cque.cpp
Stephen Dennis 9a4de32dcb fix(queue,db): scheduler enqueue success and flatfile write failures (#1871 #1869)
DeferTask/DeferImmediateTask return bool (nothrow OOM); wait_que and
sql_que free the BQUE and refund quota/waitcost when enqueue fails.
db_write_object reports stream status via ferror; db_write returns -1
on I/O failure after header/object/end/flush checks, and dump/dbconvert
callers refuse to publish a truncated flatfile.
2026-07-31 09:09:18 -06:00

2279 lines
62 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*! \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<int64_t*>(&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<long long>(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<int>(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<BQUE*>(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<const UTF8**>(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<BQUE*>(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<BQUE*>(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<BQUE*>(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, <object>) matches only queue entries run from <object>.
// (<executor>, NOTHING) matches only queue entries owned by <executor>.
// (<executor>, <object>) matches only queue entries run from <objects>
// and owned by <executor>.
//
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<BQUE*>(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_("Cant 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<uint64_t>(*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_("Cant 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<BQUE*>(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<BQUE*>(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<ATTR *>(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<char *>(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<const char *>(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<UTF8*>(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 &ltaWhen,
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<BQUE *>(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<UTF8 *>(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<unsigned long long>(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<unsigned long long>(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<unsigned long long>(pid), tmp->u.s.sem, bufp,
tmp->comm));
}
else
{
notify(Show_Player, tprintf(M_("[PID %llu]%s:%s"),
static_cast<unsigned long long>(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<UTF8>(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<BQUE*>(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<BQUE*>(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<BQUE*>(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_("Cant 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<BQUE*>(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<int>(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."));
}
}
}