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