mirror of
https://github.com/brazilofmux/tinymux
synced 2026-08-13 00:23:11 -04:00
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.
2279 lines
62 KiB
C++
2279 lines
62 KiB
C++
/*! \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_("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<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_("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<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 <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<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_("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<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."));
|
||
}
|
||
}
|
||
}
|