Skip to content

Commit 72350c3

Browse files
committed
mem: HG_MALLOC - module arenas: memory a module asks for and HG manages
A module whose memory has nothing to do with transactions - a cache with its own lifetime, class mix and growth - can now ask for an arena of its own instead of carving chunks out of shm and managing them itself: #include "mem/mem_arena.h" mem_arena_t *a = shm_arena_create("cachedb_perf", init, cap); /* mod_init */ shm_arena_set_profile(a, "CACHE_PROFILE"); /* optional */ p = mem_arena_malloc(a, size); mem_arena_free(a, p); The arena is an HG block like shm: slab classes with the per-process caches, block GC and cross-class re-typing, elastic growth and shrink within its own INIT:CAP, the maintenance process, its own hg_stats section and lock histograms. It is created before the fork so every child inherits the one shared mapping, independent of which allocator -a selected for the core; the ownership registry routes a free to whichever arena owns the pointer. The maintenance process now ticks every registered shared elastic arena rather than the shm block alone, hg_stats lists module arenas by name, and a process keeps cache state for up to eight arenas. With no HG_MALLOC compiled in the facade returns NULL and a module falls back to its own scheme.
1 parent 3d8c2a1 commit 72350c3

7 files changed

Lines changed: 328 additions & 28 deletions

File tree

‎mem/README.hg_malloc_v3.md‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -724,6 +724,29 @@ tune thresholds against the advice lines, flip to 0.
724724

725725
---
726726

727+
## 11b. Module arenas — memory a module asks for and HG manages
728+
729+
A module whose memory has nothing to do with transactions — a cache with
730+
its own lifetime, class mix and growth — can ask for an arena of its own
731+
instead of carving chunks out of shm and managing them itself:
732+
733+
```c
734+
#include "../../mem/mem_arena.h"
735+
mem_arena_t *a = shm_arena_create("cachedb_perf", init_bytes, cap_bytes); /* mod_init only */
736+
shm_arena_set_profile(a, "CACHE_PROFILE"); /* optional: the Section 10 grammar */
737+
p = mem_arena_malloc(a, size); mem_arena_free(a, p);
738+
```
739+
740+
The arena is an HG block like shm — slab classes, per-process caches,
741+
block GC and cross-class re-typing, elastic growth and shrink within its
742+
own `INIT:CAP`, the maintenance process (which ticks every registered
743+
shared arena), its own section in `hg_stats` and its own lock
744+
histograms — created before the fork so every child inherits the one
745+
shared mapping, independent of which allocator `-a` selected for the
746+
core. The ownership registry routes a free to whichever arena owns the
747+
pointer. With no HG_MALLOC compiled in, `shm_arena_create()` returns
748+
NULL and the module falls back to its own scheme. Up to 8 arenas.
749+
727750
## 12. pkg arenas — what is different
728751
729752
* **Per-child, post-config.** Each worker's private arena is created at

‎mem/hg_arena.c‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ static const unsigned int cell_sizes[HG_NCLASSES] = {
147147
* its own dead TLS rather than returning them - harmless here, because the
148148
* IO pool threads live for the lifetime of the process.
149149
*/
150-
#define HG_MAX_INSTANCES 4
150+
#define HG_MAX_INSTANCES 8 /* shm, pkg, shm_dbg + module arenas */
151151
static __thread struct hg_palloc palloc_slots[HG_MAX_INSTANCES];
152152

153153
/*

‎mem/hg_maint.c‎

Lines changed: 64 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
#ifdef HG_MALLOC
4747

4848
#include <unistd.h>
49+
#include <string.h>
4950

5051
#include "../dprint.h"
5152
#include "../globals.h"
@@ -61,17 +62,26 @@
6162
#define HG_MAINT_TICK_MS 1000
6263
#define HG_MAINT_SLOW_TICKS 30 /* profile + shrink cadence, in ticks */
6364

64-
static struct hg_block *maint_hb;
6565
static unsigned long maint_last_ns;
6666
static unsigned int maint_ticks;
6767

68+
/* a shared arena with a cap above its initial size: the shm arena under
69+
* -a HG_MALLOC with -m INIT:CAP, or any module arena (hg_arena_create) -
70+
* the registry has them all, and module arenas exist before the process
71+
* count (modules initialise first) */
72+
static int maint_arena(const struct hg_block *hb)
73+
{
74+
return hb && hb->shared && hb->buddy_ready && hb->hcap > hb->hsize_min;
75+
}
76+
6877
static int hg_maint_wanted(void)
6978
{
70-
if (mem_allocator_shm != MM_HG_MALLOC &&
71-
mem_allocator_shm != MM_HG_MALLOC_DBG)
72-
return 0;
73-
/* a cap above the initial size is what makes the arena elastic */
74-
return hg_shm_cap_bytes > (unsigned long)shm_mem_size;
79+
int i;
80+
81+
for (i = 0; i < HG_ARENA_REG_MAX; i++)
82+
if (maint_arena(hg_arena_reg[i].hb))
83+
return 1;
84+
return 0;
7585
}
7686

7787
int hg_maint_count_processes(void)
@@ -81,23 +91,30 @@ int hg_maint_count_processes(void)
8191

8292
static void hg_maint_tick(void)
8393
{
84-
struct hg_block *hb = maint_hb;
8594
unsigned long now = hg_now_ns();
95+
int i, slow;
8696

8797
/* the reactor returns on every IPC job too; tick once a second */
88-
if (!hb || !hb->buddy_ready ||
89-
now - maint_last_ns < (unsigned long)HG_MAINT_TICK_MS * 1000000UL)
98+
if (now - maint_last_ns < (unsigned long)HG_MAINT_TICK_MS * 1000000UL)
9099
return;
91100
maint_last_ns = now;
92-
93-
hg_lock_enter(hb, HG_LK_POLICY);
94-
hg_grow_headroom_tick(hb);
95-
if (++maint_ticks >= HG_MAINT_SLOW_TICKS) {
101+
slow = (++maint_ticks >= HG_MAINT_SLOW_TICKS);
102+
if (slow)
96103
maint_ticks = 0;
97-
hg_grow_tick(hb);
98-
hg_shrink_tick(hb);
104+
105+
for (i = 0; i < HG_ARENA_REG_MAX; i++) {
106+
struct hg_block *hb = hg_arena_reg[i].hb;
107+
108+
if (!maint_arena(hb))
109+
continue;
110+
hg_lock_enter(hb, HG_LK_POLICY);
111+
hg_grow_headroom_tick(hb);
112+
if (slow) {
113+
hg_grow_tick(hb);
114+
hg_shrink_tick(hb);
115+
}
116+
hg_lock_leave(hb);
99117
}
100-
hg_lock_leave(hb);
101118
}
102119

103120
inline static int handle_io(struct fd_map *fm, int idx, int event_type)
@@ -137,7 +154,6 @@ int hg_maint_start(void)
137154

138155
/* the child */
139156
clean_write_pipeend();
140-
maint_hb = (struct hg_block *)shm_block;
141157
if (init_worker_reactor("HG_maintenance", RCT_PRIO_MAX) < 0) {
142158
LM_ERR("failed to init the maintenance reactor\n");
143159
goto error;
@@ -146,20 +162,42 @@ int hg_maint_start(void)
146162
LM_CRIT("failed to add the IPC pipe to the maintenance reactor\n");
147163
goto error;
148164
}
149-
maint_hb->maint_proc = process_no;
150-
__atomic_store_n(&maint_hb->maint_active, 1, __ATOMIC_RELEASE);
151-
maint_last_ns = hg_now_ns();
152-
LM_NOTICE("HG maintenance process started: %s arena grows ahead of "
153-
"demand here (headroom tick every %d ms, policy every %d ticks), "
154-
"the sweep's proactive ticks stand down\n", maint_hb->name,
155-
HG_MAINT_TICK_MS, HG_MAINT_SLOW_TICKS);
165+
{
166+
int i;
167+
char names[256] = "";
168+
169+
for (i = 0; i < HG_ARENA_REG_MAX; i++) {
170+
struct hg_block *hb = hg_arena_reg[i].hb;
171+
172+
if (!maint_arena(hb))
173+
continue;
174+
hb->maint_proc = process_no;
175+
__atomic_store_n(&hb->maint_active, 1, __ATOMIC_RELEASE);
176+
if (strlen(names) + strlen(hb->name) + 2 < sizeof names) {
177+
if (*names)
178+
strcat(names, ",");
179+
strcat(names, hb->name);
180+
}
181+
}
182+
maint_last_ns = hg_now_ns();
183+
LM_NOTICE("HG maintenance process started: arena(s) %s grow ahead "
184+
"of demand here (headroom tick every %d ms, policy every %d "
185+
"ticks), the sweep's proactive ticks stand down\n", names,
186+
HG_MAINT_TICK_MS, HG_MAINT_SLOW_TICKS);
187+
}
156188

157189
reactor_main_loop(HG_MAINT_TICK_MS, error, hg_maint_tick());
158190
destroy_worker_reactor();
159191
error:
160192
LM_ERR("the HG maintenance process failed to initialise, exiting\n");
161-
if (maint_hb)
162-
__atomic_store_n(&maint_hb->maint_active, 0, __ATOMIC_RELEASE);
193+
{
194+
int i;
195+
196+
for (i = 0; i < HG_ARENA_REG_MAX; i++)
197+
if (maint_arena(hg_arena_reg[i].hb))
198+
__atomic_store_n(&hg_arena_reg[i].hb->maint_active, 0,
199+
__ATOMIC_RELEASE);
200+
}
163201
exit(-1);
164202
}
165203

‎mem/hg_malloc.c‎

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -853,6 +853,15 @@ static void hg_arena_reg_del(struct hg_block *hb)
853853
*/
854854
struct hg_block *hg_malloc_init(unsigned long size, char *name, int shared,
855855
const char *proc_desc, unsigned int flags)
856+
{
857+
/* the core arenas take their cap by name (below); a module arena
858+
* brings its own - see hg_arena_create() */
859+
return hg_malloc_init_cap(size, HG_CAP_BY_NAME, name, shared, proc_desc,
860+
flags);
861+
}
862+
863+
struct hg_block *hg_malloc_init_cap(unsigned long size, unsigned long cap_req,
864+
char *name, int shared, const char *proc_desc, unsigned int flags)
856865
{
857866
enum hg_mem_tier tier;
858867
unsigned long locked_mb;
@@ -872,7 +881,9 @@ struct hg_block *hg_malloc_init(unsigned long size, char *name, int shared,
872881
* they cannot come from the config, which is parsed only after this
873882
* arena exists (and, for tier 1, after the pool reservation is
874883
* already taken). */
875-
if (!strcmp(name, "shm"))
884+
if (cap_req != HG_CAP_BY_NAME)
885+
cap = cap_req;
886+
else if (!strcmp(name, "shm"))
876887
cap = hg_shm_cap_bytes;
877888
else if (!strcmp(name, "pkg"))
878889
cap = hg_pkg_cap_bytes;
@@ -1226,3 +1237,79 @@ void hg_status_dbg(struct hg_block *hb)
12261237
#endif
12271238

12281239
#endif /* HG_MALLOC */
1240+
1241+
/* --- module arenas ------------------------------------------------------
1242+
*
1243+
* A module that wants its own memory - a cache whose lifetime, class mix and
1244+
* growth have nothing to do with transactions - asks for an arena here and
1245+
* gets one that HG_MALLOC manages completely: the slab classes with their
1246+
* per-process caches, block GC and re-typing, elastic growth and shrink
1247+
* inside the module's own INIT:CAP, the maintenance process (which ticks
1248+
* every registered shared arena), hg_stats. The arena is created BEFORE the
1249+
* fork (mod_init), so every child inherits the one shared mapping exactly
1250+
* as it does the shm arena; the ownership registry routes a free to the
1251+
* arena that owns the pointer, whichever one it is. Independent of the -a
1252+
* choice: the core allocator can be anything while the module's cells live
1253+
* in HG.
1254+
*/
1255+
struct hg_block *hg_arena_create(char *name, unsigned long init_bytes,
1256+
unsigned long cap_bytes)
1257+
{
1258+
struct hg_block *hb;
1259+
int i, free_slot = -1;
1260+
1261+
if (!name || !*name) {
1262+
LM_ERR("module arena needs a name\n");
1263+
return NULL;
1264+
}
1265+
if (init_bytes < 2 * HG_HPS) {
1266+
LM_ERR("module arena '%s': %lu MB is below the %lu MB minimum "
1267+
"viable arena\n", name, init_bytes >> 20, (2 * HG_HPS) >> 20);
1268+
return NULL;
1269+
}
1270+
if (cap_bytes < init_bytes)
1271+
cap_bytes = init_bytes;
1272+
for (i = 0; i < HG_ARENA_REG_MAX; i++) {
1273+
if (hg_arena_reg[i].hb && hg_arena_reg[i].hb->name &&
1274+
!strcmp(hg_arena_reg[i].hb->name, name)) {
1275+
LM_ERR("module arena '%s' already exists\n", name);
1276+
return NULL;
1277+
}
1278+
if (free_slot < 0 && !hg_arena_reg[i].base)
1279+
free_slot = i;
1280+
}
1281+
if (free_slot < 0) {
1282+
LM_ERR("module arena '%s': all %d arena slots are taken\n",
1283+
name, HG_ARENA_REG_MAX);
1284+
return NULL;
1285+
}
1286+
1287+
hb = hg_malloc_init_cap(init_bytes, HG_HPS_ROUND(cap_bytes), name, 1,
1288+
NULL, 0);
1289+
if (!hb) {
1290+
LM_ERR("module arena '%s': could not reserve %lu MB (cap %lu MB)\n",
1291+
name, init_bytes >> 20, cap_bytes >> 20);
1292+
return NULL;
1293+
}
1294+
LM_NOTICE("module arena '%s': %lu MB committed, can grow to %lu MB - "
1295+
"managed by HG_MALLOC (classes, GC, growth, maintenance)\n",
1296+
name, hb->hsize >> 20, hb->hcap >> 20);
1297+
return hb;
1298+
}
1299+
1300+
/* attach an auto_scaling_profile to a module arena (mod_init, pre-fork) */
1301+
int hg_arena_set_profile(struct hg_block *hb, const char *profile_name)
1302+
{
1303+
struct scaling_profile *p;
1304+
1305+
if (!hb || !profile_name)
1306+
return -1;
1307+
p = get_scaling_profile((char *)profile_name);
1308+
if (!p) {
1309+
LM_ERR("arena '%s': '%s' does not name an auto_scaling_profile\n",
1310+
hb->name, profile_name);
1311+
return -1;
1312+
}
1313+
return hg_autoscale_apply(hb, hb->name, p, hb->hsize_min, hb->hcap);
1314+
}
1315+

‎mem/hg_malloc.h‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1232,6 +1232,16 @@ unsigned long hg_slab_recycled(struct hg_block *hb);
12321232
*/
12331233
#ifdef HG_MALLOC
12341234
int hg_register_stats(void);
1235+
1236+
/* --- module arenas (see hg_arena_create() in hg_malloc.c) --------------- */
1237+
#define HG_CAP_BY_NAME (~0UL) /* hg_malloc_init_cap: take the cap from -m/-M */
1238+
struct hg_block *hg_malloc_init_cap(unsigned long size, unsigned long cap_req,
1239+
char *name, int shared, const char *proc_desc, unsigned int flags);
1240+
/* a shared, elastic arena of the module's own; @name must outlive the block;
1241+
* pre-fork only. NULL = refused (logged) */
1242+
struct hg_block *hg_arena_create(char *name, unsigned long init_bytes,
1243+
unsigned long cap_bytes);
1244+
int hg_arena_set_profile(struct hg_block *hb, const char *profile_name);
12351245
#else
12361246
/* No-op without the allocator, so callers (statistics.c) need no #ifdef of
12371247
* their own. Building with -DHG_MALLOC removed previously failed at link with

0 commit comments

Comments
 (0)