mem.c revision 1.15.2.1 1 /* $NetBSD: mem.c,v 1.15.2.1 2025/08/02 05:53:53 perseant Exp $ */
2
3 /*
4 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
5 *
6 * SPDX-License-Identifier: MPL-2.0
7 *
8 * This Source Code Form is subject to the terms of the Mozilla Public
9 * License, v. 2.0. If a copy of the MPL was not distributed with this
10 * file, you can obtain one at https://mozilla.org/MPL/2.0/.
11 *
12 * See the COPYRIGHT file distributed with this work for additional
13 * information regarding copyright ownership.
14 */
15
16 /*! \file */
17
18 #include <inttypes.h>
19 #include <limits.h>
20 #include <stdbool.h>
21 #include <stddef.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24
25 #include <isc/hash.h>
26 #include <isc/magic.h>
27 #include <isc/mem.h>
28 #include <isc/mutex.h>
29 #include <isc/once.h>
30 #include <isc/os.h>
31 #include <isc/overflow.h>
32 #include <isc/refcount.h>
33 #include <isc/strerr.h>
34 #include <isc/string.h>
35 #include <isc/types.h>
36 #include <isc/urcu.h>
37 #include <isc/util.h>
38
39 #ifdef HAVE_LIBXML2
40 #include <libxml/xmlwriter.h>
41 #define ISC_XMLCHAR (const xmlChar *)
42 #endif /* HAVE_LIBXML2 */
43
44 #ifdef HAVE_JSON_C
45 #include <json_object.h>
46 #endif /* HAVE_JSON_C */
47
48 /* On DragonFly BSD the header does not provide jemalloc API */
49 #if defined(HAVE_MALLOC_NP_H) && !defined(__DragonFly__)
50 #include <malloc_np.h>
51 #define JEMALLOC_API_SUPPORTED 1
52 #elif defined(HAVE_JEMALLOC)
53 #include <jemalloc/jemalloc.h>
54 #define JEMALLOC_API_SUPPORTED 1
55 #else
56 #include "jemalloc_shim.h"
57 #endif
58
59 #include "mem_p.h"
60
61 #define MCTXLOCK(m) LOCK(&m->lock)
62 #define MCTXUNLOCK(m) UNLOCK(&m->lock)
63
64 #ifndef ISC_MEM_DEBUGGING
65 #define ISC_MEM_DEBUGGING 0
66 #endif /* ifndef ISC_MEM_DEBUGGING */
67 unsigned int isc_mem_debugging = ISC_MEM_DEBUGGING;
68 unsigned int isc_mem_defaultflags = ISC_MEMFLAG_DEFAULT;
69
70 #define ISC_MEM_ILLEGAL_ARENA (UINT_MAX)
71
72 volatile void *isc__mem_malloc = mallocx;
73
74 /*
75 * Constants.
76 */
77
78 #define ZERO_ALLOCATION_SIZE sizeof(void *)
79 #define ALIGNMENT 8U /*%< must be a power of 2 */
80 #define ALIGNMENT_SIZE sizeof(size_info)
81 #define DEBUG_TABLE_COUNT 512U
82
83 /*
84 * Types.
85 */
86 #if ISC_MEM_TRACKLINES
87 typedef struct debuglink debuglink_t;
88 struct debuglink {
89 size_t dlsize;
90 ISC_LINK(debuglink_t) link;
91 const void *ptr;
92 size_t size;
93 unsigned int line;
94 const char file[];
95 };
96
97 typedef ISC_LIST(debuglink_t) debuglist_t;
98
99 #define FLARG_PASS , file, line
100 #define FLARG , const char *file, unsigned int line
101 #else /* if ISC_MEM_TRACKLINES */
102 #define FLARG_PASS
103 #define FLARG
104 #endif /* if ISC_MEM_TRACKLINES */
105
106 typedef struct element element;
107 struct element {
108 element *next;
109 };
110
111 #define MEM_MAGIC ISC_MAGIC('M', 'e', 'm', 'C')
112 #define VALID_CONTEXT(c) ISC_MAGIC_VALID(c, MEM_MAGIC)
113
114 /* List of all active memory contexts. */
115
116 static ISC_LIST(isc_mem_t) contexts;
117
118 static isc_once_t init_once = ISC_ONCE_INIT;
119 static isc_once_t shut_once = ISC_ONCE_INIT;
120 static isc_mutex_t contextslock;
121
122 struct isc_mem {
123 unsigned int magic;
124 unsigned int flags;
125 unsigned int jemalloc_flags;
126 unsigned int jemalloc_arena;
127 unsigned int debugging;
128 isc_mutex_t lock;
129 bool checkfree;
130 isc_refcount_t references;
131 char name[16];
132 atomic_size_t inuse;
133 atomic_bool hi_called;
134 atomic_bool is_overmem;
135 atomic_size_t hi_water;
136 atomic_size_t lo_water;
137 ISC_LIST(isc_mempool_t) pools;
138 unsigned int poolcnt;
139
140 #if ISC_MEM_TRACKLINES
141 debuglist_t *debuglist;
142 size_t debuglistcnt;
143 #endif /* if ISC_MEM_TRACKLINES */
144
145 ISC_LINK(isc_mem_t) link;
146 };
147
148 #define MEMPOOL_MAGIC ISC_MAGIC('M', 'E', 'M', 'p')
149 #define VALID_MEMPOOL(c) ISC_MAGIC_VALID(c, MEMPOOL_MAGIC)
150
151 struct isc_mempool {
152 /* always unlocked */
153 unsigned int magic;
154 isc_mem_t *mctx; /*%< our memory context */
155 ISC_LINK(isc_mempool_t) link; /*%< next pool in this mem context */
156 element *items; /*%< low water item list */
157 size_t size; /*%< size of each item on this pool */
158 size_t allocated; /*%< # of items currently given out */
159 size_t freecount; /*%< # of items on reserved list */
160 size_t freemax; /*%< # of items allowed on free list */
161 size_t fillcount; /*%< # of items to fetch on each fill */
162 /*%< Stats only. */
163 size_t gets; /*%< # of requests to this pool */
164 /*%< Debugging only. */
165 char name[16]; /*%< printed name in stats reports */
166 };
167
168 /*
169 * Private Inline-able.
170 */
171
172 #if !ISC_MEM_TRACKLINES
173 #define ADD_TRACE(mctx, ptr, size, file, line)
174 #define DELETE_TRACE(mctx, ptr, size, file, line)
175 #define ISC_MEMFUNC_SCOPE
176 #else /* if !ISC_MEM_TRACKLINES */
177 #define TRACE_OR_RECORD (ISC_MEM_DEBUGTRACE | ISC_MEM_DEBUGRECORD)
178
179 #define SHOULD_TRACE_OR_RECORD(mctx, ptr) \
180 (((mctx)->debugging & TRACE_OR_RECORD) != 0 && ptr != NULL)
181
182 #define ADD_TRACE(mctx, ptr, size, file, line) \
183 if (SHOULD_TRACE_OR_RECORD(mctx, ptr)) { \
184 add_trace_entry(mctx, ptr, size, file, line); \
185 }
186
187 #define DELETE_TRACE(mctx, ptr, size, file, line) \
188 if (SHOULD_TRACE_OR_RECORD(mctx, ptr)) { \
189 delete_trace_entry(mctx, ptr, size, file, line); \
190 }
191
192 static void
193 print_active(isc_mem_t *ctx, FILE *out);
194 #endif /* ISC_MEM_TRACKLINES */
195
196 #if ISC_MEM_TRACKLINES
197 /*!
198 * mctx must not be locked.
199 */
200 static void
201 add_trace_entry(isc_mem_t *mctx, const void *ptr, size_t size FLARG) {
202 debuglink_t *dl = NULL;
203 uint32_t hash;
204 uint32_t idx;
205
206 /*
207 * "file" needs to be copied because it can be part of a dynamically
208 * loaded plugin which would be unloaded at the time the trace is
209 * dumped. Storing "file" pointer then leads to a dangling pointer
210 * dereference and a crash.
211 */
212 size_t filelen = strlen(file) + 1;
213 size_t dlsize = STRUCT_FLEX_SIZE(dl, file, filelen);
214
215 MCTXLOCK(mctx);
216
217 if ((mctx->debugging & ISC_MEM_DEBUGTRACE) != 0) {
218 fprintf(stderr, "add %p size %zu file %s line %u mctx %p\n",
219 ptr, size, file, line, mctx);
220 }
221
222 if (mctx->debuglist == NULL) {
223 goto unlock;
224 }
225
226 #ifdef __COVERITY__
227 /*
228 * Use simple conversion from pointer to hash to avoid
229 * tainting 'ptr' due to byte swap in isc_hash32.
230 */
231 hash = (uintptr_t)ptr >> 3;
232 #else
233 hash = isc_hash32(&ptr, sizeof(ptr), true);
234 #endif
235 idx = hash % DEBUG_TABLE_COUNT;
236
237 dl = mallocx(dlsize, mctx->jemalloc_flags);
238 INSIST(dl != NULL);
239
240 ISC_LINK_INIT(dl, link);
241 dl->ptr = ptr;
242 dl->size = size;
243 dl->line = line;
244 dl->dlsize = dlsize;
245 strlcpy((char *)dl->file, file, filelen);
246
247 ISC_LIST_PREPEND(mctx->debuglist[idx], dl, link);
248 mctx->debuglistcnt++;
249 unlock:
250 MCTXUNLOCK(mctx);
251 }
252
253 static void
254 delete_trace_entry(isc_mem_t *mctx, const void *ptr, size_t size,
255 const char *file, unsigned int line) {
256 debuglink_t *dl = NULL;
257 uint32_t hash;
258 uint32_t idx;
259
260 MCTXLOCK(mctx);
261
262 if ((mctx->debugging & ISC_MEM_DEBUGTRACE) != 0) {
263 fprintf(stderr, "del %p size %zu file %s line %u mctx %p\n",
264 ptr, size, file, line, mctx);
265 }
266
267 if (mctx->debuglist == NULL) {
268 goto unlock;
269 }
270
271 #ifdef __COVERITY__
272 /*
273 * Use simple conversion from pointer to hash to avoid
274 * tainting 'ptr' due to byte swap in isc_hash32.
275 */
276 hash = (uintptr_t)ptr >> 3;
277 #else
278 hash = isc_hash32(&ptr, sizeof(ptr), true);
279 #endif
280 idx = hash % DEBUG_TABLE_COUNT;
281
282 dl = ISC_LIST_HEAD(mctx->debuglist[idx]);
283 while (dl != NULL) {
284 if (dl->ptr == ptr) {
285 ISC_LIST_UNLINK(mctx->debuglist[idx], dl, link);
286 sdallocx(dl, dl->dlsize, mctx->jemalloc_flags);
287 goto unlock;
288 }
289 dl = ISC_LIST_NEXT(dl, link);
290 }
291
292 /*
293 * If we get here, we didn't find the item on the list. We're
294 * screwed.
295 */
296 UNREACHABLE();
297 unlock:
298 MCTXUNLOCK(mctx);
299 }
300 #endif /* ISC_MEM_TRACKLINES */
301
302 #define ADJUST_ZERO_ALLOCATION_SIZE(s) \
303 if (s == 0) { \
304 s = ZERO_ALLOCATION_SIZE; \
305 }
306
307 /*!
308 * Perform a malloc, doing memory filling and overrun detection as necessary.
309 */
310 static void *
311 mem_get(isc_mem_t *ctx, size_t size, int flags) {
312 char *ret = NULL;
313
314 ADJUST_ZERO_ALLOCATION_SIZE(size);
315
316 ret = mallocx(size, flags | ctx->jemalloc_flags);
317 INSIST(ret != NULL);
318
319 if ((flags & ISC__MEM_ZERO) == 0 &&
320 (ctx->flags & ISC_MEMFLAG_FILL) != 0)
321 {
322 memset(ret, 0xbe, size); /* Mnemonic for "beef". */
323 }
324
325 return ret;
326 }
327
328 /*!
329 * Perform a free, doing memory filling and overrun detection as necessary.
330 */
331 /* coverity[+free : arg-1] */
332 static void
333 mem_put(isc_mem_t *ctx, void *mem, size_t size, int flags) {
334 ADJUST_ZERO_ALLOCATION_SIZE(size);
335
336 if ((ctx->flags & ISC_MEMFLAG_FILL) != 0) {
337 memset(mem, 0xde, size); /* Mnemonic for "dead". */
338 }
339 sdallocx(mem, size, flags | ctx->jemalloc_flags);
340 }
341
342 static void *
343 mem_realloc(isc_mem_t *ctx, void *old_ptr, size_t old_size, size_t new_size,
344 int flags) {
345 void *new_ptr = NULL;
346
347 ADJUST_ZERO_ALLOCATION_SIZE(new_size);
348
349 new_ptr = rallocx(old_ptr, new_size, flags | ctx->jemalloc_flags);
350 INSIST(new_ptr != NULL);
351
352 if ((flags & ISC__MEM_ZERO) == 0 &&
353 (ctx->flags & ISC_MEMFLAG_FILL) != 0)
354 {
355 ssize_t diff_size = new_size - old_size;
356 void *diff_ptr = (uint8_t *)new_ptr + old_size;
357 if (diff_size > 0) {
358 /* Mnemonic for "beef". */
359 memset(diff_ptr, 0xbe, diff_size);
360 }
361 }
362
363 return new_ptr;
364 }
365
366 /*!
367 * Update internal counters after a memory get.
368 */
369 static void
370 mem_getstats(isc_mem_t *ctx, size_t size) {
371 atomic_fetch_add_relaxed(&ctx->inuse, size);
372 }
373
374 /*!
375 * Update internal counters after a memory put.
376 */
377 static void
378 mem_putstats(isc_mem_t *ctx, size_t size) {
379 atomic_size_t s = atomic_fetch_sub_relaxed(&ctx->inuse, size);
380 INSIST(s >= size);
381 }
382
383 /*
384 * Private.
385 */
386
387 static bool
388 mem_jemalloc_arena_create(unsigned int *pnew_arenano) {
389 REQUIRE(pnew_arenano != NULL);
390
391 #ifdef JEMALLOC_API_SUPPORTED
392 unsigned int arenano = 0;
393 size_t len = sizeof(arenano);
394 int res = 0;
395
396 res = mallctl("arenas.create", &arenano, &len, NULL, 0);
397 if (res != 0) {
398 return false;
399 }
400
401 *pnew_arenano = arenano;
402
403 return true;
404 #else
405 *pnew_arenano = ISC_MEM_ILLEGAL_ARENA;
406 return true;
407 #endif /* JEMALLOC_API_SUPPORTED */
408 }
409
410 static bool
411 mem_jemalloc_arena_destroy(unsigned int arenano) {
412 #ifdef JEMALLOC_API_SUPPORTED
413 int res = 0;
414 char buf[256] = { 0 };
415
416 (void)snprintf(buf, sizeof(buf), "arena.%u.destroy", arenano);
417 res = mallctl(buf, NULL, NULL, NULL, 0);
418 if (res != 0) {
419 return false;
420 }
421
422 return true;
423 #else
424 UNUSED(arenano);
425 return true;
426 #endif /* JEMALLOC_API_SUPPORTED */
427 }
428
429 static void
430 mem_initialize(void) {
431 /*
432 * Check if the values copied from jemalloc still match
433 */
434 #ifdef JEMALLOC_API_SUPPORTED
435 RUNTIME_CHECK(ISC__MEM_ZERO == MALLOCX_ZERO);
436 #endif /* JEMALLOC_API_SUPPORTED */
437
438 isc_mutex_init(&contextslock);
439 ISC_LIST_INIT(contexts);
440 }
441
442 void
443 isc__mem_initialize(void) {
444 isc_once_do(&init_once, mem_initialize);
445 }
446
447 static void
448 mem_shutdown(void) {
449 bool empty;
450
451 isc__mem_checkdestroyed();
452
453 LOCK(&contextslock);
454 empty = ISC_LIST_EMPTY(contexts);
455 UNLOCK(&contextslock);
456
457 if (empty) {
458 isc_mutex_destroy(&contextslock);
459 }
460 }
461
462 void
463 isc__mem_shutdown(void) {
464 isc_once_do(&shut_once, mem_shutdown);
465 }
466
467 static void
468 mem_create(isc_mem_t **ctxp, unsigned int debugging, unsigned int flags,
469 unsigned int jemalloc_flags) {
470 isc_mem_t *ctx = NULL;
471
472 REQUIRE(ctxp != NULL && *ctxp == NULL);
473
474 ctx = mallocx(sizeof(*ctx), jemalloc_flags);
475 INSIST(ctx != NULL);
476
477 *ctx = (isc_mem_t){
478 .magic = MEM_MAGIC,
479 .debugging = debugging,
480 .flags = flags,
481 .jemalloc_flags = jemalloc_flags,
482 .jemalloc_arena = ISC_MEM_ILLEGAL_ARENA,
483 .checkfree = true,
484 };
485
486 isc_mutex_init(&ctx->lock);
487 isc_refcount_init(&ctx->references, 1);
488
489 atomic_init(&ctx->inuse, 0);
490 atomic_init(&ctx->hi_water, 0);
491 atomic_init(&ctx->lo_water, 0);
492 atomic_init(&ctx->hi_called, false);
493 atomic_init(&ctx->is_overmem, false);
494
495 ISC_LIST_INIT(ctx->pools);
496
497 #if ISC_MEM_TRACKLINES
498 if ((ctx->debugging & ISC_MEM_DEBUGRECORD) != 0) {
499 unsigned int i;
500
501 ctx->debuglist = mallocx(
502 ISC_CHECKED_MUL(DEBUG_TABLE_COUNT, sizeof(debuglist_t)),
503 jemalloc_flags);
504 INSIST(ctx->debuglist != NULL);
505
506 for (i = 0; i < DEBUG_TABLE_COUNT; i++) {
507 ISC_LIST_INIT(ctx->debuglist[i]);
508 }
509 }
510 #endif /* if ISC_MEM_TRACKLINES */
511
512 LOCK(&contextslock);
513 ISC_LIST_INITANDAPPEND(contexts, ctx, link);
514 UNLOCK(&contextslock);
515
516 *ctxp = ctx;
517 }
518
519 /*
520 * Public.
521 */
522
523 static void
524 destroy(isc_mem_t *ctx) {
525 unsigned int arena_no;
526 LOCK(&contextslock);
527 ISC_LIST_UNLINK(contexts, ctx, link);
528 UNLOCK(&contextslock);
529
530 ctx->magic = 0;
531
532 arena_no = ctx->jemalloc_arena;
533
534 INSIST(ISC_LIST_EMPTY(ctx->pools));
535
536 #if ISC_MEM_TRACKLINES
537 if (ctx->debuglist != NULL) {
538 debuglink_t *dl;
539 for (size_t i = 0; i < DEBUG_TABLE_COUNT; i++) {
540 for (dl = ISC_LIST_HEAD(ctx->debuglist[i]); dl != NULL;
541 dl = ISC_LIST_HEAD(ctx->debuglist[i]))
542 {
543 if (ctx->checkfree && dl->ptr != NULL) {
544 print_active(ctx, stderr);
545 }
546 INSIST(!ctx->checkfree || dl->ptr == NULL);
547
548 ISC_LIST_UNLINK(ctx->debuglist[i], dl, link);
549 sdallocx(dl, sizeof(*dl), ctx->jemalloc_flags);
550 }
551 }
552
553 sdallocx(
554 ctx->debuglist,
555 ISC_CHECKED_MUL(DEBUG_TABLE_COUNT, sizeof(debuglist_t)),
556 ctx->jemalloc_flags);
557 }
558 #endif /* if ISC_MEM_TRACKLINES */
559
560 isc_mutex_destroy(&ctx->lock);
561
562 if (ctx->checkfree) {
563 INSIST(atomic_load(&ctx->inuse) == 0);
564 }
565 sdallocx(ctx, sizeof(*ctx), ctx->jemalloc_flags);
566
567 if (arena_no != ISC_MEM_ILLEGAL_ARENA) {
568 RUNTIME_CHECK(mem_jemalloc_arena_destroy(arena_no) == true);
569 }
570 }
571
572 void
573 isc_mem_attach(isc_mem_t *source, isc_mem_t **targetp) {
574 REQUIRE(VALID_CONTEXT(source));
575 REQUIRE(targetp != NULL && *targetp == NULL);
576
577 isc_refcount_increment(&source->references);
578
579 *targetp = source;
580 }
581
582 void
583 isc__mem_detach(isc_mem_t **ctxp FLARG) {
584 isc_mem_t *ctx = NULL;
585
586 REQUIRE(ctxp != NULL && VALID_CONTEXT(*ctxp));
587
588 ctx = *ctxp;
589 *ctxp = NULL;
590
591 if (isc_refcount_decrement(&ctx->references) == 1) {
592 isc_refcount_destroy(&ctx->references);
593 #if ISC_MEM_TRACKLINES
594 if ((ctx->debugging & ISC_MEM_DEBUGTRACE) != 0) {
595 fprintf(stderr, "destroy mctx %p file %s line %u\n",
596 ctx, file, line);
597 }
598 #endif
599 destroy(ctx);
600 }
601 }
602
603 /*
604 * isc_mem_putanddetach() is the equivalent of:
605 *
606 * mctx = NULL;
607 * isc_mem_attach(ptr->mctx, &mctx);
608 * isc_mem_detach(&ptr->mctx);
609 * isc_mem_put(mctx, ptr, sizeof(*ptr);
610 * isc_mem_detach(&mctx);
611 */
612
613 void
614 isc__mem_putanddetach(isc_mem_t **ctxp, void *ptr, size_t size,
615 int flags FLARG) {
616 REQUIRE(ctxp != NULL && VALID_CONTEXT(*ctxp));
617 REQUIRE(ptr != NULL);
618 REQUIRE(size != 0);
619
620 isc_mem_t *ctx = *ctxp;
621 *ctxp = NULL;
622
623 isc__mem_put(ctx, ptr, size, flags FLARG_PASS);
624 isc__mem_detach(&ctx FLARG_PASS);
625 }
626
627 void
628 isc__mem_destroy(isc_mem_t **ctxp FLARG) {
629 isc_mem_t *ctx = NULL;
630
631 /*
632 * This routine provides legacy support for callers who use mctxs
633 * without attaching/detaching.
634 */
635
636 REQUIRE(ctxp != NULL && VALID_CONTEXT(*ctxp));
637
638 ctx = *ctxp;
639 *ctxp = NULL;
640
641 rcu_barrier();
642
643 #if ISC_MEM_TRACKLINES
644 if ((ctx->debugging & ISC_MEM_DEBUGTRACE) != 0) {
645 fprintf(stderr, "destroy mctx %p file %s line %u\n", ctx, file,
646 line);
647 }
648
649 if (isc_refcount_decrement(&ctx->references) > 1) {
650 print_active(ctx, stderr);
651 }
652 #else /* if ISC_MEM_TRACKLINES */
653 isc_refcount_decrementz(&ctx->references);
654 #endif /* if ISC_MEM_TRACKLINES */
655 isc_refcount_destroy(&ctx->references);
656 destroy(ctx);
657
658 *ctxp = NULL;
659 }
660
661 void *
662 isc__mem_get(isc_mem_t *ctx, size_t size, int flags FLARG) {
663 void *ptr = NULL;
664
665 REQUIRE(VALID_CONTEXT(ctx));
666
667 ptr = mem_get(ctx, size, flags);
668
669 mem_getstats(ctx, size);
670 ADD_TRACE(ctx, ptr, size, file, line);
671
672 return ptr;
673 }
674
675 void
676 isc__mem_put(isc_mem_t *ctx, void *ptr, size_t size, int flags FLARG) {
677 REQUIRE(VALID_CONTEXT(ctx));
678
679 DELETE_TRACE(ctx, ptr, size, file, line);
680
681 mem_putstats(ctx, size);
682 mem_put(ctx, ptr, size, flags);
683 }
684
685 #if ISC_MEM_TRACKLINES
686 static void
687 print_active(isc_mem_t *mctx, FILE *out) {
688 if (mctx->debuglist != NULL) {
689 debuglink_t *dl;
690 unsigned int i;
691 bool found;
692
693 fprintf(out, "Dump of all outstanding memory "
694 "allocations:\n");
695 found = false;
696 for (i = 0; i < DEBUG_TABLE_COUNT; i++) {
697 dl = ISC_LIST_HEAD(mctx->debuglist[i]);
698
699 if (dl != NULL) {
700 found = true;
701 }
702
703 while (dl != NULL) {
704 if (dl->ptr != NULL) {
705 fprintf(out,
706 "\tptr %p size %zu "
707 "file %s "
708 "line %u\n",
709 dl->ptr, dl->size, dl->file,
710 dl->line);
711 }
712 dl = ISC_LIST_NEXT(dl, link);
713 }
714 }
715
716 if (!found) {
717 fprintf(out, "\tNone.\n");
718 }
719 }
720 }
721 #endif /* if ISC_MEM_TRACKLINES */
722
723 /*
724 * Print the stats[] on the stream "out" with suitable formatting.
725 */
726 void
727 isc_mem_stats(isc_mem_t *ctx, FILE *out) {
728 isc_mempool_t *pool = NULL;
729
730 REQUIRE(VALID_CONTEXT(ctx));
731
732 MCTXLOCK(ctx);
733
734 /*
735 * Note that since a pool can be locked now, these stats might
736 * be somewhat off if the pool is in active use at the time the
737 * stats are dumped. The link fields are protected by the
738 * isc_mem_t's lock, however, so walking this list and
739 * extracting integers from stats fields is always safe.
740 */
741 pool = ISC_LIST_HEAD(ctx->pools);
742 if (pool != NULL) {
743 fprintf(out, "[Pool statistics]\n");
744 fprintf(out, "%15s %10s %10s %10s %10s %10s %10s %1s\n", "name",
745 "size", "allocated", "freecount", "freemax",
746 "fillcount", "gets", "L");
747 }
748 while (pool != NULL) {
749 fprintf(out,
750 "%15s %10zu %10zu %10zu %10zu %10zu %10zu %10zu %s\n",
751 pool->name, pool->size, (size_t)0, pool->allocated,
752 pool->freecount, pool->freemax, pool->fillcount,
753 pool->gets, "N");
754 pool = ISC_LIST_NEXT(pool, link);
755 }
756
757 #if ISC_MEM_TRACKLINES
758 print_active(ctx, out);
759 #endif /* if ISC_MEM_TRACKLINES */
760
761 MCTXUNLOCK(ctx);
762 }
763
764 void *
765 isc__mem_allocate(isc_mem_t *ctx, size_t size, int flags FLARG) {
766 void *ptr = NULL;
767
768 REQUIRE(VALID_CONTEXT(ctx));
769
770 ptr = mem_get(ctx, size, flags);
771
772 /* Recalculate the real allocated size */
773 size = sallocx(ptr, flags | ctx->jemalloc_flags);
774
775 mem_getstats(ctx, size);
776 ADD_TRACE(ctx, ptr, size, file, line);
777
778 return ptr;
779 }
780
781 void *
782 isc__mem_reget(isc_mem_t *ctx, void *old_ptr, size_t old_size, size_t new_size,
783 int flags FLARG) {
784 void *new_ptr = NULL;
785
786 if (old_ptr == NULL) {
787 REQUIRE(old_size == 0);
788 new_ptr = isc__mem_get(ctx, new_size, flags FLARG_PASS);
789 } else if (new_size == 0) {
790 isc__mem_put(ctx, old_ptr, old_size, flags FLARG_PASS);
791 } else {
792 DELETE_TRACE(ctx, old_ptr, old_size, file, line);
793 mem_putstats(ctx, old_size);
794
795 new_ptr = mem_realloc(ctx, old_ptr, old_size, new_size, flags);
796
797 mem_getstats(ctx, new_size);
798 ADD_TRACE(ctx, new_ptr, new_size, file, line);
799
800 /*
801 * We want to postpone the call to water in edge case
802 * where the realloc will exactly hit on the boundary of
803 * the water and we would call water twice.
804 */
805 }
806
807 return new_ptr;
808 }
809
810 void *
811 isc__mem_reallocate(isc_mem_t *ctx, void *old_ptr, size_t new_size,
812 int flags FLARG) {
813 void *new_ptr = NULL;
814
815 REQUIRE(VALID_CONTEXT(ctx));
816
817 if (old_ptr == NULL) {
818 new_ptr = isc__mem_allocate(ctx, new_size, flags FLARG_PASS);
819 } else if (new_size == 0) {
820 isc__mem_free(ctx, old_ptr, flags FLARG_PASS);
821 } else {
822 size_t old_size = sallocx(old_ptr, flags | ctx->jemalloc_flags);
823
824 DELETE_TRACE(ctx, old_ptr, old_size, file, line);
825 mem_putstats(ctx, old_size);
826
827 new_ptr = mem_realloc(ctx, old_ptr, old_size, new_size, flags);
828
829 /* Recalculate the real allocated size */
830 new_size = sallocx(new_ptr, flags | ctx->jemalloc_flags);
831
832 mem_getstats(ctx, new_size);
833 ADD_TRACE(ctx, new_ptr, new_size, file, line);
834
835 /*
836 * We want to postpone the call to water in edge case
837 * where the realloc will exactly hit on the boundary of
838 * the water and we would call water twice.
839 */
840 }
841
842 return new_ptr;
843 }
844
845 void
846 isc__mem_free(isc_mem_t *ctx, void *ptr, int flags FLARG) {
847 size_t size = 0;
848
849 REQUIRE(VALID_CONTEXT(ctx));
850 REQUIRE(ptr != NULL);
851
852 size = sallocx(ptr, flags | ctx->jemalloc_flags);
853
854 DELETE_TRACE(ctx, ptr, size, file, line);
855
856 mem_putstats(ctx, size);
857 mem_put(ctx, ptr, size, flags);
858 }
859
860 /*
861 * Other useful things.
862 */
863
864 char *
865 isc__mem_strdup(isc_mem_t *mctx, const char *s FLARG) {
866 size_t len;
867 char *ns = NULL;
868
869 REQUIRE(VALID_CONTEXT(mctx));
870 REQUIRE(s != NULL);
871
872 len = strlen(s) + 1;
873
874 ns = isc__mem_allocate(mctx, len, 0 FLARG_PASS);
875
876 strlcpy(ns, s, len);
877
878 return ns;
879 }
880
881 char *
882 isc__mem_strndup(isc_mem_t *mctx, const char *s, size_t size FLARG) {
883 size_t len;
884 char *ns = NULL;
885
886 REQUIRE(VALID_CONTEXT(mctx));
887 REQUIRE(s != NULL);
888 REQUIRE(size != 0);
889
890 len = strlen(s) + 1;
891 if (len > size) {
892 len = size;
893 }
894
895 ns = isc__mem_allocate(mctx, len, 0 FLARG_PASS);
896
897 strlcpy(ns, s, len);
898
899 return ns;
900 }
901
902 void
903 isc_mem_setdestroycheck(isc_mem_t *ctx, bool flag) {
904 REQUIRE(VALID_CONTEXT(ctx));
905
906 MCTXLOCK(ctx);
907
908 ctx->checkfree = flag;
909
910 MCTXUNLOCK(ctx);
911 }
912
913 size_t
914 isc_mem_inuse(isc_mem_t *ctx) {
915 REQUIRE(VALID_CONTEXT(ctx));
916
917 return atomic_load_relaxed(&ctx->inuse);
918 }
919
920 void
921 isc_mem_clearwater(isc_mem_t *mctx) {
922 isc_mem_setwater(mctx, 0, 0);
923 }
924
925 void
926 isc_mem_setwater(isc_mem_t *ctx, size_t hiwater, size_t lowater) {
927 REQUIRE(VALID_CONTEXT(ctx));
928 REQUIRE(hiwater >= lowater);
929
930 atomic_store_release(&ctx->hi_water, hiwater);
931 atomic_store_release(&ctx->lo_water, lowater);
932
933 return;
934 }
935
936 bool
937 isc_mem_isovermem(isc_mem_t *ctx) {
938 REQUIRE(VALID_CONTEXT(ctx));
939
940 bool is_overmem = atomic_load_relaxed(&ctx->is_overmem);
941
942 if (!is_overmem) {
943 /* We are not overmem, check whether we should be? */
944 size_t hiwater = atomic_load_relaxed(&ctx->hi_water);
945 if (hiwater == 0) {
946 return false;
947 }
948
949 size_t inuse = atomic_load_relaxed(&ctx->inuse);
950 if (inuse <= hiwater) {
951 return false;
952 }
953
954 if ((isc_mem_debugging & ISC_MEM_DEBUGUSAGE) != 0) {
955 fprintf(stderr,
956 "overmem mctx %p inuse %zu hi_water %zu\n", ctx,
957 inuse, hiwater);
958 }
959
960 atomic_store_relaxed(&ctx->is_overmem, true);
961 return true;
962 } else {
963 /* We are overmem, check whether we should not be? */
964 size_t lowater = atomic_load_relaxed(&ctx->lo_water);
965 if (lowater == 0) {
966 return false;
967 }
968
969 size_t inuse = atomic_load_relaxed(&ctx->inuse);
970 if (inuse >= lowater) {
971 return true;
972 }
973
974 if ((isc_mem_debugging & ISC_MEM_DEBUGUSAGE) != 0) {
975 fprintf(stderr,
976 "overmem mctx %p inuse %zu lo_water %zu\n", ctx,
977 inuse, lowater);
978 }
979 atomic_store_relaxed(&ctx->is_overmem, false);
980 return false;
981 }
982 }
983
984 void
985 isc_mem_setname(isc_mem_t *ctx, const char *name) {
986 REQUIRE(VALID_CONTEXT(ctx));
987
988 LOCK(&ctx->lock);
989 strlcpy(ctx->name, name, sizeof(ctx->name));
990 UNLOCK(&ctx->lock);
991 }
992
993 const char *
994 isc_mem_getname(isc_mem_t *ctx) {
995 REQUIRE(VALID_CONTEXT(ctx));
996
997 if (ctx->name[0] == 0) {
998 return "";
999 }
1000
1001 return ctx->name;
1002 }
1003
1004 /*
1005 * Memory pool stuff
1006 */
1007
1008 void
1009 isc__mempool_create(isc_mem_t *restrict mctx, const size_t element_size,
1010 isc_mempool_t **restrict mpctxp FLARG) {
1011 isc_mempool_t *restrict mpctx = NULL;
1012 size_t size = element_size;
1013
1014 REQUIRE(VALID_CONTEXT(mctx));
1015 REQUIRE(size > 0U);
1016 REQUIRE(mpctxp != NULL && *mpctxp == NULL);
1017
1018 /*
1019 * Mempools are stored as a linked list of element.
1020 */
1021 if (size < sizeof(element)) {
1022 size = sizeof(element);
1023 }
1024
1025 /*
1026 * Allocate space for this pool, initialize values, and if all
1027 * works well, attach to the memory context.
1028 */
1029 mpctx = isc_mem_get(mctx, sizeof(isc_mempool_t));
1030
1031 *mpctx = (isc_mempool_t){
1032 .size = size,
1033 .freemax = 1,
1034 .fillcount = 1,
1035 };
1036
1037 #if ISC_MEM_TRACKLINES
1038 if ((mctx->debugging & ISC_MEM_DEBUGTRACE) != 0) {
1039 fprintf(stderr, "create pool %p file %s line %u mctx %p\n",
1040 mpctx, file, line, mctx);
1041 }
1042 #endif /* ISC_MEM_TRACKLINES */
1043
1044 isc_mem_attach(mctx, &mpctx->mctx);
1045 mpctx->magic = MEMPOOL_MAGIC;
1046
1047 *mpctxp = (isc_mempool_t *)mpctx;
1048
1049 MCTXLOCK(mctx);
1050 ISC_LIST_INITANDAPPEND(mctx->pools, mpctx, link);
1051 mctx->poolcnt++;
1052 MCTXUNLOCK(mctx);
1053 }
1054
1055 void
1056 isc_mempool_setname(isc_mempool_t *restrict mpctx, const char *name) {
1057 REQUIRE(VALID_MEMPOOL(mpctx));
1058 REQUIRE(name != NULL);
1059
1060 strlcpy(mpctx->name, name, sizeof(mpctx->name));
1061 }
1062
1063 void
1064 isc__mempool_destroy(isc_mempool_t **restrict mpctxp FLARG) {
1065 isc_mempool_t *restrict mpctx = NULL;
1066 isc_mem_t *mctx = NULL;
1067 element *restrict item = NULL;
1068
1069 REQUIRE(mpctxp != NULL);
1070 REQUIRE(VALID_MEMPOOL(*mpctxp));
1071
1072 mpctx = *mpctxp;
1073 *mpctxp = NULL;
1074
1075 mctx = mpctx->mctx;
1076
1077 #if ISC_MEM_TRACKLINES
1078 if ((mctx->debugging & ISC_MEM_DEBUGTRACE) != 0) {
1079 fprintf(stderr, "destroy pool %p file %s line %u mctx %p\n",
1080 mpctx, file, line, mctx);
1081 }
1082 #endif
1083
1084 if (mpctx->allocated > 0) {
1085 UNEXPECTED_ERROR("mempool %s leaked memory", mpctx->name);
1086 }
1087 REQUIRE(mpctx->allocated == 0);
1088
1089 /*
1090 * Return any items on the free list
1091 */
1092 while (mpctx->items != NULL) {
1093 INSIST(mpctx->freecount > 0);
1094 mpctx->freecount--;
1095
1096 item = mpctx->items;
1097 mpctx->items = item->next;
1098
1099 mem_putstats(mctx, mpctx->size);
1100 mem_put(mctx, item, mpctx->size, 0);
1101 }
1102
1103 /*
1104 * Remove our linked list entry from the memory context.
1105 */
1106 MCTXLOCK(mctx);
1107 ISC_LIST_UNLINK(mctx->pools, mpctx, link);
1108 mctx->poolcnt--;
1109 MCTXUNLOCK(mctx);
1110
1111 mpctx->magic = 0;
1112
1113 isc_mem_putanddetach(&mpctx->mctx, mpctx, sizeof(isc_mempool_t));
1114 }
1115
1116 void *
1117 isc__mempool_get(isc_mempool_t *restrict mpctx FLARG) {
1118 element *restrict item = NULL;
1119
1120 REQUIRE(VALID_MEMPOOL(mpctx));
1121
1122 mpctx->allocated++;
1123
1124 if (mpctx->items == NULL) {
1125 isc_mem_t *mctx = mpctx->mctx;
1126 #if !__SANITIZE_ADDRESS__
1127 const size_t fillcount = mpctx->fillcount;
1128 #else
1129 const size_t fillcount = 1;
1130 #endif
1131 /*
1132 * We need to dip into the well. Fill up our free list.
1133 */
1134 for (size_t i = 0; i < fillcount; i++) {
1135 item = mem_get(mctx, mpctx->size, 0);
1136 mem_getstats(mctx, mpctx->size);
1137 item->next = mpctx->items;
1138 mpctx->items = item;
1139 mpctx->freecount++;
1140 }
1141 }
1142
1143 INSIST(mpctx->items != NULL);
1144 item = mpctx->items;
1145
1146 mpctx->items = item->next;
1147
1148 INSIST(mpctx->freecount > 0);
1149 mpctx->freecount--;
1150 mpctx->gets++;
1151
1152 ADD_TRACE(mpctx->mctx, item, mpctx->size, file, line);
1153
1154 return item;
1155 }
1156
1157 /* coverity[+free : arg-1] */
1158 void
1159 isc__mempool_put(isc_mempool_t *restrict mpctx, void *mem FLARG) {
1160 element *restrict item = NULL;
1161
1162 REQUIRE(VALID_MEMPOOL(mpctx));
1163 REQUIRE(mem != NULL);
1164
1165 isc_mem_t *mctx = mpctx->mctx;
1166 const size_t freecount = mpctx->freecount;
1167 #if !__SANITIZE_ADDRESS__
1168 const size_t freemax = mpctx->freemax;
1169 #else
1170 const size_t freemax = 0;
1171 #endif
1172
1173 INSIST(mpctx->allocated > 0);
1174 mpctx->allocated--;
1175
1176 DELETE_TRACE(mctx, mem, mpctx->size, file, line);
1177
1178 /*
1179 * If our free list is full, return this to the mctx directly.
1180 */
1181 if (freecount >= freemax) {
1182 mem_putstats(mctx, mpctx->size);
1183 mem_put(mctx, mem, mpctx->size, 0);
1184 return;
1185 }
1186
1187 /*
1188 * Otherwise, attach it to our free list and bump the counter.
1189 */
1190 item = (element *)mem;
1191 item->next = mpctx->items;
1192 mpctx->items = item;
1193 mpctx->freecount++;
1194 }
1195
1196 /*
1197 * Quotas
1198 */
1199
1200 void
1201 isc_mempool_setfreemax(isc_mempool_t *restrict mpctx,
1202 const unsigned int limit) {
1203 REQUIRE(VALID_MEMPOOL(mpctx));
1204 mpctx->freemax = limit;
1205 }
1206
1207 unsigned int
1208 isc_mempool_getfreemax(isc_mempool_t *restrict mpctx) {
1209 REQUIRE(VALID_MEMPOOL(mpctx));
1210
1211 return mpctx->freemax;
1212 }
1213
1214 unsigned int
1215 isc_mempool_getfreecount(isc_mempool_t *restrict mpctx) {
1216 REQUIRE(VALID_MEMPOOL(mpctx));
1217
1218 return mpctx->freecount;
1219 }
1220
1221 unsigned int
1222 isc_mempool_getallocated(isc_mempool_t *restrict mpctx) {
1223 REQUIRE(VALID_MEMPOOL(mpctx));
1224
1225 return mpctx->allocated;
1226 }
1227
1228 void
1229 isc_mempool_setfillcount(isc_mempool_t *restrict mpctx,
1230 unsigned int const limit) {
1231 REQUIRE(VALID_MEMPOOL(mpctx));
1232 REQUIRE(limit > 0);
1233
1234 mpctx->fillcount = limit;
1235 }
1236
1237 unsigned int
1238 isc_mempool_getfillcount(isc_mempool_t *restrict mpctx) {
1239 REQUIRE(VALID_MEMPOOL(mpctx));
1240
1241 return mpctx->fillcount;
1242 }
1243
1244 /*
1245 * Requires contextslock to be held by caller.
1246 */
1247 #if ISC_MEM_TRACKLINES
1248 static void
1249 print_contexts(FILE *file) {
1250 isc_mem_t *ctx;
1251
1252 for (ctx = ISC_LIST_HEAD(contexts); ctx != NULL;
1253 ctx = ISC_LIST_NEXT(ctx, link))
1254 {
1255 fprintf(file, "context: %p (%s): %" PRIuFAST32 " references\n",
1256 ctx, ctx->name[0] == 0 ? "<unknown>" : ctx->name,
1257 isc_refcount_current(&ctx->references));
1258 print_active(ctx, file);
1259 }
1260 fflush(file);
1261 }
1262 #endif
1263
1264 static atomic_uintptr_t checkdestroyed = 0;
1265
1266 void
1267 isc_mem_checkdestroyed(FILE *file) {
1268 atomic_store_release(&checkdestroyed, (uintptr_t)file);
1269 }
1270
1271 void
1272 isc__mem_checkdestroyed(void) {
1273 FILE *file = (FILE *)atomic_load_acquire(&checkdestroyed);
1274
1275 if (file == NULL) {
1276 return;
1277 }
1278
1279 LOCK(&contextslock);
1280 if (!ISC_LIST_EMPTY(contexts)) {
1281 #if ISC_MEM_TRACKLINES
1282 if ((isc_mem_debugging & TRACE_OR_RECORD) != 0) {
1283 print_contexts(file);
1284 }
1285 #endif /* if ISC_MEM_TRACKLINES */
1286 UNREACHABLE();
1287 }
1288 UNLOCK(&contextslock);
1289 }
1290
1291 unsigned int
1292 isc_mem_references(isc_mem_t *ctx) {
1293 return isc_refcount_current(&ctx->references);
1294 }
1295
1296 #ifdef HAVE_LIBXML2
1297 #define TRY0(a) \
1298 do { \
1299 xmlrc = (a); \
1300 if (xmlrc < 0) \
1301 goto error; \
1302 } while (0)
1303 static int
1304 xml_renderctx(isc_mem_t *ctx, size_t *inuse, xmlTextWriterPtr writer) {
1305 REQUIRE(VALID_CONTEXT(ctx));
1306
1307 int xmlrc;
1308
1309 MCTXLOCK(ctx);
1310
1311 TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "context"));
1312
1313 TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "id"));
1314 TRY0(xmlTextWriterWriteFormatString(writer, "%p", ctx));
1315 TRY0(xmlTextWriterEndElement(writer)); /* id */
1316
1317 if (ctx->name[0] != 0) {
1318 TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "name"));
1319 TRY0(xmlTextWriterWriteFormatString(writer, "%s", ctx->name));
1320 TRY0(xmlTextWriterEndElement(writer)); /* name */
1321 }
1322
1323 TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "references"));
1324 TRY0(xmlTextWriterWriteFormatString(
1325 writer, "%" PRIuFAST32,
1326 isc_refcount_current(&ctx->references)));
1327 TRY0(xmlTextWriterEndElement(writer)); /* references */
1328
1329 *inuse += isc_mem_inuse(ctx);
1330 TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "inuse"));
1331 TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "",
1332 (uint64_t)isc_mem_inuse(ctx)));
1333 TRY0(xmlTextWriterEndElement(writer)); /* inuse */
1334
1335 TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "malloced"));
1336 TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "",
1337 (uint64_t)isc_mem_inuse(ctx)));
1338 TRY0(xmlTextWriterEndElement(writer)); /* malloced */
1339
1340 TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "pools"));
1341 TRY0(xmlTextWriterWriteFormatString(writer, "%u", ctx->poolcnt));
1342 TRY0(xmlTextWriterEndElement(writer)); /* pools */
1343
1344 TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "hiwater"));
1345 TRY0(xmlTextWriterWriteFormatString(
1346 writer, "%" PRIu64 "",
1347 (uint64_t)atomic_load_relaxed(&ctx->hi_water)));
1348 TRY0(xmlTextWriterEndElement(writer)); /* hiwater */
1349
1350 TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "lowater"));
1351 TRY0(xmlTextWriterWriteFormatString(
1352 writer, "%" PRIu64 "",
1353 (uint64_t)atomic_load_relaxed(&ctx->lo_water)));
1354 TRY0(xmlTextWriterEndElement(writer)); /* lowater */
1355
1356 TRY0(xmlTextWriterEndElement(writer)); /* context */
1357
1358 error:
1359 MCTXUNLOCK(ctx);
1360
1361 return xmlrc;
1362 }
1363
1364 int
1365 isc_mem_renderxml(void *writer0) {
1366 isc_mem_t *ctx;
1367 size_t inuse = 0;
1368 int xmlrc;
1369 xmlTextWriterPtr writer = (xmlTextWriterPtr)writer0;
1370
1371 TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "contexts"));
1372
1373 LOCK(&contextslock);
1374 for (ctx = ISC_LIST_HEAD(contexts); ctx != NULL;
1375 ctx = ISC_LIST_NEXT(ctx, link))
1376 {
1377 xmlrc = xml_renderctx(ctx, &inuse, writer);
1378 if (xmlrc < 0) {
1379 UNLOCK(&contextslock);
1380 goto error;
1381 }
1382 }
1383 UNLOCK(&contextslock);
1384
1385 TRY0(xmlTextWriterEndElement(writer)); /* contexts */
1386
1387 TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "summary"));
1388
1389 TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "Malloced"));
1390 TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "",
1391 (uint64_t)inuse));
1392 TRY0(xmlTextWriterEndElement(writer)); /* malloced */
1393
1394 TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "InUse"));
1395 TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "",
1396 (uint64_t)inuse));
1397 TRY0(xmlTextWriterEndElement(writer)); /* InUse */
1398
1399 TRY0(xmlTextWriterEndElement(writer)); /* summary */
1400 error:
1401 return xmlrc;
1402 }
1403
1404 #endif /* HAVE_LIBXML2 */
1405
1406 #ifdef HAVE_JSON_C
1407 #define CHECKMEM(m) RUNTIME_CHECK(m != NULL)
1408
1409 static isc_result_t
1410 json_renderctx(isc_mem_t *ctx, size_t *inuse, json_object *array) {
1411 REQUIRE(VALID_CONTEXT(ctx));
1412 REQUIRE(array != NULL);
1413
1414 json_object *ctxobj, *obj;
1415 char buf[1024];
1416
1417 MCTXLOCK(ctx);
1418
1419 *inuse += isc_mem_inuse(ctx);
1420
1421 ctxobj = json_object_new_object();
1422 CHECKMEM(ctxobj);
1423
1424 snprintf(buf, sizeof(buf), "%p", ctx);
1425 obj = json_object_new_string(buf);
1426 CHECKMEM(obj);
1427 json_object_object_add(ctxobj, "id", obj);
1428
1429 if (ctx->name[0] != 0) {
1430 obj = json_object_new_string(ctx->name);
1431 CHECKMEM(obj);
1432 json_object_object_add(ctxobj, "name", obj);
1433 }
1434
1435 obj = json_object_new_int64(isc_refcount_current(&ctx->references));
1436 CHECKMEM(obj);
1437 json_object_object_add(ctxobj, "references", obj);
1438
1439 obj = json_object_new_int64(isc_mem_inuse(ctx));
1440 CHECKMEM(obj);
1441 json_object_object_add(ctxobj, "malloced", obj);
1442
1443 obj = json_object_new_int64(isc_mem_inuse(ctx));
1444 CHECKMEM(obj);
1445 json_object_object_add(ctxobj, "inuse", obj);
1446
1447 obj = json_object_new_int64(ctx->poolcnt);
1448 CHECKMEM(obj);
1449 json_object_object_add(ctxobj, "pools", obj);
1450
1451 obj = json_object_new_int64(atomic_load_relaxed(&ctx->hi_water));
1452 CHECKMEM(obj);
1453 json_object_object_add(ctxobj, "hiwater", obj);
1454
1455 obj = json_object_new_int64(atomic_load_relaxed(&ctx->lo_water));
1456 CHECKMEM(obj);
1457 json_object_object_add(ctxobj, "lowater", obj);
1458
1459 MCTXUNLOCK(ctx);
1460 json_object_array_add(array, ctxobj);
1461 return ISC_R_SUCCESS;
1462 }
1463
1464 isc_result_t
1465 isc_mem_renderjson(void *memobj0) {
1466 isc_result_t result = ISC_R_SUCCESS;
1467 isc_mem_t *ctx;
1468 size_t inuse = 0;
1469 json_object *ctxarray, *obj;
1470 json_object *memobj = (json_object *)memobj0;
1471
1472 ctxarray = json_object_new_array();
1473 CHECKMEM(ctxarray);
1474
1475 LOCK(&contextslock);
1476 for (ctx = ISC_LIST_HEAD(contexts); ctx != NULL;
1477 ctx = ISC_LIST_NEXT(ctx, link))
1478 {
1479 result = json_renderctx(ctx, &inuse, ctxarray);
1480 if (result != ISC_R_SUCCESS) {
1481 UNLOCK(&contextslock);
1482 goto error;
1483 }
1484 }
1485 UNLOCK(&contextslock);
1486
1487 obj = json_object_new_int64(inuse);
1488 CHECKMEM(obj);
1489 json_object_object_add(memobj, "InUse", obj);
1490
1491 obj = json_object_new_int64(inuse);
1492 CHECKMEM(obj);
1493 json_object_object_add(memobj, "Malloced", obj);
1494
1495 json_object_object_add(memobj, "contexts", ctxarray);
1496 return ISC_R_SUCCESS;
1497
1498 error:
1499 if (ctxarray != NULL) {
1500 json_object_put(ctxarray);
1501 }
1502 return result;
1503 }
1504 #endif /* HAVE_JSON_C */
1505
1506 void
1507 isc__mem_create(isc_mem_t **mctxp FLARG) {
1508 mem_create(mctxp, isc_mem_debugging, isc_mem_defaultflags, 0);
1509 #if ISC_MEM_TRACKLINES
1510 if ((isc_mem_debugging & ISC_MEM_DEBUGTRACE) != 0) {
1511 fprintf(stderr, "create mctx %p file %s line %u\n", *mctxp,
1512 file, line);
1513 }
1514 #endif /* ISC_MEM_TRACKLINES */
1515 }
1516
1517 void
1518 isc__mem_create_arena(isc_mem_t **mctxp FLARG) {
1519 unsigned int arena_no = ISC_MEM_ILLEGAL_ARENA;
1520
1521 RUNTIME_CHECK(mem_jemalloc_arena_create(&arena_no));
1522
1523 /*
1524 * We use MALLOCX_TCACHE_NONE to bypass the tcache and route
1525 * allocations directly to the arena. That is a recommendation
1526 * from jemalloc developers:
1527 *
1528 * https://github.com/jemalloc/jemalloc/issues/2483#issuecomment-1698173849
1529 */
1530 mem_create(mctxp, isc_mem_debugging, isc_mem_defaultflags,
1531 arena_no == ISC_MEM_ILLEGAL_ARENA
1532 ? 0
1533 : MALLOCX_ARENA(arena_no) | MALLOCX_TCACHE_NONE);
1534 (*mctxp)->jemalloc_arena = arena_no;
1535 #if ISC_MEM_TRACKLINES
1536 if ((isc_mem_debugging & ISC_MEM_DEBUGTRACE) != 0) {
1537 fprintf(stderr,
1538 "create mctx %p file %s line %u for jemalloc arena "
1539 "%u\n",
1540 *mctxp, file, line, arena_no);
1541 }
1542 #endif /* ISC_MEM_TRACKLINES */
1543 }
1544
1545 #ifdef JEMALLOC_API_SUPPORTED
1546 static bool
1547 jemalloc_set_ssize_value(const char *valname, ssize_t newval) {
1548 int ret;
1549
1550 ret = mallctl(valname, NULL, NULL, &newval, sizeof(newval));
1551 return ret == 0;
1552 }
1553 #endif /* JEMALLOC_API_SUPPORTED */
1554
1555 static isc_result_t
1556 mem_set_arena_ssize_value(isc_mem_t *mctx, const char *arena_valname,
1557 const ssize_t newval) {
1558 REQUIRE(VALID_CONTEXT(mctx));
1559 #ifdef JEMALLOC_API_SUPPORTED
1560 bool ret;
1561 char buf[256] = { 0 };
1562
1563 if (mctx->jemalloc_arena == ISC_MEM_ILLEGAL_ARENA) {
1564 return ISC_R_UNEXPECTED;
1565 }
1566
1567 (void)snprintf(buf, sizeof(buf), "arena.%u.%s", mctx->jemalloc_arena,
1568 arena_valname);
1569
1570 ret = jemalloc_set_ssize_value(buf, newval);
1571
1572 if (!ret) {
1573 return ISC_R_FAILURE;
1574 }
1575
1576 return ISC_R_SUCCESS;
1577 #else
1578 UNUSED(arena_valname);
1579 UNUSED(newval);
1580 return ISC_R_NOTIMPLEMENTED;
1581 #endif
1582 }
1583
1584 isc_result_t
1585 isc_mem_arena_set_muzzy_decay_ms(isc_mem_t *mctx, const ssize_t decay_ms) {
1586 return mem_set_arena_ssize_value(mctx, "muzzy_decay_ms", decay_ms);
1587 }
1588
1589 isc_result_t
1590 isc_mem_arena_set_dirty_decay_ms(isc_mem_t *mctx, const ssize_t decay_ms) {
1591 return mem_set_arena_ssize_value(mctx, "dirty_decay_ms", decay_ms);
1592 }
1593
1594 void
1595 isc__mem_printactive(isc_mem_t *ctx, FILE *file) {
1596 #if ISC_MEM_TRACKLINES
1597 REQUIRE(VALID_CONTEXT(ctx));
1598 REQUIRE(file != NULL);
1599
1600 print_active(ctx, file);
1601 #else /* if ISC_MEM_TRACKLINES */
1602 UNUSED(ctx);
1603 UNUSED(file);
1604 #endif /* if ISC_MEM_TRACKLINES */
1605 }
1606