2006-10-05 19:27:57 +00:00
|
|
|
/*! \file alloc.cpp
|
2007-01-03 03:11:53 +00:00
|
|
|
* \brief Memory Allocation Subsystem.
|
2006-10-05 19:27:57 +00:00
|
|
|
*
|
|
|
|
|
* 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
|
2026-03-19 09:08:32 -06:00
|
|
|
* 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.
|
2006-10-05 19:27:57 +00:00
|
|
|
*/
|
|
|
|
|
|
2006-09-01 22:59:23 +00:00
|
|
|
#include "copyright.h"
|
|
|
|
|
#include "autoconf.h"
|
|
|
|
|
#include "config.h"
|
Rework Windows build for component-based directory layout
Adapt all vcxproj files and solution to the new directory structure
(src/ driver, lib/ shared library, modules/engine/ game logic,
modules/{comsys,mail,exp3,sqlproxy,sqlslave}/ loadable modules).
Key changes:
- libmux.dll exports utility symbols via LIBMUX_API macro
(__declspec(dllexport) when BUILDING_LIBMUX, dllimport otherwise)
- LIBMUX_API added to all shared headers: stringutil.h, timeutil.h,
mathutil.h, utf8tables.h, svdhash.h, svdrand.h, sha1.h, alloc.h,
dbutil.h, core.h
- Per-file PreprocessorDefinitions in libmux.vcxproj inherit from
project-level via %(PreprocessorDefinitions)
- Driver factory declarations (CDriverControlFactory,
CConnectionManagerFactory) guarded with BUILDING_DRIVER
- PCG-XSH-RR-64/32 (pcg32) for Windows (no __int128 needed);
Unix PCG-XSL-RR-128/64 unchanged
- MSVC portability fixes: _strnicmp, _BitScanForward64, (std::min)(),
HAVE_WORKING_FORK guards, WINDOWS_FILES/UNIX_FILES ModuleAdd paths
- Remove stubslave.cpp and slave.cpp from netmux.vcxproj (separate
processes)
- Fix sqlproxy/sqlslave vcxproj relative paths for new layout
- Add ws2_32.lib to engine.vcxproj for socket functions
- Add strcasecmp/strtok_r/strndup compat shims for comsys/mail
Builds successfully: libmux.dll, engine.dll, netmux.exe, exp3.dll,
sqlproxy.dll, sqlslave.dll. Comsys/mail blocked on sqlite3 linking
architecture (need COM-mediated or independent sqlite3 linkage).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 09:38:57 -06:00
|
|
|
#include "core.h"
|
|
|
|
|
#include "modules.h"
|
2026-07-28 11:31:23 -06:00
|
|
|
#include "mux_table.h"
|
2006-09-01 22:59:23 +00:00
|
|
|
|
2026-03-19 09:08:32 -06:00
|
|
|
#include <algorithm>
|
|
|
|
|
#include <unordered_set>
|
|
|
|
|
#include <vector>
|
|
|
|
|
|
2026-03-09 14:34:03 -06:00
|
|
|
// 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;
|
|
|
|
|
|
feat(alloc): pool memory footprint budget (defense-in-depth, off by default)
Backstop for the pool_alloc -> fatal OutOfMemory cliff
(docs/survey-resource-defenses.md gap #3). pool_alloc/pool_alloc_lbuf
take memory from the system only on the slow path (freelist empty) and
never return it, so the process's pool footprint is its PEAK CONCURRENT
buffer count. New pool_memory_limit config (bytes; K/M/G suffixes;
0=unlimited) caps that footprint: on the slow path, crossing it trips
the same cooperative per-command abort the wall-clock alarm uses
(alarm_clock.alarmed, reachable from alloc.cpp — both are libmux) so a
runaway command unwinds and frees its buffers back to the freelist
instead of the server aborting on OutOfMemory. Composes with the JIT
wall-clock alarm (same flag). cf_pool_limit pushes the value to the
libmux global on load and @readcache reload.
Off by default, and honestly so: measurement showed the cliff is HARD
TO REACH, which is the point — the interpreter's alloc-and-free
discipline, bounded nesting (func_nest_lim/nStackLimit), and freelist
reuse keep peak pool footprint tiny (a short session didn't cross even
256KB). And no non-zero default is safe across deployments (a 256MB
VPS vs a 32GB host want different ceilings), so the admin sizes it.
Note: the nested-iter OS-OOM crash seen while building the JIT alarm is
a DIFFERENT path (JIT arena/guest growth, bounded by max_dispatch=10M
by default), not this one.
Verified default-off is a true no-op: smoke 1319/1319, oracle 9/9,
jit_diff 400/0, stress 8/8; the trip path is wired (server survives +
stays responsive when the budget is crossed).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 00:08:59 -06:00
|
|
|
// Pool memory footprint budget (#pool-oom). See alloc.h.
|
|
|
|
|
size_t g_pool_limit_bytes = 0;
|
|
|
|
|
size_t g_pool_system_bytes = 0;
|
|
|
|
|
|
2026-07-23 19:43:29 -06:00
|
|
|
// Engine → driver live config push. See alloc.h.
|
|
|
|
|
DRIVER_CONFIG_SYNC_FN g_driver_config_sync_fn = nullptr;
|
|
|
|
|
|
feat(alloc): pool memory footprint budget (defense-in-depth, off by default)
Backstop for the pool_alloc -> fatal OutOfMemory cliff
(docs/survey-resource-defenses.md gap #3). pool_alloc/pool_alloc_lbuf
take memory from the system only on the slow path (freelist empty) and
never return it, so the process's pool footprint is its PEAK CONCURRENT
buffer count. New pool_memory_limit config (bytes; K/M/G suffixes;
0=unlimited) caps that footprint: on the slow path, crossing it trips
the same cooperative per-command abort the wall-clock alarm uses
(alarm_clock.alarmed, reachable from alloc.cpp — both are libmux) so a
runaway command unwinds and frees its buffers back to the freelist
instead of the server aborting on OutOfMemory. Composes with the JIT
wall-clock alarm (same flag). cf_pool_limit pushes the value to the
libmux global on load and @readcache reload.
Off by default, and honestly so: measurement showed the cliff is HARD
TO REACH, which is the point — the interpreter's alloc-and-free
discipline, bounded nesting (func_nest_lim/nStackLimit), and freelist
reuse keep peak pool footprint tiny (a short session didn't cross even
256KB). And no non-zero default is safe across deployments (a 256MB
VPS vs a 32GB host want different ceilings), so the admin sizes it.
Note: the nested-iter OS-OOM crash seen while building the JIT alarm is
a DIFFERENT path (JIT arena/guest growth, bounded by max_dispatch=10M
by default), not this one.
Verified default-off is a true no-op: smoke 1319/1319, oracle 9/9,
jit_diff 400/0, stress 8/8; the trip path is wired (server survives +
stays responsive when the budget is crossed).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 00:08:59 -06:00
|
|
|
// 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
|
2026-07-23 19:49:49 -06:00
|
|
|
// — 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.
|
|
|
|
|
//
|
feat(alloc): pool memory footprint budget (defense-in-depth, off by default)
Backstop for the pool_alloc -> fatal OutOfMemory cliff
(docs/survey-resource-defenses.md gap #3). pool_alloc/pool_alloc_lbuf
take memory from the system only on the slow path (freelist empty) and
never return it, so the process's pool footprint is its PEAK CONCURRENT
buffer count. New pool_memory_limit config (bytes; K/M/G suffixes;
0=unlimited) caps that footprint: on the slow path, crossing it trips
the same cooperative per-command abort the wall-clock alarm uses
(alarm_clock.alarmed, reachable from alloc.cpp — both are libmux) so a
runaway command unwinds and frees its buffers back to the freelist
instead of the server aborting on OutOfMemory. Composes with the JIT
wall-clock alarm (same flag). cf_pool_limit pushes the value to the
libmux global on load and @readcache reload.
Off by default, and honestly so: measurement showed the cliff is HARD
TO REACH, which is the point — the interpreter's alloc-and-free
discipline, bounded nesting (func_nest_lim/nStackLimit), and freelist
reuse keep peak pool footprint tiny (a short session didn't cross even
256KB). And no non-zero default is safe across deployments (a 256MB
VPS vs a 32GB host want different ceilings), so the admin sizes it.
Note: the nested-iter OS-OOM crash seen while building the JIT alarm is
a DIFFERENT path (JIT arena/guest growth, bounded by max_dispatch=10M
by default), not this one.
Verified default-off is a true no-op: smoke 1319/1319, oracle 9/9,
jit_diff 400/0, stress 8/8; the trip path is wired (server survives +
stays responsive when the budget is crossed).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 00:08:59 -06:00
|
|
|
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 "
|
2026-07-23 19:49:49 -06:00
|
|
|
"the current command to protect the server. Further "
|
|
|
|
|
"slow-path growth will keep aborting until the budget "
|
|
|
|
|
"is raised or the process restarts." ENDLINE),
|
feat(alloc): pool memory footprint budget (defense-in-depth, off by default)
Backstop for the pool_alloc -> fatal OutOfMemory cliff
(docs/survey-resource-defenses.md gap #3). pool_alloc/pool_alloc_lbuf
take memory from the system only on the slow path (freelist empty) and
never return it, so the process's pool footprint is its PEAK CONCURRENT
buffer count. New pool_memory_limit config (bytes; K/M/G suffixes;
0=unlimited) caps that footprint: on the slow path, crossing it trips
the same cooperative per-command abort the wall-clock alarm uses
(alarm_clock.alarmed, reachable from alloc.cpp — both are libmux) so a
runaway command unwinds and frees its buffers back to the freelist
instead of the server aborting on OutOfMemory. Composes with the JIT
wall-clock alarm (same flag). cf_pool_limit pushes the value to the
libmux global on load and @readcache reload.
Off by default, and honestly so: measurement showed the cliff is HARD
TO REACH, which is the point — the interpreter's alloc-and-free
discipline, bounded nesting (func_nest_lim/nStackLimit), and freelist
reuse keep peak pool footprint tiny (a short session didn't cross even
256KB). And no non-zero default is safe across deployments (a 256MB
VPS vs a 32GB host want different ceilings), so the admin sizes it.
Note: the nested-iter OS-OOM crash seen while building the JIT alarm is
a DIFFERENT path (JIT arena/guest growth, bounded by max_dispatch=10M
by default), not this one.
Verified default-off is a true no-op: smoke 1319/1319, oracle 9/9,
jit_diff 400/0, stress 8/8; the trip path is wired (server survives +
stays responsive when the budget is crossed).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 00:08:59 -06:00
|
|
|
g_pool_system_bytes, g_pool_limit_bytes);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-07-23 19:49:49 -06:00
|
|
|
// 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)
|
feat(alloc): pool memory footprint budget (defense-in-depth, off by default)
Backstop for the pool_alloc -> fatal OutOfMemory cliff
(docs/survey-resource-defenses.md gap #3). pool_alloc/pool_alloc_lbuf
take memory from the system only on the slow path (freelist empty) and
never return it, so the process's pool footprint is its PEAK CONCURRENT
buffer count. New pool_memory_limit config (bytes; K/M/G suffixes;
0=unlimited) caps that footprint: on the slow path, crossing it trips
the same cooperative per-command abort the wall-clock alarm uses
(alarm_clock.alarmed, reachable from alloc.cpp — both are libmux) so a
runaway command unwinds and frees its buffers back to the freelist
instead of the server aborting on OutOfMemory. Composes with the JIT
wall-clock alarm (same flag). cf_pool_limit pushes the value to the
libmux global on load and @readcache reload.
Off by default, and honestly so: measurement showed the cliff is HARD
TO REACH, which is the point — the interpreter's alloc-and-free
discipline, bounded nesting (func_nest_lim/nStackLimit), and freelist
reuse keep peak pool footprint tiny (a short session didn't cross even
256KB). And no non-zero default is safe across deployments (a 256MB
VPS vs a 32GB host want different ceilings), so the admin sizes it.
Note: the nested-iter OS-OOM crash seen while building the JIT alarm is
a DIFFERENT path (JIT arena/guest growth, bounded by max_dispatch=10M
by default), not this one.
Verified default-off is a true no-op: smoke 1319/1319, oracle 9/9,
jit_diff 400/0, stress 8/8; the trip path is wired (server survives +
stays responsive when the budget is crossed).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 00:08:59 -06:00
|
|
|
{
|
|
|
|
|
warned = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-09 19:40:40 -06:00
|
|
|
// Output callback for @list buffers — set by engine at startup.
|
|
|
|
|
//
|
|
|
|
|
ALLOC_NOTIFY_FN g_alloc_notify_fn = nullptr;
|
|
|
|
|
|
2008-01-25 08:06:38 -08:00
|
|
|
/*! \brief Per-buffer header to manage and organize client allocation.
|
|
|
|
|
*
|
2026-03-19 09:08:32 -06:00
|
|
|
* The POOLHDR structure precedes a client area which must be properly
|
2008-01-25 08:06:38 -08:00
|
|
|
* aligned to avoid faults when the client accesses structure members within
|
2026-03-19 09:08:32 -06:00
|
|
|
* the client area. 64-bit alignment should be sufficient.
|
2008-01-25 08:06:38 -08:00
|
|
|
*
|
2026-03-19 09:08:32 -06:00
|
|
|
* 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.
|
2008-01-25 08:06:38 -08:00
|
|
|
*/
|
2006-09-01 22:59:23 +00:00
|
|
|
|
|
|
|
|
typedef struct pool_header
|
|
|
|
|
{
|
2026-03-19 09:08:32 -06:00
|
|
|
unsigned int magicnum; // For consistency check
|
|
|
|
|
size_t pool_size; // For consistency check
|
2008-01-25 08:06:38 -08:00
|
|
|
union
|
|
|
|
|
{
|
2026-03-19 09:08:32 -06:00
|
|
|
const UTF8 *buf_tag; // Debugging/trace tag
|
|
|
|
|
uint64_t align; // Alignment padding
|
2008-01-25 08:06:38 -08:00
|
|
|
} u;
|
2006-09-01 22:59:23 +00:00
|
|
|
} POOLHDR;
|
|
|
|
|
|
2008-01-25 08:06:38 -08:00
|
|
|
/*! \brief Per-buffer footer to catch buffer overruns.
|
|
|
|
|
*
|
|
|
|
|
* The POOLFTR structure helps detect when a client has written beyond the
|
|
|
|
|
* bounds of the buffer.
|
|
|
|
|
*/
|
|
|
|
|
|
2006-09-01 22:59:23 +00:00
|
|
|
typedef struct pool_footer
|
|
|
|
|
{
|
|
|
|
|
unsigned int magicnum; // For consistency check
|
|
|
|
|
} POOLFTR;
|
|
|
|
|
|
2026-03-19 09:08:32 -06:00
|
|
|
/*! \brief Per-pool structure containing statistics and vector-based lists.
|
2008-01-25 08:06:38 -08:00
|
|
|
*
|
2026-03-19 09:08:32 -06:00
|
|
|
* The freelist is a stack (vector) of raw allocation pointers.
|
|
|
|
|
* all_buffers tracks every allocation for diagnostics and cleanup.
|
2008-01-25 08:06:38 -08:00
|
|
|
*/
|
|
|
|
|
|
2006-09-01 22:59:23 +00:00
|
|
|
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
|
2026-03-19 09:08:32 -06:00
|
|
|
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
|
2006-09-01 22:59:23 +00:00
|
|
|
} POOL;
|
|
|
|
|
|
|
|
|
|
static POOL pools[NUM_POOLS];
|
2007-03-14 00:55:08 +00:00
|
|
|
static const UTF8 *poolnames[] =
|
2006-09-01 22:59:23 +00:00
|
|
|
{
|
2007-03-14 22:33:04 +00:00
|
|
|
T("Lbufs"),
|
|
|
|
|
T("Sbufs"),
|
|
|
|
|
T("Mbufs"),
|
|
|
|
|
T("Bools"),
|
|
|
|
|
T("Descs"),
|
|
|
|
|
T("Qentries"),
|
2026-03-19 09:27:48 -06:00
|
|
|
T("Pcaches")
|
2006-09-01 22:59:23 +00:00
|
|
|
};
|
|
|
|
|
|
2008-01-25 08:06:38 -08:00
|
|
|
/*! \brief Initialize a buffer pool.
|
|
|
|
|
*
|
|
|
|
|
* This is done once. The client size, magic, and allocation size are chosen
|
2026-03-19 09:08:32 -06:00
|
|
|
* at this time. After this initialization, allocations can be done.
|
2008-01-25 08:06:38 -08:00
|
|
|
*
|
|
|
|
|
* \param poolnum An integer uniquely indicating which pool.
|
|
|
|
|
* \param poolsize The size of the client area this pool supports.
|
|
|
|
|
*/
|
2006-09-01 22:59:23 +00:00
|
|
|
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);
|
2026-03-19 09:08:32 -06:00
|
|
|
pools[poolnum].free_stack.clear();
|
|
|
|
|
pools[poolnum].all_buffers.clear();
|
2006-09-01 22:59:23 +00:00
|
|
|
pools[poolnum].tot_alloc = 0;
|
|
|
|
|
pools[poolnum].num_alloc = 0;
|
|
|
|
|
pools[poolnum].max_alloc = 0;
|
|
|
|
|
pools[poolnum].num_lost = 0;
|
|
|
|
|
}
|
|
|
|
|
|
2008-01-25 08:06:38 -08:00
|
|
|
/*! \brief Helper function for logging pool errors.
|
|
|
|
|
*/
|
2006-09-01 22:59:23 +00:00
|
|
|
static void pool_err
|
|
|
|
|
(
|
2025-03-24 14:53:29 -06:00
|
|
|
const UTF8 *logsys,
|
2022-03-12 16:46:50 -07:00
|
|
|
int logflag,
|
|
|
|
|
int poolnum,
|
2025-03-24 14:53:29 -06:00
|
|
|
const UTF8 *tag,
|
|
|
|
|
POOLHDR* ph,
|
|
|
|
|
const UTF8 *action,
|
|
|
|
|
const UTF8 *reason,
|
|
|
|
|
const UTF8 *file,
|
2022-03-12 16:46:50 -07:00
|
|
|
const int line
|
2006-09-01 22:59:23 +00:00
|
|
|
)
|
|
|
|
|
{
|
2026-03-09 13:44:58 -06:00
|
|
|
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);
|
2006-09-01 22:59:23 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-19 09:08:32 -06:00
|
|
|
/*! \brief Validates the buffers in the all_buffers list.
|
2008-01-25 08:06:38 -08:00
|
|
|
*
|
2026-03-19 09:08:32 -06:00
|
|
|
* 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.
|
2008-01-25 08:06:38 -08:00
|
|
|
*/
|
|
|
|
|
static void pool_vfy
|
|
|
|
|
(
|
|
|
|
|
int poolnum,
|
2025-03-24 14:53:29 -06:00
|
|
|
const UTF8 *tag,
|
|
|
|
|
const UTF8 *file,
|
2008-01-25 08:06:38 -08:00
|
|
|
const int line
|
|
|
|
|
)
|
|
|
|
|
{
|
2022-03-12 16:46:50 -07:00
|
|
|
const size_t psize = pools[poolnum].pool_client_size;
|
2026-03-19 09:08:32 -06:00
|
|
|
for (char *raw : pools[poolnum].all_buffers)
|
2006-09-01 22:59:23 +00:00
|
|
|
{
|
2026-03-19 09:08:32 -06:00
|
|
|
auto ph = reinterpret_cast<POOLHDR *>(raw);
|
|
|
|
|
auto h = raw + sizeof(POOLHDR);
|
|
|
|
|
auto pf = reinterpret_cast<POOLFTR *>(h + pools[poolnum].pool_client_size);
|
2006-09-01 22:59:23 +00:00
|
|
|
|
|
|
|
|
if (ph->magicnum != pools[poolnum].poolmagic)
|
|
|
|
|
{
|
2007-03-14 22:33:04 +00:00
|
|
|
pool_err(T("BUG"), LOG_ALWAYS, poolnum, tag, ph, T("Verify"),
|
2026-03-19 09:08:32 -06:00
|
|
|
T("header corrupted"), file, line);
|
|
|
|
|
continue;
|
2006-09-01 22:59:23 +00:00
|
|
|
}
|
2008-01-25 08:06:38 -08:00
|
|
|
|
2006-09-01 22:59:23 +00:00
|
|
|
if (pf->magicnum != pools[poolnum].poolmagic)
|
|
|
|
|
{
|
2007-03-14 22:33:04 +00:00
|
|
|
pool_err(T("BUG"), LOG_ALWAYS, poolnum, tag, ph, T("Verify"),
|
|
|
|
|
T("footer corrupted"), file, line);
|
2006-09-01 22:59:23 +00:00
|
|
|
pf->magicnum = pools[poolnum].poolmagic;
|
|
|
|
|
}
|
2008-01-25 08:06:38 -08:00
|
|
|
|
2006-09-01 22:59:23 +00:00
|
|
|
if (ph->pool_size != psize)
|
|
|
|
|
{
|
2007-03-14 22:33:04 +00:00
|
|
|
pool_err(T("BUG"), LOG_ALWAYS, poolnum, tag, ph,
|
|
|
|
|
T("Verify"), T("header has incorrect size"), file, line);
|
2006-09-01 22:59:23 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-24 14:53:29 -06:00
|
|
|
static void pool_check(const UTF8 *tag, const UTF8 *file, const int line)
|
2006-09-01 22:59:23 +00:00
|
|
|
{
|
2026-03-19 09:08:32 -06:00
|
|
|
for (int i = 0; i < NUM_POOLS; i++)
|
2006-09-01 22:59:23 +00:00
|
|
|
{
|
|
|
|
|
pool_vfy(i, tag, file, line);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-24 14:53:29 -06:00
|
|
|
UTF8 *pool_alloc(int poolnum, const UTF8 *tag, const UTF8 *file, const int line)
|
2006-09-01 22:59:23 +00:00
|
|
|
{
|
2026-03-09 14:34:03 -06:00
|
|
|
if (g_paranoid_alloc)
|
2006-09-01 22:59:23 +00:00
|
|
|
{
|
|
|
|
|
pool_check(tag, file, line);
|
|
|
|
|
}
|
|
|
|
|
|
2007-03-14 00:55:08 +00:00
|
|
|
UTF8 *p;
|
2026-03-19 09:08:32 -06:00
|
|
|
POOLHDR *ph;
|
2006-09-01 22:59:23 +00:00
|
|
|
POOLFTR *pf;
|
|
|
|
|
|
2026-07-25 20:10:03 -06:00
|
|
|
// Walk freelist; drop only corrupt entries rather than clearing the
|
|
|
|
|
// entire freelist (old path leaked every still-valid free buffer —
|
2026-07-25 20:10:18 -06:00
|
|
|
// Pass H1 residual #1290).
|
2026-07-25 20:10:03 -06:00
|
|
|
//
|
|
|
|
|
while (!pools[poolnum].free_stack.empty())
|
2006-09-01 22:59:23 +00:00
|
|
|
{
|
2026-03-19 09:08:32 -06:00
|
|
|
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)
|
2006-09-01 22:59:23 +00:00
|
|
|
{
|
2007-03-14 22:33:04 +00:00
|
|
|
pool_err(T("BUG"), LOG_ALWAYS, poolnum, tag, ph, T("Alloc"),
|
2026-03-19 09:08:32 -06:00
|
|
|
T("corrupted buffer header on freelist"), file, line);
|
2026-07-25 20:10:03 -06:00
|
|
|
pools[poolnum].num_lost++;
|
|
|
|
|
// raw is abandoned (still tracked in all_buffers for diagnostics).
|
|
|
|
|
continue;
|
2006-09-01 22:59:23 +00:00
|
|
|
}
|
2026-07-25 20:10:03 -06:00
|
|
|
|
|
|
|
|
// Check for corrupted footer, just report and fix it.
|
|
|
|
|
//
|
|
|
|
|
if (pf->magicnum != pools[poolnum].poolmagic)
|
2006-10-12 00:30:24 +00:00
|
|
|
{
|
2026-07-25 20:10:03 -06:00
|
|
|
pool_err(T("BUG"), LOG_ALWAYS, poolnum, tag, ph, T("Alloc"),
|
|
|
|
|
T("corrupted buffer footer"), file, line);
|
|
|
|
|
pf->magicnum = pools[poolnum].poolmagic;
|
|
|
|
|
}
|
2006-10-12 00:30:24 +00:00
|
|
|
|
2026-07-25 20:10:03 -06:00
|
|
|
ph->u.buf_tag = tag;
|
|
|
|
|
pools[poolnum].tot_alloc++;
|
|
|
|
|
pools[poolnum].num_alloc++;
|
2026-03-19 09:08:32 -06:00
|
|
|
|
2026-07-25 20:10:03 -06:00
|
|
|
// 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);
|
2006-10-12 00:30:24 +00:00
|
|
|
}
|
2026-07-25 20:10:03 -06:00
|
|
|
*pui = 0;
|
|
|
|
|
return p;
|
2026-03-19 09:08:32 -06:00
|
|
|
}
|
2006-10-12 00:30:24 +00:00
|
|
|
|
2026-03-19 09:08:32 -06:00
|
|
|
// Allocate a new buffer from the system.
|
|
|
|
|
//
|
|
|
|
|
char *raw = nullptr;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
raw = new char[pools[poolnum].pool_alloc_size];
|
|
|
|
|
}
|
|
|
|
|
catch (...)
|
|
|
|
|
{
|
|
|
|
|
}
|
2006-09-01 22:59:23 +00:00
|
|
|
|
2026-03-19 09:08:32 -06:00
|
|
|
if (nullptr == raw)
|
|
|
|
|
{
|
Remove ISOUTOFMEMORY macro; add per-site OOM recovery and fix g_dump_child_pid
Eliminate the ISOUTOFMEMORY macro that unconditionally aborted on allocation
failure. Each of the 23 call sites now handles OOM appropriately:
- Fatal sites (buffer pools, db array, anum table): mux_assert or OutOfMemory
- Recoverable sites (queue, mail, commands, guests, vattrs, config, restart,
forward lists): log the failure and return gracefully
Also fix g_dump_child_pid portability: volatile pid_t -> volatile sig_atomic_t
with explicit casts in ganl_adapter.cpp.
Close integer overflow issue as false alarm (getstring_noalloc uses a bounded
static buffer, so nBuffer+1 cannot wrap).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 19:31:20 -06:00
|
|
|
OutOfMemory(reinterpret_cast<const UTF8 *>(__FILE__), __LINE__);
|
2026-03-19 09:08:32 -06:00
|
|
|
return nullptr;
|
2006-09-01 22:59:23 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-19 09:08:32 -06:00
|
|
|
pools[poolnum].all_buffers.push_back(raw);
|
feat(alloc): pool memory footprint budget (defense-in-depth, off by default)
Backstop for the pool_alloc -> fatal OutOfMemory cliff
(docs/survey-resource-defenses.md gap #3). pool_alloc/pool_alloc_lbuf
take memory from the system only on the slow path (freelist empty) and
never return it, so the process's pool footprint is its PEAK CONCURRENT
buffer count. New pool_memory_limit config (bytes; K/M/G suffixes;
0=unlimited) caps that footprint: on the slow path, crossing it trips
the same cooperative per-command abort the wall-clock alarm uses
(alarm_clock.alarmed, reachable from alloc.cpp — both are libmux) so a
runaway command unwinds and frees its buffers back to the freelist
instead of the server aborting on OutOfMemory. Composes with the JIT
wall-clock alarm (same flag). cf_pool_limit pushes the value to the
libmux global on load and @readcache reload.
Off by default, and honestly so: measurement showed the cliff is HARD
TO REACH, which is the point — the interpreter's alloc-and-free
discipline, bounded nesting (func_nest_lim/nStackLimit), and freelist
reuse keep peak pool footprint tiny (a short session didn't cross even
256KB). And no non-zero default is safe across deployments (a 256MB
VPS vs a 32GB host want different ceilings), so the admin sizes it.
Note: the nested-iter OS-OOM crash seen while building the JIT alarm is
a DIFFERENT path (JIT arena/guest growth, bounded by max_dispatch=10M
by default), not this one.
Verified default-off is a true no-op: smoke 1319/1319, oracle 9/9,
jit_diff 400/0, stress 8/8; the trip path is wired (server survives +
stays responsive when the budget is crossed).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 00:08:59 -06:00
|
|
|
pool_account_system(pools[poolnum].pool_alloc_size);
|
2026-03-19 09:08:32 -06:00
|
|
|
|
|
|
|
|
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++;
|
|
|
|
|
|
2008-01-25 08:06:38 -08:00
|
|
|
ph->u.buf_tag = tag;
|
2006-09-01 22:59:23 +00:00
|
|
|
pools[poolnum].tot_alloc++;
|
|
|
|
|
pools[poolnum].num_alloc++;
|
|
|
|
|
|
2026-03-19 09:08:32 -06:00
|
|
|
auto pui = reinterpret_cast<unsigned *>(p);
|
2006-09-01 22:59:23 +00:00
|
|
|
*pui = 0;
|
|
|
|
|
return p;
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-24 14:53:29 -06:00
|
|
|
UTF8 *pool_alloc_lbuf(const UTF8 *tag, const UTF8 *file, const int line)
|
2006-09-01 22:59:23 +00:00
|
|
|
{
|
2026-03-09 14:34:03 -06:00
|
|
|
if (g_paranoid_alloc)
|
2006-09-01 22:59:23 +00:00
|
|
|
{
|
|
|
|
|
pool_check(tag, file, line);
|
|
|
|
|
}
|
|
|
|
|
|
2007-03-14 00:55:08 +00:00
|
|
|
UTF8 *p;
|
2026-03-19 09:08:32 -06:00
|
|
|
POOLHDR *ph;
|
2006-09-01 22:59:23 +00:00
|
|
|
POOLFTR *pf;
|
|
|
|
|
|
2026-07-25 20:10:18 -06:00
|
|
|
// Same freelist discipline as pool_alloc: drop only the bad entry (#1290).
|
2026-07-25 20:10:03 -06:00
|
|
|
//
|
|
|
|
|
while (!pools[POOL_LBUF].free_stack.empty())
|
2006-09-01 22:59:23 +00:00
|
|
|
{
|
2026-03-19 09:08:32 -06:00
|
|
|
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)
|
2006-09-01 22:59:23 +00:00
|
|
|
{
|
2007-03-14 22:33:04 +00:00
|
|
|
pool_err(T("BUG"), LOG_ALWAYS, POOL_LBUF, tag, ph, T("Alloc"),
|
2026-03-19 09:08:32 -06:00
|
|
|
T("corrupted buffer header on freelist"), file, line);
|
2026-07-25 20:10:03 -06:00
|
|
|
pools[POOL_LBUF].num_lost++;
|
|
|
|
|
continue;
|
2006-09-01 22:59:23 +00:00
|
|
|
}
|
2026-07-25 20:10:03 -06:00
|
|
|
|
|
|
|
|
if (pf->magicnum != pools[POOL_LBUF].poolmagic)
|
2006-10-12 00:30:24 +00:00
|
|
|
{
|
2026-07-25 20:10:03 -06:00
|
|
|
pool_err(T("BUG"), LOG_ALWAYS, POOL_LBUF, tag, ph, T("Alloc"),
|
|
|
|
|
T("corrupted buffer footer"), file, line);
|
|
|
|
|
pf->magicnum = pools[POOL_LBUF].poolmagic;
|
|
|
|
|
}
|
2006-10-12 00:30:24 +00:00
|
|
|
|
2026-07-25 20:10:03 -06:00
|
|
|
ph->u.buf_tag = tag;
|
|
|
|
|
pools[POOL_LBUF].tot_alloc++;
|
|
|
|
|
pools[POOL_LBUF].num_alloc++;
|
2026-03-19 09:08:32 -06:00
|
|
|
|
2026-07-25 20:10:03 -06:00
|
|
|
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);
|
2006-10-12 00:30:24 +00:00
|
|
|
}
|
2026-07-25 20:10:03 -06:00
|
|
|
*pui = 0;
|
|
|
|
|
return p;
|
2026-03-19 09:08:32 -06:00
|
|
|
}
|
2006-10-12 00:30:24 +00:00
|
|
|
|
2026-03-19 09:08:32 -06:00
|
|
|
char *raw = nullptr;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
raw = new char[LBUF_SIZE + sizeof(POOLHDR) + sizeof(POOLFTR)];
|
|
|
|
|
}
|
|
|
|
|
catch (...)
|
|
|
|
|
{
|
|
|
|
|
}
|
2006-09-01 22:59:23 +00:00
|
|
|
|
2026-03-19 09:08:32 -06:00
|
|
|
if (nullptr == raw)
|
|
|
|
|
{
|
Remove ISOUTOFMEMORY macro; add per-site OOM recovery and fix g_dump_child_pid
Eliminate the ISOUTOFMEMORY macro that unconditionally aborted on allocation
failure. Each of the 23 call sites now handles OOM appropriately:
- Fatal sites (buffer pools, db array, anum table): mux_assert or OutOfMemory
- Recoverable sites (queue, mail, commands, guests, vattrs, config, restart,
forward lists): log the failure and return gracefully
Also fix g_dump_child_pid portability: volatile pid_t -> volatile sig_atomic_t
with explicit casts in ganl_adapter.cpp.
Close integer overflow issue as false alarm (getstring_noalloc uses a bounded
static buffer, so nBuffer+1 cannot wrap).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 19:31:20 -06:00
|
|
|
OutOfMemory(reinterpret_cast<const UTF8 *>(__FILE__), __LINE__);
|
2026-03-19 09:08:32 -06:00
|
|
|
return nullptr;
|
2006-09-01 22:59:23 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-19 09:08:32 -06:00
|
|
|
pools[POOL_LBUF].all_buffers.push_back(raw);
|
feat(alloc): pool memory footprint budget (defense-in-depth, off by default)
Backstop for the pool_alloc -> fatal OutOfMemory cliff
(docs/survey-resource-defenses.md gap #3). pool_alloc/pool_alloc_lbuf
take memory from the system only on the slow path (freelist empty) and
never return it, so the process's pool footprint is its PEAK CONCURRENT
buffer count. New pool_memory_limit config (bytes; K/M/G suffixes;
0=unlimited) caps that footprint: on the slow path, crossing it trips
the same cooperative per-command abort the wall-clock alarm uses
(alarm_clock.alarmed, reachable from alloc.cpp — both are libmux) so a
runaway command unwinds and frees its buffers back to the freelist
instead of the server aborting on OutOfMemory. Composes with the JIT
wall-clock alarm (same flag). cf_pool_limit pushes the value to the
libmux global on load and @readcache reload.
Off by default, and honestly so: measurement showed the cliff is HARD
TO REACH, which is the point — the interpreter's alloc-and-free
discipline, bounded nesting (func_nest_lim/nStackLimit), and freelist
reuse keep peak pool footprint tiny (a short session didn't cross even
256KB). And no non-zero default is safe across deployments (a 256MB
VPS vs a 32GB host want different ceilings), so the admin sizes it.
Note: the nested-iter OS-OOM crash seen while building the JIT alarm is
a DIFFERENT path (JIT arena/guest growth, bounded by max_dispatch=10M
by default), not this one.
Verified default-off is a true no-op: smoke 1319/1319, oracle 9/9,
jit_diff 400/0, stress 8/8; the trip path is wired (server survives +
stays responsive when the budget is crossed).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 00:08:59 -06:00
|
|
|
pool_account_system(LBUF_SIZE + sizeof(POOLHDR) + sizeof(POOLFTR));
|
2026-03-19 09:08:32 -06:00
|
|
|
|
|
|
|
|
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++;
|
|
|
|
|
|
2008-01-25 08:06:38 -08:00
|
|
|
ph->u.buf_tag = tag;
|
2006-09-01 22:59:23 +00:00
|
|
|
pools[POOL_LBUF].tot_alloc++;
|
|
|
|
|
pools[POOL_LBUF].num_alloc++;
|
|
|
|
|
|
2026-03-19 09:08:32 -06:00
|
|
|
auto pui = reinterpret_cast<unsigned *>(p);
|
2006-09-01 22:59:23 +00:00
|
|
|
*pui = 0;
|
|
|
|
|
return p;
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-24 14:53:29 -06:00
|
|
|
void pool_free(int poolnum, UTF8* buf, const UTF8* file, const int line)
|
2006-09-01 22:59:23 +00:00
|
|
|
{
|
2018-10-03 17:54:51 +00:00
|
|
|
if (buf == nullptr)
|
2006-09-01 22:59:23 +00:00
|
|
|
{
|
2026-03-09 13:44:58 -06:00
|
|
|
mux_fprintf(stderr, T("BUG ALLOC: Attempt to free null pointer in %s line %d." ENDLINE), file, line);
|
2006-09-01 22:59:23 +00:00
|
|
|
return;
|
|
|
|
|
}
|
2026-03-19 09:08:32 -06:00
|
|
|
|
|
|
|
|
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);
|
2006-09-01 22:59:23 +00:00
|
|
|
|
2026-03-09 14:34:03 -06:00
|
|
|
if (g_paranoid_alloc)
|
2006-09-01 22:59:23 +00:00
|
|
|
{
|
2008-01-25 08:06:38 -08:00
|
|
|
pool_check(ph->u.buf_tag, file, line);
|
2006-09-01 22:59:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
{
|
2008-01-25 08:06:38 -08:00
|
|
|
pool_err(T("BUG"), LOG_ALWAYS, poolnum, ph->u.buf_tag, ph, T("Free"),
|
2007-03-14 22:33:04 +00:00
|
|
|
T("corrupted buffer header"), file, line);
|
2006-09-01 22:59:23 +00:00
|
|
|
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)
|
|
|
|
|
{
|
2008-01-25 08:06:38 -08:00
|
|
|
pool_err(T("BUG"), LOG_ALWAYS, poolnum, ph->u.buf_tag, ph, T("Free"),
|
2007-03-14 22:33:04 +00:00
|
|
|
T("corrupted buffer footer"), file, line);
|
2006-09-01 22:59:23 +00:00
|
|
|
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)
|
|
|
|
|
{
|
2008-01-25 08:06:38 -08:00
|
|
|
pool_err(T("BUG"), LOG_ALWAYS, poolnum, ph->u.buf_tag, ph, T("Free"),
|
2007-03-14 22:33:04 +00:00
|
|
|
T("Attempt to free into a different pool."), file, line);
|
2006-09-01 22:59:23 +00:00
|
|
|
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)
|
|
|
|
|
{
|
2008-01-25 08:06:38 -08:00
|
|
|
pool_err(T("BUG"), LOG_BUGS, poolnum, ph->u.buf_tag, ph, T("Free"),
|
2007-03-14 22:33:04 +00:00
|
|
|
T("buffer already freed"), file, line);
|
2006-09-01 22:59:23 +00:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
*pui = pools[poolnum].poolmagic;
|
2026-03-19 09:08:32 -06:00
|
|
|
pools[poolnum].free_stack.push_back(raw);
|
2006-09-01 22:59:23 +00:00
|
|
|
pools[poolnum].num_alloc--;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-24 14:53:29 -06:00
|
|
|
void pool_free_lbuf(UTF8 *buf, const UTF8 *file, const int line)
|
2006-09-01 22:59:23 +00:00
|
|
|
{
|
2018-10-03 17:54:51 +00:00
|
|
|
if (buf == nullptr)
|
2006-09-01 22:59:23 +00:00
|
|
|
{
|
2026-03-09 13:44:58 -06:00
|
|
|
mux_fprintf(stderr, T("BUG ALLOC: Attempt to free_lbuf null pointer in %s line %d." ENDLINE), file, line);
|
2006-09-01 22:59:23 +00:00
|
|
|
return;
|
|
|
|
|
}
|
2026-03-19 09:08:32 -06:00
|
|
|
|
|
|
|
|
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);
|
2006-09-01 22:59:23 +00:00
|
|
|
|
2026-03-09 14:34:03 -06:00
|
|
|
if (g_paranoid_alloc)
|
2006-09-01 22:59:23 +00:00
|
|
|
{
|
2008-01-25 08:06:38 -08:00
|
|
|
pool_check(ph->u.buf_tag, file, line);
|
2006-09-01 22:59:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
2008-01-25 08:06:38 -08:00
|
|
|
pool_err(T("BUG"), LOG_ALWAYS, POOL_LBUF, ph->u.buf_tag, ph, T("Free"),
|
2007-03-14 22:33:04 +00:00
|
|
|
T("corrupted buffer header"), file, line);
|
2006-09-01 22:59:23 +00:00
|
|
|
pools[POOL_LBUF].num_lost++;
|
|
|
|
|
pools[POOL_LBUF].num_alloc--;
|
|
|
|
|
pools[POOL_LBUF].tot_alloc--;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
else if (pf->magicnum != pools[POOL_LBUF].poolmagic)
|
|
|
|
|
{
|
2008-01-25 08:06:38 -08:00
|
|
|
pool_err(T("BUG"), LOG_ALWAYS, POOL_LBUF, ph->u.buf_tag, ph, T("Free"),
|
2007-03-14 22:33:04 +00:00
|
|
|
T("corrupted buffer footer"), file, line);
|
2006-09-01 22:59:23 +00:00
|
|
|
pf->magicnum = pools[POOL_LBUF].poolmagic;
|
|
|
|
|
}
|
|
|
|
|
else if (ph->pool_size != LBUF_SIZE)
|
|
|
|
|
{
|
2008-01-25 08:06:38 -08:00
|
|
|
pool_err(T("BUG"), LOG_ALWAYS, POOL_LBUF, ph->u.buf_tag, ph, T("Free"),
|
2007-03-14 22:33:04 +00:00
|
|
|
T("Attempt to free into a different pool."), file, line);
|
2006-09-01 22:59:23 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (*pui == pools[POOL_LBUF].poolmagic)
|
|
|
|
|
{
|
2008-01-25 08:06:38 -08:00
|
|
|
pool_err(T("BUG"), LOG_BUGS, POOL_LBUF, ph->u.buf_tag, ph, T("Free"),
|
2007-03-14 22:33:04 +00:00
|
|
|
T("buffer already freed"), file, line);
|
2006-09-01 22:59:23 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*pui = pools[POOL_LBUF].poolmagic;
|
2026-03-19 09:08:32 -06:00
|
|
|
pools[POOL_LBUF].free_stack.push_back(raw);
|
2006-09-01 22:59:23 +00:00
|
|
|
pools[POOL_LBUF].num_alloc--;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-09 19:40:40 -06:00
|
|
|
static inline void alloc_notify(dbref player, const UTF8 *msg)
|
|
|
|
|
{
|
|
|
|
|
if (g_alloc_notify_fn)
|
|
|
|
|
{
|
|
|
|
|
g_alloc_notify_fn(player, msg);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-24 14:53:29 -06:00
|
|
|
static void pool_trace(const dbref player, const int poolnum, const UTF8 *text)
|
2006-09-01 22:59:23 +00:00
|
|
|
{
|
2026-03-19 09:08:32 -06:00
|
|
|
int numfree = 0;
|
2026-03-09 19:40:40 -06:00
|
|
|
alloc_notify(player, tprintf(T("----- %s -----"), text));
|
2026-03-19 09:08:32 -06:00
|
|
|
for (char *raw : pools[poolnum].all_buffers)
|
2006-09-01 22:59:23 +00:00
|
|
|
{
|
2026-03-19 09:08:32 -06:00
|
|
|
auto ph = reinterpret_cast<POOLHDR *>(raw);
|
2006-09-01 22:59:23 +00:00
|
|
|
if (ph->magicnum != pools[poolnum].poolmagic)
|
|
|
|
|
{
|
2026-07-27 12:58:59 +00:00
|
|
|
alloc_notify(player, M_("*** CORRUPTED BUFFER HEADER, ABORTING SCAN ***"));
|
2026-03-09 19:40:40 -06:00
|
|
|
alloc_notify(player, tprintf(T("%d free %s (before corruption)"),
|
2006-09-01 22:59:23 +00:00
|
|
|
numfree, text));
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-03-19 09:08:32 -06:00
|
|
|
auto ibuf = reinterpret_cast<unsigned *>(raw + sizeof(POOLHDR));
|
2006-09-01 22:59:23 +00:00
|
|
|
if (*ibuf != pools[poolnum].poolmagic)
|
|
|
|
|
{
|
2026-03-09 19:40:40 -06:00
|
|
|
alloc_notify(player, ph->u.buf_tag);
|
2006-09-01 22:59:23 +00:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
numfree++;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-09 19:40:40 -06:00
|
|
|
alloc_notify(player, tprintf(T("%d free %s"), numfree, text));
|
2006-09-01 22:59:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void list_bufstats(dbref player)
|
|
|
|
|
{
|
2026-07-28 11:31:23 -06:00
|
|
|
// Buffer pool table (#1667 Phase 4 C4). Name is left-justified; numeric
|
2026-07-28 17:34:14 +00:00
|
|
|
// columns keep fixed-width right justification via RightJustifyNumber
|
|
|
|
|
// (mux_vsnprintf has no %* width — #1429).
|
2026-07-28 11:31:23 -06:00
|
|
|
//
|
|
|
|
|
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);
|
|
|
|
|
}
|
2006-11-05 13:43:16 +00:00
|
|
|
for (int i = 0; i < NUM_POOLS; i++)
|
2006-09-01 22:59:23 +00:00
|
|
|
{
|
2007-03-14 00:55:08 +00:00
|
|
|
UTF8 buff[MBUF_SIZE];
|
2026-07-28 11:31:23 -06:00
|
|
|
UTF8 num[24];
|
|
|
|
|
size_t pos = 0;
|
2026-07-28 17:34:14 +00:00
|
|
|
size_t nw;
|
2026-07-28 11:31:23 -06:00
|
|
|
pos = mux_table_append_ljust(buff, sizeof(buff), pos,
|
|
|
|
|
poolnames[i], kBufNameCols);
|
|
|
|
|
pos = mux_table_append_bytes(buff, sizeof(buff), pos, " ");
|
2026-07-28 17:34:14 +00:00
|
|
|
nw = RightJustifyNumber(num, kBufSizeCols,
|
|
|
|
|
static_cast<int64_t>(pools[i].pool_client_size), ' ');
|
|
|
|
|
num[nw] = '\0';
|
2026-07-28 11:31:23 -06:00
|
|
|
pos = mux_table_append_bytes(buff, sizeof(buff), pos,
|
|
|
|
|
reinterpret_cast<const char *>(num));
|
|
|
|
|
pos = mux_table_append_bytes(buff, sizeof(buff), pos, " ");
|
2026-07-28 17:34:14 +00:00
|
|
|
nw = RightJustifyNumber(num, kBufInUseCols,
|
|
|
|
|
static_cast<int64_t>(pools[i].num_alloc), ' ');
|
|
|
|
|
num[nw] = '\0';
|
2026-07-28 11:31:23 -06:00
|
|
|
pos = mux_table_append_bytes(buff, sizeof(buff), pos,
|
|
|
|
|
reinterpret_cast<const char *>(num));
|
|
|
|
|
pos = mux_table_append_bytes(buff, sizeof(buff), pos, " ");
|
2026-07-28 17:34:14 +00:00
|
|
|
nw = RightJustifyNumber(num, kBufTotalCols,
|
|
|
|
|
static_cast<int64_t>(pools[i].max_alloc), ' ');
|
|
|
|
|
num[nw] = '\0';
|
2026-07-28 11:31:23 -06:00
|
|
|
pos = mux_table_append_bytes(buff, sizeof(buff), pos,
|
|
|
|
|
reinterpret_cast<const char *>(num));
|
|
|
|
|
pos = mux_table_append_bytes(buff, sizeof(buff), pos, " ");
|
2026-07-28 17:34:14 +00:00
|
|
|
nw = RightJustifyNumber(num, kBufAllocsCols,
|
|
|
|
|
static_cast<int64_t>(pools[i].tot_alloc), ' ');
|
|
|
|
|
num[nw] = '\0';
|
2026-07-28 11:31:23 -06:00
|
|
|
pos = mux_table_append_bytes(buff, sizeof(buff), pos,
|
|
|
|
|
reinterpret_cast<const char *>(num));
|
|
|
|
|
pos = mux_table_append_bytes(buff, sizeof(buff), pos, " ");
|
2026-07-28 17:34:14 +00:00
|
|
|
nw = RightJustifyNumber(num, kBufLostCols,
|
|
|
|
|
static_cast<int64_t>(pools[i].num_lost), ' ');
|
|
|
|
|
num[nw] = '\0';
|
2026-07-28 11:31:23 -06:00
|
|
|
pos = mux_table_append_bytes(buff, sizeof(buff), pos,
|
|
|
|
|
reinterpret_cast<const char *>(num));
|
2026-03-09 19:40:40 -06:00
|
|
|
alloc_notify(player, buff);
|
2006-09-01 22:59:23 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void list_buftrace(dbref player)
|
|
|
|
|
{
|
2026-03-19 09:08:32 -06:00
|
|
|
for (int i = 0; i < NUM_POOLS; i++)
|
2006-09-01 22:59:23 +00:00
|
|
|
{
|
|
|
|
|
pool_trace(player, i, poolnames[i]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pool_reset(void)
|
|
|
|
|
{
|
2026-03-19 09:08:32 -06:00
|
|
|
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
|
2006-09-01 22:59:23 +00:00
|
|
|
{
|
2026-03-19 09:08:32 -06:00
|
|
|
if (free_set.count(raw))
|
|
|
|
|
{
|
|
|
|
|
delete[] raw;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
});
|
|
|
|
|
pool.all_buffers.erase(new_end, pool.all_buffers.end());
|
2022-03-12 16:46:50 -07:00
|
|
|
pool.max_alloc = pool.num_alloc;
|
2006-09-01 22:59:23 +00:00
|
|
|
}
|
|
|
|
|
}
|