mirror of
https://github.com/brazilofmux/tinymux
synced 2026-08-13 00:23:11 -04:00
Regenerate pot/xx/ko after Phase 4 C* (drop blob headers; add per-label msgids). Row right-just must use RightJustifyNumber — mux_vsnprintf does not implement %* width (#1429), so the filed %*d/%*lld path would echo literally and drop the rest of each stats line.
696 lines
23 KiB
C++
696 lines
23 KiB
C++
/*! \file alloc.cpp
|
|
* \brief Memory Allocation Subsystem.
|
|
*
|
|
* The functions here manage pools of often-used buffers of fixed-size. It
|
|
* adds value by greatly reducing the number and strength of calls to the
|
|
* underlying platform's memory management. Headers and footers detect
|
|
* misuse of buffers by callers.
|
|
*
|
|
* Buffer tracking and freelists use std::vector instead of intrusive
|
|
* linked lists, eliminating the next/nxtfree pointers from POOLHDR.
|
|
*/
|
|
|
|
#include "copyright.h"
|
|
#include "autoconf.h"
|
|
#include "config.h"
|
|
#include "core.h"
|
|
#include "modules.h"
|
|
#include "mux_table.h"
|
|
|
|
#include <algorithm>
|
|
#include <unordered_set>
|
|
#include <vector>
|
|
|
|
// libmux.so cannot access mudconf directly (it lives in engine.so).
|
|
// This flag mirrors g_paranoid_alloc; the engine sets it after
|
|
// loading configuration via g_paranoid_alloc in alloc.h (future work:
|
|
// libmux config broadcast). For now, default to false.
|
|
//
|
|
bool g_paranoid_alloc = false;
|
|
|
|
// Pool memory footprint budget (#pool-oom). See alloc.h.
|
|
size_t g_pool_limit_bytes = 0;
|
|
size_t g_pool_system_bytes = 0;
|
|
|
|
// Engine → driver live config push. See alloc.h.
|
|
DRIVER_CONFIG_SYNC_FN g_driver_config_sync_fn = nullptr;
|
|
|
|
// Account bytes just taken from the system on a pool slow-path alloc and, if
|
|
// they push the footprint past the budget, trip the per-command abort so the
|
|
// current (runaway) command unwinds and frees its buffers back to the freelist
|
|
// — a graceful degrade instead of a fatal OutOfMemory.
|
|
//
|
|
// Policy: the budget is a permanent soft ceiling on system growth. Pool
|
|
// freelist returns never reduce g_pool_system_bytes (buffers stay owned by
|
|
// the pool), so once past the limit the process stays over budget until
|
|
// restart or the admin raises pool_memory_limit. Log once per breach; do
|
|
// not re-arm the warning from freelist activity (there is none that lowers
|
|
// the counter). After the first breach, continue to trip alarm_clock on
|
|
// further slow-path growth so subsequent runaway allocs still unwind rather
|
|
// than growing without bound.
|
|
//
|
|
static void pool_account_system(size_t nBytes)
|
|
{
|
|
static bool warned = false;
|
|
g_pool_system_bytes += nBytes;
|
|
if (0 != g_pool_limit_bytes)
|
|
{
|
|
if (g_pool_system_bytes > g_pool_limit_bytes)
|
|
{
|
|
alarm_clock.alarmed.store(true, std::memory_order_relaxed);
|
|
if (!warned)
|
|
{
|
|
warned = true;
|
|
mux_fprintf(stderr,
|
|
T("Pool memory budget exceeded (%zu > %zu bytes); aborting "
|
|
"the current command to protect the server. Further "
|
|
"slow-path growth will keep aborting until the budget "
|
|
"is raised or the process restarts." ENDLINE),
|
|
g_pool_system_bytes, g_pool_limit_bytes);
|
|
}
|
|
}
|
|
// Re-arm only when the admin raises the limit past the current
|
|
// footprint (g_pool_system_bytes never shrinks).
|
|
else if (warned && g_pool_system_bytes <= g_pool_limit_bytes)
|
|
{
|
|
warned = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Output callback for @list buffers — set by engine at startup.
|
|
//
|
|
ALLOC_NOTIFY_FN g_alloc_notify_fn = nullptr;
|
|
|
|
/*! \brief Per-buffer header to manage and organize client allocation.
|
|
*
|
|
* The POOLHDR structure precedes a client area which must be properly
|
|
* aligned to avoid faults when the client accesses structure members within
|
|
* the client area. 64-bit alignment should be sufficient.
|
|
*
|
|
* The magicnum and pool_size are chosen when the pool is initialized.
|
|
* buf_tag comes from the client so that buffers can be associated with the
|
|
* places that allocated them.
|
|
*/
|
|
|
|
typedef struct pool_header
|
|
{
|
|
unsigned int magicnum; // For consistency check
|
|
size_t pool_size; // For consistency check
|
|
union
|
|
{
|
|
const UTF8 *buf_tag; // Debugging/trace tag
|
|
uint64_t align; // Alignment padding
|
|
} u;
|
|
} POOLHDR;
|
|
|
|
/*! \brief Per-buffer footer to catch buffer overruns.
|
|
*
|
|
* The POOLFTR structure helps detect when a client has written beyond the
|
|
* bounds of the buffer.
|
|
*/
|
|
|
|
typedef struct pool_footer
|
|
{
|
|
unsigned int magicnum; // For consistency check
|
|
} POOLFTR;
|
|
|
|
/*! \brief Per-pool structure containing statistics and vector-based lists.
|
|
*
|
|
* The freelist is a stack (vector) of raw allocation pointers.
|
|
* all_buffers tracks every allocation for diagnostics and cleanup.
|
|
*/
|
|
|
|
typedef struct pooldata
|
|
{
|
|
size_t pool_client_size; // Size in bytes of a buffer as seen by client.
|
|
size_t pool_alloc_size; // Size as allocated from system.
|
|
unsigned int poolmagic; // Magic number specific to this pool
|
|
std::vector<char *> free_stack; // Free buffers (stack — push/pop from back)
|
|
std::vector<char *> all_buffers;// All allocated raw blocks (for diagnostics)
|
|
uint64_t tot_alloc; // Total buffers allocated
|
|
uint64_t num_alloc; // Number of buffers currently allocated
|
|
uint64_t max_alloc; // Max # buffers allocated at one time
|
|
uint64_t num_lost; // Buffers lost due to corruption
|
|
} POOL;
|
|
|
|
static POOL pools[NUM_POOLS];
|
|
static const UTF8 *poolnames[] =
|
|
{
|
|
T("Lbufs"),
|
|
T("Sbufs"),
|
|
T("Mbufs"),
|
|
T("Bools"),
|
|
T("Descs"),
|
|
T("Qentries"),
|
|
T("Pcaches")
|
|
};
|
|
|
|
/*! \brief Initialize a buffer pool.
|
|
*
|
|
* This is done once. The client size, magic, and allocation size are chosen
|
|
* at this time. After this initialization, allocations can be done.
|
|
*
|
|
* \param poolnum An integer uniquely indicating which pool.
|
|
* \param poolsize The size of the client area this pool supports.
|
|
*/
|
|
void pool_init(int poolnum, int poolsize)
|
|
{
|
|
pools[poolnum].pool_client_size = poolsize;
|
|
pools[poolnum].pool_alloc_size = poolsize + sizeof(POOLHDR) + sizeof(POOLFTR);
|
|
mux_assert(pools[poolnum].pool_client_size < pools[poolnum].pool_alloc_size);
|
|
pools[poolnum].poolmagic = CRC32_ProcessInteger2(poolnum, poolsize);
|
|
pools[poolnum].free_stack.clear();
|
|
pools[poolnum].all_buffers.clear();
|
|
pools[poolnum].tot_alloc = 0;
|
|
pools[poolnum].num_alloc = 0;
|
|
pools[poolnum].max_alloc = 0;
|
|
pools[poolnum].num_lost = 0;
|
|
}
|
|
|
|
/*! \brief Helper function for logging pool errors.
|
|
*/
|
|
static void pool_err
|
|
(
|
|
const UTF8 *logsys,
|
|
int logflag,
|
|
int poolnum,
|
|
const UTF8 *tag,
|
|
POOLHDR* ph,
|
|
const UTF8 *action,
|
|
const UTF8 *reason,
|
|
const UTF8 *file,
|
|
const int line
|
|
)
|
|
{
|
|
UNUSED_PARAMETER(logflag);
|
|
|
|
mux_fprintf(stderr, T("%s %s[%d] (tag %s) %s in %s line %d at %p." ENDLINE),
|
|
logsys, action, pools[poolnum].pool_client_size, tag, reason,
|
|
file, line, ph);
|
|
}
|
|
|
|
/*! \brief Validates the buffers in the all_buffers list.
|
|
*
|
|
* Walks all allocated buffers and checks that magic numbers are correct.
|
|
* Reports errors but does not unlink or leak buffers — that strategy was
|
|
* a relic of the intrusive-list era.
|
|
*/
|
|
static void pool_vfy
|
|
(
|
|
int poolnum,
|
|
const UTF8 *tag,
|
|
const UTF8 *file,
|
|
const int line
|
|
)
|
|
{
|
|
const size_t psize = pools[poolnum].pool_client_size;
|
|
for (char *raw : pools[poolnum].all_buffers)
|
|
{
|
|
auto ph = reinterpret_cast<POOLHDR *>(raw);
|
|
auto h = raw + sizeof(POOLHDR);
|
|
auto pf = reinterpret_cast<POOLFTR *>(h + pools[poolnum].pool_client_size);
|
|
|
|
if (ph->magicnum != pools[poolnum].poolmagic)
|
|
{
|
|
pool_err(T("BUG"), LOG_ALWAYS, poolnum, tag, ph, T("Verify"),
|
|
T("header corrupted"), file, line);
|
|
continue;
|
|
}
|
|
|
|
if (pf->magicnum != pools[poolnum].poolmagic)
|
|
{
|
|
pool_err(T("BUG"), LOG_ALWAYS, poolnum, tag, ph, T("Verify"),
|
|
T("footer corrupted"), file, line);
|
|
pf->magicnum = pools[poolnum].poolmagic;
|
|
}
|
|
|
|
if (ph->pool_size != psize)
|
|
{
|
|
pool_err(T("BUG"), LOG_ALWAYS, poolnum, tag, ph,
|
|
T("Verify"), T("header has incorrect size"), file, line);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void pool_check(const UTF8 *tag, const UTF8 *file, const int line)
|
|
{
|
|
for (int i = 0; i < NUM_POOLS; i++)
|
|
{
|
|
pool_vfy(i, tag, file, line);
|
|
}
|
|
}
|
|
|
|
UTF8 *pool_alloc(int poolnum, const UTF8 *tag, const UTF8 *file, const int line)
|
|
{
|
|
if (g_paranoid_alloc)
|
|
{
|
|
pool_check(tag, file, line);
|
|
}
|
|
|
|
UTF8 *p;
|
|
POOLHDR *ph;
|
|
POOLFTR *pf;
|
|
|
|
// Walk freelist; drop only corrupt entries rather than clearing the
|
|
// entire freelist (old path leaked every still-valid free buffer —
|
|
// Pass H1 residual #1290).
|
|
//
|
|
while (!pools[poolnum].free_stack.empty())
|
|
{
|
|
char *raw = pools[poolnum].free_stack.back();
|
|
pools[poolnum].free_stack.pop_back();
|
|
ph = reinterpret_cast<POOLHDR *>(raw);
|
|
p = reinterpret_cast<UTF8 *>(raw + sizeof(POOLHDR));
|
|
pf = reinterpret_cast<POOLFTR *>(p + pools[poolnum].pool_client_size);
|
|
|
|
if (ph->magicnum != pools[poolnum].poolmagic)
|
|
{
|
|
pool_err(T("BUG"), LOG_ALWAYS, poolnum, tag, ph, T("Alloc"),
|
|
T("corrupted buffer header on freelist"), file, line);
|
|
pools[poolnum].num_lost++;
|
|
// raw is abandoned (still tracked in all_buffers for diagnostics).
|
|
continue;
|
|
}
|
|
|
|
// Check for corrupted footer, just report and fix it.
|
|
//
|
|
if (pf->magicnum != pools[poolnum].poolmagic)
|
|
{
|
|
pool_err(T("BUG"), LOG_ALWAYS, poolnum, tag, ph, T("Alloc"),
|
|
T("corrupted buffer footer"), file, line);
|
|
pf->magicnum = pools[poolnum].poolmagic;
|
|
}
|
|
|
|
ph->u.buf_tag = tag;
|
|
pools[poolnum].tot_alloc++;
|
|
pools[poolnum].num_alloc++;
|
|
|
|
// If the buffer was modified after it was last freed, log it.
|
|
//
|
|
auto pui = reinterpret_cast<unsigned *>(p);
|
|
if (*pui != pools[poolnum].poolmagic)
|
|
{
|
|
pool_err(T("BUG"), LOG_PROBLEMS, poolnum, tag, ph, T("Alloc"),
|
|
T("buffer modified after free"), file, line);
|
|
}
|
|
*pui = 0;
|
|
return p;
|
|
}
|
|
|
|
// Allocate a new buffer from the system.
|
|
//
|
|
char *raw = nullptr;
|
|
try
|
|
{
|
|
raw = new char[pools[poolnum].pool_alloc_size];
|
|
}
|
|
catch (...)
|
|
{
|
|
}
|
|
|
|
if (nullptr == raw)
|
|
{
|
|
OutOfMemory(reinterpret_cast<const UTF8 *>(__FILE__), __LINE__);
|
|
return nullptr;
|
|
}
|
|
|
|
pools[poolnum].all_buffers.push_back(raw);
|
|
pool_account_system(pools[poolnum].pool_alloc_size);
|
|
|
|
ph = reinterpret_cast<POOLHDR *>(raw);
|
|
p = reinterpret_cast<UTF8 *>(raw + sizeof(POOLHDR));
|
|
pf = reinterpret_cast<POOLFTR *>(p + pools[poolnum].pool_client_size);
|
|
|
|
ph->magicnum = pools[poolnum].poolmagic;
|
|
ph->pool_size = pools[poolnum].pool_client_size;
|
|
pf->magicnum = pools[poolnum].poolmagic;
|
|
*reinterpret_cast<unsigned *>(p) = pools[poolnum].poolmagic;
|
|
pools[poolnum].max_alloc++;
|
|
|
|
ph->u.buf_tag = tag;
|
|
pools[poolnum].tot_alloc++;
|
|
pools[poolnum].num_alloc++;
|
|
|
|
auto pui = reinterpret_cast<unsigned *>(p);
|
|
*pui = 0;
|
|
return p;
|
|
}
|
|
|
|
UTF8 *pool_alloc_lbuf(const UTF8 *tag, const UTF8 *file, const int line)
|
|
{
|
|
if (g_paranoid_alloc)
|
|
{
|
|
pool_check(tag, file, line);
|
|
}
|
|
|
|
UTF8 *p;
|
|
POOLHDR *ph;
|
|
POOLFTR *pf;
|
|
|
|
// Same freelist discipline as pool_alloc: drop only the bad entry (#1290).
|
|
//
|
|
while (!pools[POOL_LBUF].free_stack.empty())
|
|
{
|
|
char *raw = pools[POOL_LBUF].free_stack.back();
|
|
pools[POOL_LBUF].free_stack.pop_back();
|
|
ph = reinterpret_cast<POOLHDR *>(raw);
|
|
p = reinterpret_cast<UTF8 *>(raw + sizeof(POOLHDR));
|
|
pf = reinterpret_cast<POOLFTR *>(p + LBUF_SIZE);
|
|
|
|
if (ph->magicnum != pools[POOL_LBUF].poolmagic)
|
|
{
|
|
pool_err(T("BUG"), LOG_ALWAYS, POOL_LBUF, tag, ph, T("Alloc"),
|
|
T("corrupted buffer header on freelist"), file, line);
|
|
pools[POOL_LBUF].num_lost++;
|
|
continue;
|
|
}
|
|
|
|
if (pf->magicnum != pools[POOL_LBUF].poolmagic)
|
|
{
|
|
pool_err(T("BUG"), LOG_ALWAYS, POOL_LBUF, tag, ph, T("Alloc"),
|
|
T("corrupted buffer footer"), file, line);
|
|
pf->magicnum = pools[POOL_LBUF].poolmagic;
|
|
}
|
|
|
|
ph->u.buf_tag = tag;
|
|
pools[POOL_LBUF].tot_alloc++;
|
|
pools[POOL_LBUF].num_alloc++;
|
|
|
|
auto pui = reinterpret_cast<unsigned *>(p);
|
|
if (*pui != pools[POOL_LBUF].poolmagic)
|
|
{
|
|
pool_err(T("BUG"), LOG_PROBLEMS, POOL_LBUF, tag, ph, T("Alloc"),
|
|
T("buffer modified after free"), file, line);
|
|
}
|
|
*pui = 0;
|
|
return p;
|
|
}
|
|
|
|
char *raw = nullptr;
|
|
try
|
|
{
|
|
raw = new char[LBUF_SIZE + sizeof(POOLHDR) + sizeof(POOLFTR)];
|
|
}
|
|
catch (...)
|
|
{
|
|
}
|
|
|
|
if (nullptr == raw)
|
|
{
|
|
OutOfMemory(reinterpret_cast<const UTF8 *>(__FILE__), __LINE__);
|
|
return nullptr;
|
|
}
|
|
|
|
pools[POOL_LBUF].all_buffers.push_back(raw);
|
|
pool_account_system(LBUF_SIZE + sizeof(POOLHDR) + sizeof(POOLFTR));
|
|
|
|
ph = reinterpret_cast<POOLHDR *>(raw);
|
|
p = reinterpret_cast<UTF8 *>(raw + sizeof(POOLHDR));
|
|
pf = reinterpret_cast<POOLFTR *>(p + LBUF_SIZE);
|
|
|
|
ph->magicnum = pools[POOL_LBUF].poolmagic;
|
|
ph->pool_size = LBUF_SIZE;
|
|
pf->magicnum = pools[POOL_LBUF].poolmagic;
|
|
*reinterpret_cast<unsigned *>(p) = pools[POOL_LBUF].poolmagic;
|
|
pools[POOL_LBUF].max_alloc++;
|
|
|
|
ph->u.buf_tag = tag;
|
|
pools[POOL_LBUF].tot_alloc++;
|
|
pools[POOL_LBUF].num_alloc++;
|
|
|
|
auto pui = reinterpret_cast<unsigned *>(p);
|
|
*pui = 0;
|
|
return p;
|
|
}
|
|
|
|
void pool_free(int poolnum, UTF8* buf, const UTF8* file, const int line)
|
|
{
|
|
if (buf == nullptr)
|
|
{
|
|
mux_fprintf(stderr, T("BUG ALLOC: Attempt to free null pointer in %s line %d." ENDLINE), file, line);
|
|
return;
|
|
}
|
|
|
|
char *raw = reinterpret_cast<char *>(buf) - sizeof(POOLHDR);
|
|
POOLHDR *ph = reinterpret_cast<POOLHDR *>(raw);
|
|
const auto pf = reinterpret_cast<POOLFTR *>(buf + pools[poolnum].pool_client_size);
|
|
const auto pui = reinterpret_cast<unsigned *>(buf);
|
|
|
|
if (g_paranoid_alloc)
|
|
{
|
|
pool_check(ph->u.buf_tag, file, line);
|
|
}
|
|
|
|
// Make sure the buffer header is good. If it isn't, log the error and
|
|
// throw away the buffer.
|
|
//
|
|
if (ph->magicnum != pools[poolnum].poolmagic)
|
|
{
|
|
pool_err(T("BUG"), LOG_ALWAYS, poolnum, ph->u.buf_tag, ph, T("Free"),
|
|
T("corrupted buffer header"), file, line);
|
|
pools[poolnum].num_lost++;
|
|
pools[poolnum].num_alloc--;
|
|
pools[poolnum].tot_alloc--;
|
|
return;
|
|
}
|
|
|
|
// Verify the buffer footer. Don't unlink if damaged, just repair.
|
|
//
|
|
if (pf->magicnum != pools[poolnum].poolmagic)
|
|
{
|
|
pool_err(T("BUG"), LOG_ALWAYS, poolnum, ph->u.buf_tag, ph, T("Free"),
|
|
T("corrupted buffer footer"), file, line);
|
|
pf->magicnum = pools[poolnum].poolmagic;
|
|
}
|
|
|
|
// Verify that we are not trying to free someone else's buffer.
|
|
//
|
|
if (ph->pool_size != pools[poolnum].pool_client_size)
|
|
{
|
|
pool_err(T("BUG"), LOG_ALWAYS, poolnum, ph->u.buf_tag, ph, T("Free"),
|
|
T("Attempt to free into a different pool."), file, line);
|
|
return;
|
|
}
|
|
|
|
// Make sure we aren't freeing an already free buffer. If we are, log an
|
|
// error, otherwise update the pool header and stats.
|
|
//
|
|
if (*pui == pools[poolnum].poolmagic)
|
|
{
|
|
pool_err(T("BUG"), LOG_BUGS, poolnum, ph->u.buf_tag, ph, T("Free"),
|
|
T("buffer already freed"), file, line);
|
|
}
|
|
else
|
|
{
|
|
*pui = pools[poolnum].poolmagic;
|
|
pools[poolnum].free_stack.push_back(raw);
|
|
pools[poolnum].num_alloc--;
|
|
}
|
|
}
|
|
|
|
void pool_free_lbuf(UTF8 *buf, const UTF8 *file, const int line)
|
|
{
|
|
if (buf == nullptr)
|
|
{
|
|
mux_fprintf(stderr, T("BUG ALLOC: Attempt to free_lbuf null pointer in %s line %d." ENDLINE), file, line);
|
|
return;
|
|
}
|
|
|
|
char *raw = reinterpret_cast<char *>(buf) - sizeof(POOLHDR);
|
|
POOLHDR *ph = reinterpret_cast<POOLHDR *>(raw);
|
|
const auto pf = reinterpret_cast<POOLFTR *>(buf + LBUF_SIZE);
|
|
const auto pui = reinterpret_cast<unsigned *>(buf);
|
|
|
|
if (g_paranoid_alloc)
|
|
{
|
|
pool_check(ph->u.buf_tag, file, line);
|
|
}
|
|
|
|
if ( ph->magicnum != pools[POOL_LBUF].poolmagic
|
|
|| pf->magicnum != pools[POOL_LBUF].poolmagic
|
|
|| ph->pool_size != LBUF_SIZE
|
|
|| *pui == pools[POOL_LBUF].poolmagic)
|
|
{
|
|
if (ph->magicnum != pools[POOL_LBUF].poolmagic)
|
|
{
|
|
pool_err(T("BUG"), LOG_ALWAYS, POOL_LBUF, ph->u.buf_tag, ph, T("Free"),
|
|
T("corrupted buffer header"), file, line);
|
|
pools[POOL_LBUF].num_lost++;
|
|
pools[POOL_LBUF].num_alloc--;
|
|
pools[POOL_LBUF].tot_alloc--;
|
|
return;
|
|
}
|
|
else if (pf->magicnum != pools[POOL_LBUF].poolmagic)
|
|
{
|
|
pool_err(T("BUG"), LOG_ALWAYS, POOL_LBUF, ph->u.buf_tag, ph, T("Free"),
|
|
T("corrupted buffer footer"), file, line);
|
|
pf->magicnum = pools[POOL_LBUF].poolmagic;
|
|
}
|
|
else if (ph->pool_size != LBUF_SIZE)
|
|
{
|
|
pool_err(T("BUG"), LOG_ALWAYS, POOL_LBUF, ph->u.buf_tag, ph, T("Free"),
|
|
T("Attempt to free into a different pool."), file, line);
|
|
return;
|
|
}
|
|
|
|
if (*pui == pools[POOL_LBUF].poolmagic)
|
|
{
|
|
pool_err(T("BUG"), LOG_BUGS, POOL_LBUF, ph->u.buf_tag, ph, T("Free"),
|
|
T("buffer already freed"), file, line);
|
|
return;
|
|
}
|
|
}
|
|
|
|
*pui = pools[POOL_LBUF].poolmagic;
|
|
pools[POOL_LBUF].free_stack.push_back(raw);
|
|
pools[POOL_LBUF].num_alloc--;
|
|
}
|
|
|
|
static inline void alloc_notify(dbref player, const UTF8 *msg)
|
|
{
|
|
if (g_alloc_notify_fn)
|
|
{
|
|
g_alloc_notify_fn(player, msg);
|
|
}
|
|
}
|
|
|
|
static void pool_trace(const dbref player, const int poolnum, const UTF8 *text)
|
|
{
|
|
int numfree = 0;
|
|
alloc_notify(player, tprintf(T("----- %s -----"), text));
|
|
for (char *raw : pools[poolnum].all_buffers)
|
|
{
|
|
auto ph = reinterpret_cast<POOLHDR *>(raw);
|
|
if (ph->magicnum != pools[poolnum].poolmagic)
|
|
{
|
|
alloc_notify(player, M_("*** CORRUPTED BUFFER HEADER, ABORTING SCAN ***"));
|
|
alloc_notify(player, tprintf(T("%d free %s (before corruption)"),
|
|
numfree, text));
|
|
return;
|
|
}
|
|
auto ibuf = reinterpret_cast<unsigned *>(raw + sizeof(POOLHDR));
|
|
if (*ibuf != pools[poolnum].poolmagic)
|
|
{
|
|
alloc_notify(player, ph->u.buf_tag);
|
|
}
|
|
else
|
|
{
|
|
numfree++;
|
|
}
|
|
}
|
|
alloc_notify(player, tprintf(T("%d free %s"), numfree, text));
|
|
}
|
|
|
|
void list_bufstats(dbref player)
|
|
{
|
|
// Buffer pool table (#1667 Phase 4 C4). Name is left-justified; numeric
|
|
// columns keep fixed-width right justification via RightJustifyNumber
|
|
// (mux_vsnprintf has no %* width — #1429).
|
|
//
|
|
static const size_t kBufNameCols = 12;
|
|
static const size_t kBufSizeCols = 5;
|
|
static const size_t kBufInUseCols = 10;
|
|
static const size_t kBufTotalCols = 10;
|
|
static const size_t kBufAllocsCols = 16;
|
|
static const size_t kBufLostCols = 6;
|
|
|
|
{
|
|
UTF8 header[MBUF_SIZE];
|
|
size_t pos = 0;
|
|
pos = mux_table_append_ljust(header, sizeof(header), pos,
|
|
M_("Buffer Stats"), kBufNameCols);
|
|
pos = mux_table_append_bytes(header, sizeof(header), pos, " ");
|
|
pos = mux_table_append_ljust(header, sizeof(header), pos,
|
|
M_("Size"), kBufSizeCols);
|
|
pos = mux_table_append_bytes(header, sizeof(header), pos, " ");
|
|
pos = mux_table_append_ljust(header, sizeof(header), pos,
|
|
M_("InUse"), kBufInUseCols);
|
|
pos = mux_table_append_bytes(header, sizeof(header), pos, " ");
|
|
pos = mux_table_append_ljust(header, sizeof(header), pos,
|
|
M_("Total"), kBufTotalCols);
|
|
pos = mux_table_append_bytes(header, sizeof(header), pos, " ");
|
|
pos = mux_table_append_ljust(header, sizeof(header), pos,
|
|
M_("Allocs"), kBufAllocsCols);
|
|
pos = mux_table_append_bytes(header, sizeof(header), pos, " ");
|
|
pos = mux_table_append_ljust(header, sizeof(header), pos,
|
|
M_("Lost"), kBufLostCols);
|
|
alloc_notify(player, header);
|
|
}
|
|
for (int i = 0; i < NUM_POOLS; i++)
|
|
{
|
|
UTF8 buff[MBUF_SIZE];
|
|
UTF8 num[24];
|
|
size_t pos = 0;
|
|
size_t nw;
|
|
pos = mux_table_append_ljust(buff, sizeof(buff), pos,
|
|
poolnames[i], kBufNameCols);
|
|
pos = mux_table_append_bytes(buff, sizeof(buff), pos, " ");
|
|
nw = RightJustifyNumber(num, kBufSizeCols,
|
|
static_cast<int64_t>(pools[i].pool_client_size), ' ');
|
|
num[nw] = '\0';
|
|
pos = mux_table_append_bytes(buff, sizeof(buff), pos,
|
|
reinterpret_cast<const char *>(num));
|
|
pos = mux_table_append_bytes(buff, sizeof(buff), pos, " ");
|
|
nw = RightJustifyNumber(num, kBufInUseCols,
|
|
static_cast<int64_t>(pools[i].num_alloc), ' ');
|
|
num[nw] = '\0';
|
|
pos = mux_table_append_bytes(buff, sizeof(buff), pos,
|
|
reinterpret_cast<const char *>(num));
|
|
pos = mux_table_append_bytes(buff, sizeof(buff), pos, " ");
|
|
nw = RightJustifyNumber(num, kBufTotalCols,
|
|
static_cast<int64_t>(pools[i].max_alloc), ' ');
|
|
num[nw] = '\0';
|
|
pos = mux_table_append_bytes(buff, sizeof(buff), pos,
|
|
reinterpret_cast<const char *>(num));
|
|
pos = mux_table_append_bytes(buff, sizeof(buff), pos, " ");
|
|
nw = RightJustifyNumber(num, kBufAllocsCols,
|
|
static_cast<int64_t>(pools[i].tot_alloc), ' ');
|
|
num[nw] = '\0';
|
|
pos = mux_table_append_bytes(buff, sizeof(buff), pos,
|
|
reinterpret_cast<const char *>(num));
|
|
pos = mux_table_append_bytes(buff, sizeof(buff), pos, " ");
|
|
nw = RightJustifyNumber(num, kBufLostCols,
|
|
static_cast<int64_t>(pools[i].num_lost), ' ');
|
|
num[nw] = '\0';
|
|
pos = mux_table_append_bytes(buff, sizeof(buff), pos,
|
|
reinterpret_cast<const char *>(num));
|
|
alloc_notify(player, buff);
|
|
}
|
|
}
|
|
|
|
void list_buftrace(dbref player)
|
|
{
|
|
for (int i = 0; i < NUM_POOLS; i++)
|
|
{
|
|
pool_trace(player, i, poolnames[i]);
|
|
}
|
|
}
|
|
|
|
void pool_reset(void)
|
|
{
|
|
for (auto &pool : pools)
|
|
{
|
|
// Build a set of free buffer pointers for fast lookup.
|
|
//
|
|
std::unordered_set<char *> free_set(
|
|
pool.free_stack.begin(), pool.free_stack.end());
|
|
pool.free_stack.clear();
|
|
|
|
// Walk all_buffers: delete free ones, keep in-use ones.
|
|
//
|
|
auto new_end = std::remove_if(pool.all_buffers.begin(),
|
|
pool.all_buffers.end(),
|
|
[&free_set](char *raw) -> bool
|
|
{
|
|
if (free_set.count(raw))
|
|
{
|
|
delete[] raw;
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
pool.all_buffers.erase(new_end, pool.all_buffers.end());
|
|
pool.max_alloc = pool.num_alloc;
|
|
}
|
|
}
|