diff --git a/src/engine/SConscript b/src/engine/SConscript index 15d3385485d..ef3aa480c33 100644 --- a/src/engine/SConscript +++ b/src/engine/SConscript @@ -32,7 +32,7 @@ def scons(): 'srv_cli.c', 'profile.c', 'rpc.c', 'server_iv.c', 'srv.c', 'srv.pb-c.c', 'sched.c', 'ult.c', 'event.pb-c.c', - 'srv_metrics.c'] + libdaos_tgts + 'srv_metrics.c', 'ref.c'] + libdaos_tgts if denv["STACK_MMAP"] == 1: denv.Append(CCFLAGS=['-DULT_MMAP_STACK']) diff --git a/src/engine/ref.c b/src/engine/ref.c new file mode 100644 index 00000000000..d6350f5c5a3 --- /dev/null +++ b/src/engine/ref.c @@ -0,0 +1,55 @@ +/* + * (C) Copyright 2026 Hewlett Packard Enterprise Development LP + * + * SPDX-License-Identifier: BSD-2-Clause-Patent + */ + +#define D_LOGFAC DD_FAC(server) + +#include + +#include + +#ifdef DAOS_WITH_REF_TRACKER + +static void +dss_ref_tracker_dumper_ult(void *arg) +{ + struct dss_ref_tracker_dumper *dumper = arg; + int n; + + for (n = 0; !dss_ult_exiting(dumper->rftd_req); n++) { + if (n % 10 == 0) + d_ref_tracker_dump(dumper->rftd_tracker, dumper->rftd_func, + dumper->rftd_line); + sched_req_sleep(dumper->rftd_req, 1000 /* ms */); + } +} + +/** Use DSS_REF_TRACKER_INIT_DUMPER instead. */ +void +dss_ref_tracker_init_dumper(struct dss_ref_tracker_dumper *dumper, struct d_ref_tracker *tracker, + const char *func, int line) +{ + uuid_t anonym_uuid; + struct sched_req_attr attr; + + uuid_clear(anonym_uuid); + sched_req_attr_init(&attr, SCHED_REQ_ANONYM, &anonym_uuid); + dumper->rftd_req = sched_create_ult(&attr, dss_ref_tracker_dumper_ult, dumper, 0); + D_ASSERT(dumper->rftd_req != NULL); + + dumper->rftd_tracker = tracker; + dumper->rftd_func = func; + dumper->rftd_line = line; +} + +/** Use DSS_REF_TRACKER_FINI_DUMPER instead. */ +void +dss_ref_tracker_fini_dumper(struct dss_ref_tracker_dumper *dumper) +{ + sched_req_wait(dumper->rftd_req, true); + sched_req_put(dumper->rftd_req); +} + +#endif /* DAOS_WITH_REF_TRACKER */ diff --git a/src/gurt/misc.c b/src/gurt/misc.c index afca34ab918..97d2b569ccd 100644 --- a/src/gurt/misc.c +++ b/src/gurt/misc.c @@ -20,8 +20,7 @@ #include #include #include - -#include +#include #include #include @@ -1718,53 +1717,170 @@ d_stand_div(double *array, int nr) return sqrt(std); } -int -d_vec_pointers_init(struct d_vec_pointers *pointers, uint32_t cap) +D_VEC_DEFINE(d_vec_pointers, void *, elem, D_) + +#ifdef DAOS_WITH_REF_TRACKER + +/* Reference tracker record */ +struct d_ref_tracker_rec { + void *rftr_addr; + uint64_t rftr_time; + const char *rftr_func; + int rftr_line; + int rftr_user; + uint64_t rftr_thread; + void *rftr_trace[5]; +}; + +D_VEC_DEFINE(d_vec_ref_tracker_rec, struct d_ref_tracker_rec, *elem, D_VEC_A_) + +/** Initialize \a tracker. */ +void +d_ref_tracker_init(struct d_ref_tracker *tracker, d_ref_tracker_cb_get_time_t get_time, + d_ref_tracker_cb_get_thread_t get_thread) { - void **buf = NULL; + int rc; - if (cap > 0) { - D_ALLOC_ARRAY(buf, cap); - if (buf == NULL) - return -DER_NOMEM; + tracker->rft_get_time = get_time; + tracker->rft_get_thread = get_thread; + + rc = d_vec_ref_tracker_rec_init(&tracker->rft_vec, 0); + D_ASSERTF(rc == 0, "d_vec_ref_tracker_rec_init(0): " DF_RC "\n", DP_RC(rc)); +} + +/** Finalize \a tracker. */ +void +d_ref_tracker_fini(struct d_ref_tracker *tracker) +{ + D_REF_TRACKER_DUMP(tracker); + D_ASSERT(tracker->rft_vec.p_len == 0); + d_vec_ref_tracker_rec_fini(&tracker->rft_vec); +} + +/** Use D_REF_TRACKER_DUMP instead. */ +void +d_ref_tracker_dump(struct d_ref_tracker *tracker, const char *func, int line) +{ + int i; + + if (tracker->rft_vec.p_len > 0) + D_INFO("%s@%d: dumping %d references:\n", func, line, tracker->rft_vec.p_len); + for (i = 0; i < tracker->rft_vec.p_len; i++) { + struct d_ref_tracker_rec *ref = &tracker->rft_vec.p_buf[i]; + char **symbols; + int j; + + D_INFO(" %p: %s@%d at " DF_X64 " (%d) by " DF_U64 "\n", ref->rftr_addr, ref->rftr_func, + ref->rftr_line, ref->rftr_time, ref->rftr_user, ref->rftr_thread); + symbols = backtrace_symbols(ref->rftr_trace, ARRAY_SIZE(ref->rftr_trace)); + D_ASSERTF(symbols != NULL, "backtrace_symbols failed\n"); + for (j = 0; j < ARRAY_SIZE(ref->rftr_trace); j++) + D_INFO(" %s\n", symbols[j]); + free(symbols); } +} - pointers->p_buf = buf; - pointers->p_cap = cap; - pointers->p_len = 0; - return 0; +static int +d_ref_tracker_find(struct d_ref_tracker *tracker, void *addr) +{ + int i; + + for (i = 0; i < tracker->rft_vec.p_len; i++) + if (tracker->rft_vec.p_buf[i].rftr_addr == addr) + return i; + return -1; } +/** Use D_REF_TRACKER_TRACK instead. */ void -d_vec_pointers_fini(struct d_vec_pointers *pointers) +d_ref_tracker_track(struct d_ref_tracker *tracker, void *addr, const char *func, int line) { - D_FREE(pointers->p_buf); - pointers->p_cap = 0; - pointers->p_len = 0; + struct d_ref_tracker_rec ref = {.rftr_addr = addr, .rftr_func = func, .rftr_line = line}; + int i; + int rc; + + D_ASSERT(addr != NULL); + + ref.rftr_time = tracker->rft_get_time(); + ref.rftr_thread = tracker->rft_get_thread(); + backtrace(ref.rftr_trace, ARRAY_SIZE(ref.rftr_trace)); + + /* + * If the address is already used by an existing reference, then the + * usual reason is that the variable storing the reference was + * destructed without a d_ref_tracker_untrack call---we have very likely leaked + * that reference. + */ + i = d_ref_tracker_find(tracker, addr); + D_ASSERTF(i == -1, "addr %p already used by %s@%d\n", addr, + tracker->rft_vec.p_buf[i].rftr_func, tracker->rft_vec.p_buf[i].rftr_line); + + rc = d_vec_ref_tracker_rec_append(&tracker->rft_vec, &ref); + D_ASSERTF(rc == 0, "d_vec_ref_tracker_rec_append: " DF_RC "\n", DP_RC(rc)); } -int -d_vec_pointers_append(struct d_vec_pointers *pointers, void *pointer) +/** Use D_REF_TRACKER_UNTRACK instead. */ +void +d_ref_tracker_untrack(struct d_ref_tracker *tracker, void *addr) { - if (pointers->p_len == pointers->p_cap) { - void **buf; - uint32_t cap; + int i; + int rc; - if (pointers->p_cap == 0) - cap = 1; - else - cap = 2 * pointers->p_cap; + D_ASSERT(addr != NULL); - D_REALLOC_ARRAY(buf, pointers->p_buf, pointers->p_cap, cap); - if (buf == NULL) - return -DER_NOMEM; + /* + * If the address is not found, then the usual reason is that we have + * copied a reference from the original variable to a new one without a + * d_ref_tracker_retrack call. + */ + i = d_ref_tracker_find(tracker, addr); + D_ASSERTF(i != -1, "addr not found\n"); - pointers->p_buf = buf; - pointers->p_cap = cap; - } + rc = d_vec_ref_tracker_rec_delete_at(&tracker->rft_vec, i, + D_VEC_F_REORDER | D_VEC_F_SHRINK); + D_ASSERTF(rc == 0, "d_vec_ref_tracker_rec_delete_at: " DF_RC "\n", DP_RC(rc)); +} - D_ASSERTF(pointers->p_len < pointers->p_cap, "%u < %u\n", pointers->p_len, pointers->p_cap); - pointers->p_buf[pointers->p_len] = pointer; - pointers->p_len++; - return 0; +/** Use D_REF_TRACKER_MOVE instead. */ +void +d_ref_tracker_retrack(struct d_ref_tracker *tracker, void *new_addr, void *old_addr, + const char *func, int line) +{ + int i; + int j; + struct d_ref_tracker_rec *ref; + int rc; + + D_ASSERT(old_addr != NULL); + D_ASSERT(new_addr != NULL); + D_ASSERT(old_addr != new_addr); + + i = d_ref_tracker_find(tracker, old_addr); + D_ASSERTF(i != -1, "old_addr not found\n"); + + j = d_ref_tracker_find(tracker, new_addr); + D_ASSERTF(j == -1, "new_addr %p already used by %s@%d\n", new_addr, + tracker->rft_vec.p_buf[j].rftr_func, tracker->rft_vec.p_buf[j].rftr_line); + + ref = &tracker->rft_vec.p_buf[i]; + ref->rftr_addr = new_addr; + ref->rftr_time = tracker->rft_get_time(); + ref->rftr_func = func; + ref->rftr_line = line; + ref->rftr_thread = tracker->rft_get_thread(); + rc = backtrace(ref->rftr_trace, ARRAY_SIZE(ref->rftr_trace)); + for (i = rc; i < ARRAY_SIZE(ref->rftr_trace); i++) + ref->rftr_trace[i] = NULL; } + +void +d_ref_tracker_set_user(struct d_ref_tracker *tracker, void *addr, int user) +{ + int i; + + i = d_ref_tracker_find(tracker, addr); + D_ASSERTF(i != -1, "addr not found\n"); + tracker->rft_vec.p_buf[i].rftr_user = user; +} + +#endif /* DAOS_WITH_REF_TRACKER */ diff --git a/src/gurt/tests/test_gurt.c b/src/gurt/tests/test_gurt.c index 92ca0aff9e1..e2a461d1348 100644 --- a/src/gurt/tests/test_gurt.c +++ b/src/gurt/tests/test_gurt.c @@ -2728,6 +2728,116 @@ test_d_rank_range_list_str(void **state) d_rank_range_list_free(range_list); } +#define assert_vec_sane(vec) \ + do { \ + assert_true((vec)->p_len >= 0); \ + assert_true((vec)->p_cap >= (vec)->p_len); \ + if ((vec)->p_cap > 0) \ + assert_non_null((vec)->p_buf); \ + } while (0) + +D_VEC_DECLARE(uint16s, uint16_t, elem); +D_VEC_DEFINE(uint16s, uint16_t, elem, D_); +D_VEC_DECLARE(uuids, struct d_uuid, *elem); +D_VEC_DEFINE(uuids, struct d_uuid, *elem, D_VEC_A_); + +static void +test_vec(void **state) +{ + struct uint16s v_uint16; + struct uuids v_uuid; + struct d_uuid e_uuids[3]; + int i; + int rc; + + rc = uint16s_init(&v_uint16, 0 /* cap */); + assert_int_equal(rc, 0); + assert_null(v_uint16.p_buf); + assert_vec_sane(&v_uint16); + uint16s_fini(&v_uint16); + + rc = uint16s_init(&v_uint16, 0 /* cap */); + assert_int_equal(rc, 0); + /* Append at 0. */ + rc = uint16s_append(&v_uint16, 0); + assert_int_equal(rc, 0); + assert_int_equal(v_uint16.p_len, 1); + assert_vec_sane(&v_uint16); + assert_true(v_uint16.p_buf[0] == 0); + /* Append at 1 and 2. */ + rc = uint16s_append(&v_uint16, 1); + assert_int_equal(rc, 0); + rc = uint16s_append(&v_uint16, 2); + assert_int_equal(rc, 0); + assert_int_equal(v_uint16.p_len, 3); + assert_vec_sane(&v_uint16); + assert_true(v_uint16.p_buf[0] == 0); + assert_true(v_uint16.p_buf[1] == 1); + assert_true(v_uint16.p_buf[2] == 2); + uint16s_fini(&v_uint16); + + rc = uint16s_init(&v_uint16, 1 /* cap */); + assert_int_equal(rc, 0); + rc = uint16s_append(&v_uint16, 0); + assert_int_equal(rc, 0); + rc = uint16s_append(&v_uint16, 1); + assert_int_equal(rc, 0); + rc = uint16s_append(&v_uint16, 2); + assert_int_equal(rc, 0); + rc = uint16s_append(&v_uint16, 3); + assert_int_equal(rc, 0); + rc = uint16s_append(&v_uint16, 4); + assert_int_equal(rc, 0); + /* Delete at 0. */ + rc = uint16s_delete_at(&v_uint16, 0, D_VEC_F_SHRINK); + assert_int_equal(rc, 0); + assert_int_equal(v_uint16.p_len, 4); + assert_vec_sane(&v_uint16); + assert_true(v_uint16.p_buf[0] == 1); + assert_true(v_uint16.p_buf[1] == 2); + assert_true(v_uint16.p_buf[2] == 3); + assert_true(v_uint16.p_buf[3] == 4); + /* Delete at 0 with D_VEC_F_REORDER. */ + rc = uint16s_delete_at(&v_uint16, 0, D_VEC_F_SHRINK | D_VEC_F_REORDER); + assert_int_equal(rc, 0); + assert_int_equal(v_uint16.p_len, 3); + assert_vec_sane(&v_uint16); + assert_true(v_uint16.p_buf[0] == 4); + assert_true(v_uint16.p_buf[1] == 2); + assert_true(v_uint16.p_buf[2] == 3); + /* Delete at the last element. Shrink. */ + rc = uint16s_delete_at(&v_uint16, 2, D_VEC_F_SHRINK); + assert_int_equal(rc, 0); + assert_int_equal(v_uint16.p_len, 2); + assert_int_equal(v_uint16.p_cap, 4); + assert_vec_sane(&v_uint16); + assert_true(v_uint16.p_buf[0] == 4); + assert_true(v_uint16.p_buf[1] == 2); + uint16s_fini(&v_uint16); + + for (i = 0; i < ARRAY_SIZE(e_uuids); i++) + uuid_generate(e_uuids[i].uuid); + rc = uuids_init(&v_uuid, 0 /* cap */); + assert_int_equal(rc, 0); + /* Append at 0. */ + rc = uuids_append(&v_uuid, &e_uuids[0]); + assert_int_equal(rc, 0); + assert_int_equal(v_uuid.p_len, 1); + assert_vec_sane(&v_uuid); + assert_true(memcmp(&v_uuid.p_buf[0], &e_uuids[0], sizeof(e_uuids[0])) == 0); + /* Append at 1 and 2. */ + rc = uuids_append(&v_uuid, &e_uuids[1]); + assert_int_equal(rc, 0); + rc = uuids_append(&v_uuid, &e_uuids[2]); + assert_int_equal(rc, 0); + assert_int_equal(v_uuid.p_len, 3); + assert_vec_sane(&v_uuid); + assert_true(memcmp(&v_uuid.p_buf[0], &e_uuids[0], sizeof(e_uuids[0])) == 0); + assert_true(memcmp(&v_uuid.p_buf[1], &e_uuids[1], sizeof(e_uuids[0])) == 0); + assert_true(memcmp(&v_uuid.p_buf[2], &e_uuids[2], sizeof(e_uuids[0])) == 0); + uuids_fini(&v_uuid); +} + int main(int argc, char **argv) { @@ -2767,7 +2877,8 @@ main(int argc, char **argv) cmocka_unit_test(test_d_setenv), cmocka_unit_test(test_d_rank_list_to_str), cmocka_unit_test(test_d_rank_range_list_create_from_ranks), - cmocka_unit_test(test_d_rank_range_list_str)}; + cmocka_unit_test(test_d_rank_range_list_str), + cmocka_unit_test(test_vec)}; d_register_alt_assert(mock_assert); diff --git a/src/include/daos_srv/daos_engine.h b/src/include/daos_srv/daos_engine.h index 68c388cab1a..b29f46a3e4f 100644 --- a/src/include/daos_srv/daos_engine.h +++ b/src/include/daos_srv/daos_engine.h @@ -807,4 +807,32 @@ dss_vos_pool_create(const char *path, unsigned char *uuid, daos_size_t scm_size, int dss_vos_pool_open(const char *path, unsigned char *uuid, unsigned int flags, daos_handle_t *pool); +struct dss_ref_tracker_dumper { + struct sched_request *rftd_req; + struct d_ref_tracker *rftd_tracker; + const char *rftd_func; + int rftd_line; +}; + +#ifdef DAOS_WITH_REF_TRACKER + +void dss_ref_tracker_init_dumper(struct dss_ref_tracker_dumper *dumper, + struct d_ref_tracker *tracker, const char *func, int line); +void dss_ref_tracker_fini_dumper(struct dss_ref_tracker_dumper *dumper); + +#define DSS_REF_TRACKER_DECLARE_DUMPER(dumper) struct dss_ref_tracker_dumper dumper + +#define DSS_REF_TRACKER_INIT_DUMPER(dumper, tracker) \ + dss_ref_tracker_init_dumper(&dumper, &tracker, __func__, __LINE__) + +#define DSS_REF_TRACKER_FINI_DUMPER(dumper) dss_ref_tracker_fini_dumper(&dumper) + +#else +#define dss_ref_tracker_init_dumper(dumper, tracker, func, line) do {} while (0) +#define dss_ref_tracker_fini_dumper(dumper) do {} while (0) +#define DSS_REF_TRACKER_DECLARE_DUMPER(dumper) +#define DSS_REF_TRACKER_INIT_DUMPER(dumper, tracker) do {} while (0) +#define DSS_REF_TRACKER_FINI_DUMPER(dumper) do {} while (0) +#endif /* DAOS_WITH_REF_TRACER */ + #endif /* __DSS_API_H__ */ diff --git a/src/include/gurt/common.h b/src/include/gurt/common.h index 5742ee3f5bf..5c9ae648bb1 100644 --- a/src/include/gurt/common.h +++ b/src/include/gurt/common.h @@ -1050,16 +1050,256 @@ d_hlc_age2sec(uint64_t hlc); uint64_t d_hlct_get(void); void d_hlct_sync(uint64_t msg); -/** Vector of pointers */ -struct d_vec_pointers { - void **p_buf; - uint32_t p_cap; - uint32_t p_len; +/** Define the type of the vector. */ +#define D_VEC_DEFINE_TYPE(name, type) \ + struct name { \ + type *p_buf; \ + int p_len; \ + int p_cap; \ + } + +#define D_VEC_A_ALLOC_ARRAY(ptr, count) \ + do { \ + ptr = calloc(count, sizeof(*ptr)); \ + } while (0) + +#define D_VEC_A_FREE(ptr) \ + do { \ + free(ptr); \ + ptr = NULL; \ + } while (0) + +#define D_VEC_A_REALLOC_ARRAY(newptr, oldptr, oldcount, count) \ + do { \ + newptr = realloc(oldptr, count * sizeof(*newptr)); \ + oldptr = NULL; \ + } while (0) + +/** + * Initialize \a vec to be an empty vector that can store \a cap elements + * without reallocating \a vec.p_buf. + * + * \param[in] vec vector + * \param[in] cap initial capacity (i.e., number of slots for elements) + * + * \return error code + */ +#define D_VEC_DECLARE_INIT(name, type) int name##_init(struct name *vec, uint32_t cap) +#define D_VEC_DEFINE_INIT(name, type, allocator) \ + D_VEC_DECLARE_INIT(name, type) \ + { \ + type *buf = NULL; \ + \ + if (cap > 0) { \ + allocator##ALLOC_ARRAY(buf, cap); \ + if (buf == NULL) \ + return -DER_NOMEM; \ + } \ + \ + vec->p_buf = buf; \ + vec->p_cap = cap; \ + vec->p_len = 0; \ + return 0; \ + } + +/** + * Finalize \a vec, which must have been initialized by the init function. + * + * \param[in] vec vector + */ +#define D_VEC_DECLARE_FINI(name, type) void name##_fini(struct name *vec) +#define D_VEC_DEFINE_FINI(name, type, allocator) \ + D_VEC_DECLARE_FINI(name, type) \ + { \ + allocator##FREE(vec->p_buf); \ + vec->p_cap = 0; \ + vec->p_len = 0; \ + } + +/** + * Append \a elem to \a vec. + * + * \param[in] vec vector + * \param[in] elem element value or pointer (see D_VEC_DECLARE) + * + * \return error code + */ +#define D_VEC_DECLARE_APPEND(name, type, elem) int name##_append(struct name *vec, type elem) +#define D_VEC_DEFINE_APPEND(name, type, elem, allocator) \ + D_VEC_DECLARE_APPEND(name, type, elem) \ + { \ + if (vec->p_len == vec->p_cap) { \ + type *buf; \ + uint32_t cap; \ + \ + if (vec->p_cap == 0) \ + cap = 1; \ + else \ + cap = 2 * vec->p_cap; \ + \ + allocator##REALLOC_ARRAY(buf, vec->p_buf, vec->p_cap, cap); \ + if (buf == NULL) \ + return -DER_NOMEM; \ + \ + vec->p_buf = buf; \ + vec->p_cap = cap; \ + } \ + \ + vec->p_buf[vec->p_len] = elem; \ + vec->p_len++; \ + return 0; \ + } + +enum d_vec_flag { + /** When deleting an element, see if the capacity can be shrunk. */ + D_VEC_F_SHRINK = 1U << 0, + /** + * When deleting an element, fill the gap with the last element, instead + * of moving all elements after the gap. + */ + D_VEC_F_REORDER = 1U << 1 }; -int d_vec_pointers_init(struct d_vec_pointers *pointers, uint32_t cap); -void d_vec_pointers_fini(struct d_vec_pointers *pointers); -int d_vec_pointers_append(struct d_vec_pointers *pointers, void *pointer); +/** + * Delete the element at index \a i in \a vec. + * + * \param[in] vec vector + * \param[in] i index in \a vec.p_buf + * \param[in] flags D_VEC_F_SHRINK or D_VEC_F_REORDER + * + * \return error code + */ +#define D_VEC_DECLARE_DELETE_AT(name, type) \ + int name##_delete_at(struct name *vec, int i, unsigned int flags) +#define D_VEC_DEFINE_DELETE_AT(name, type, allocator) \ + D_VEC_DECLARE_DELETE_AT(name, type) \ + { \ + D_ASSERTF(0 <= i && i < vec->p_len, "out of range: i=%d len=%d\n", i, vec->p_len); \ + if (i < vec->p_len - 1) { \ + if (flags & D_VEC_F_REORDER) \ + vec->p_buf[i] = vec->p_buf[vec->p_len - 1]; \ + else \ + memmove(&vec->p_buf[i], &vec->p_buf[i + 1], \ + (vec->p_len - i - 1) * sizeof(*vec->p_buf)); \ + } \ + vec->p_len--; \ + \ + if (flags & D_VEC_F_SHRINK && vec->p_len <= vec->p_cap / 4) { \ + type *buf; \ + uint32_t cap; \ + \ + cap = vec->p_cap / 2; \ + \ + allocator##REALLOC_ARRAY(buf, vec->p_buf, vec->p_cap, cap); \ + if (buf == NULL) \ + return -DER_NOMEM; \ + \ + vec->p_buf = buf; \ + vec->p_cap = cap; \ + } \ + return 0; \ + } + +/** + * Declare a vector type and the following functions. + * + * <\a name>_init(vec, cap) + * <\a name>_fini(vec) + * <\a name>_append(vec, <\a elem>) + * <\a name>_delete_at(vec, i, flags) + * + * If \a elem is elem, the append function takes a \a type elem; if \a elem is + * *elem, the append function takes a \a type *elem. + * + * \param[in] name of the vector type + * \param[in] type of elements + * \param[in] elem parameter form for <\a name>_append + */ +#define D_VEC_DECLARE(name, type, elem) \ + D_VEC_DEFINE_TYPE(name, type); \ + D_VEC_DECLARE_INIT(name, type); \ + D_VEC_DECLARE_FINI(name, type); \ + D_VEC_DECLARE_APPEND(name, type, elem); \ + D_VEC_DECLARE_DELETE_AT(name, type); + +/** + * Define a vector type and its functions. See D_VEC_DECLARE. + * + * In most cases, \a allocator shall be D_ (the standard D_FREE, etc.); for + * special cases where injection of memory allocation errors is undesirable, + * \a allocator can be D_VEC_A_ (the plain free, etc.). + */ +#define D_VEC_DEFINE(name, type, elem, allocator) \ + D_VEC_DEFINE_INIT(name, type, allocator) \ + D_VEC_DEFINE_FINI(name, type, allocator) \ + D_VEC_DEFINE_APPEND(name, type, elem, allocator) \ + D_VEC_DEFINE_DELETE_AT(name, type, allocator) + +D_VEC_DECLARE(d_vec_pointers, void *, elem) + +#ifndef DAOS_BUILD_RELEASE +#define DAOS_WITH_REF_TRACKER +#endif + +struct d_ref_tracker_rec; + +D_VEC_DECLARE(d_vec_ref_tracker_rec, struct d_ref_tracker_rec, *elem); + +typedef uint64_t (*d_ref_tracker_cb_get_time_t)(void); +typedef uint64_t (*d_ref_tracker_cb_get_thread_t)(void); + +/** + * Reference tracker. The common usage is: + * + * #ifdef DAOS_WITH_REF_TRACKER + * struct d_ref_tracker _ref_tracker + * #endif + */ +struct d_ref_tracker { + struct d_vec_ref_tracker_rec rft_vec; + d_ref_tracker_cb_get_time_t rft_get_time; + d_ref_tracker_cb_get_thread_t rft_get_thread; +}; + +#ifdef DAOS_WITH_REF_TRACKER +void d_ref_tracker_init(struct d_ref_tracker *tracker, d_ref_tracker_cb_get_time_t get_time, + d_ref_tracker_cb_get_thread_t get_thread); +void d_ref_tracker_fini(struct d_ref_tracker *tracker); +void d_ref_tracker_dump(struct d_ref_tracker *tracker, const char *func, int line); +void d_ref_tracker_track(struct d_ref_tracker *tracker, void *addr, const char *func, int line); +void d_ref_tracker_untrack(struct d_ref_tracker *tracker, void *addr); +void d_ref_tracker_retrack(struct d_ref_tracker *tracker, void *new_addr, void *old_addr, + const char *func, int line); +void d_ref_tracker_set_user(struct d_ref_tracker *tracker, void *addr, int user); +#else +#define d_ref_tracker_init(tracker, get_time, get_thread) do {} while (0) +#define d_ref_tracker_fini(tracker) do {} while (0) +#define d_ref_tracker_dump(tracker, func, line) do {} while (0) +#define d_ref_tracker_track(tracker, addr, func, line) do {} while (0) +#define d_ref_tracker_untrack(tracker, addr) do {} while (0) +#define d_ref_tracker_retrack(tracker, new_addr, old_addr, func, line) do {} while (0) +#define d_ref_tracker_set_user(tracker, addr, user) do {} while (0) +#endif + +/** Add a record for \a addr in \a tracker. */ +#define D_REF_TRACKER_TRACK(tracker, addr) d_ref_tracker_track(tracker, addr, __func__, __LINE__) + +/** Remove the record for \a addr in \a tracker. */ +#define D_REF_TRACKER_UNTRACK(tracker, addr) d_ref_tracker_untrack(tracker, addr) + +/** + * Move *\a from to *\a to and update \a tracker. NULL will be assigned to + * *\a from. + */ +#define D_REF_TRACKER_MOVE(tracker, to, from) \ + do { \ + d_ref_tracker_retrack(tracker, to, from, __func__, __LINE__); \ + *(to) = *(from); \ + *(from) = NULL; \ + } while (0) + +/** Print all references currently tracked by \a tracker. */ +#define D_REF_TRACKER_DUMP(tracker) d_ref_tracker_dump(tracker, __func__, __LINE__) /** Change the default setting for if a signal handler should be installed in crt_init() *