mirror of
https://github.com/jemalloc/jemalloc.git
synced 2026-10-28 16:55:15 +08:00
The dump reads other threads' tdata->thread_name with no lock in common
with the owner's write. buf_writer_cb() does strlen() then memcpy(), so a
shrink in between embeds a NUL and prof_dump_flush() drops the rest of
the buffer.
Writers take tdata->lock; the heap dump reads under it (tdatas_mtx ->
tdata->lock order already exists). prof_try_log() already holds it.
prof_recent copies names into the node at record time instead, since
prof_recent_dump_mtx ranks above tdata->lock.
Otherwise it may lead to a broken profile output:
$ grep -C3 -r VFSRead2c | cat -A
clickhouse.jemalloc.704.4.m4.heap- t454: 1: 1301078 [0: 0] ThreadPool$
clickhouse.jemalloc.704.4.m4.heap- t455: 0: 0 [0: 0] ThreadPool$
clickhouse.jemalloc.704.4.m4.heap- t456: 1: 1301078 [0: 0] ThreadPool$
clickhouse.jemalloc.704.4.m4.heap: t457: 1: 14316 [0: 0] VFSRead2c 0xff2c2361b79c$ # <-- this one
clickhouse.jemalloc.704.4.m4.heap- t*: 2: 128 [0: 0]$
clickhouse.jemalloc.704.4.m4.heap- t313: 2: 128 [0: 0]$
clickhouse.jemalloc.704.4.m4.heap- f: 28564753249 56 64 4 3 313$
253 lines
7.3 KiB
C
253 lines
7.3 KiB
C
#include "test/jemalloc_test.h"
|
|
|
|
#include "jemalloc/internal/prof_sys.h"
|
|
|
|
static void
|
|
mallctl_thread_name_get_impl(
|
|
const char *thread_name_expected, const char *func, int line) {
|
|
const char *thread_name_old;
|
|
size_t sz;
|
|
|
|
sz = sizeof(thread_name_old);
|
|
expect_d_eq(
|
|
mallctl("thread.prof.name", (void *)&thread_name_old, &sz, NULL, 0),
|
|
0, "%s():%d: Unexpected mallctl failure reading thread.prof.name",
|
|
func, line);
|
|
expect_str_eq(thread_name_old, thread_name_expected,
|
|
"%s():%d: Unexpected thread.prof.name value", func, line);
|
|
}
|
|
|
|
static void
|
|
mallctl_thread_name_set_impl(
|
|
const char *thread_name, const char *func, int line) {
|
|
expect_d_eq(mallctl("thread.prof.name", NULL, NULL,
|
|
(void *)&thread_name, sizeof(thread_name)),
|
|
0, "%s():%d: Unexpected mallctl failure writing thread.prof.name",
|
|
func, line);
|
|
mallctl_thread_name_get_impl(thread_name, func, line);
|
|
}
|
|
|
|
#define mallctl_thread_name_get(a) \
|
|
mallctl_thread_name_get_impl(a, __func__, __LINE__)
|
|
|
|
#define mallctl_thread_name_set(a) \
|
|
mallctl_thread_name_set_impl(a, __func__, __LINE__)
|
|
|
|
TEST_BEGIN(test_prof_thread_name_validation) {
|
|
test_skip_if(!config_prof);
|
|
test_skip_if(opt_prof_sys_thread_name);
|
|
|
|
mallctl_thread_name_get("");
|
|
|
|
const char *test_name1 = "test case1";
|
|
mallctl_thread_name_set(test_name1);
|
|
|
|
/* Test name longer than the max len. */
|
|
char long_name[] =
|
|
"test case longer than expected; test case longer than expected";
|
|
expect_zu_gt(strlen(long_name), PROF_THREAD_NAME_MAX_LEN,
|
|
"Long test name not long enough");
|
|
const char *test_name_long = long_name;
|
|
expect_d_eq(mallctl("thread.prof.name", NULL, NULL,
|
|
(void *)&test_name_long, sizeof(test_name_long)),
|
|
0, "Unexpected mallctl failure from thread.prof.name");
|
|
/* Long name cut to match. */
|
|
long_name[PROF_THREAD_NAME_MAX_LEN - 1] = '\0';
|
|
mallctl_thread_name_get(test_name_long);
|
|
|
|
/* NULL input shouldn't be allowed. */
|
|
const char *test_name2 = NULL;
|
|
expect_d_eq(mallctl("thread.prof.name", NULL, NULL, (void *)&test_name2,
|
|
sizeof(test_name2)),
|
|
EINVAL, "Unexpected mallctl result writing to thread.prof.name");
|
|
|
|
/* '\n' shouldn't be allowed. */
|
|
const char *test_name3 = "test\ncase";
|
|
expect_d_eq(mallctl("thread.prof.name", NULL, NULL, (void *)&test_name3,
|
|
sizeof(test_name3)),
|
|
EINVAL,
|
|
"Unexpected mallctl result writing \"%s\" to thread.prof.name",
|
|
test_name3);
|
|
|
|
/* Simultaneous read/write shouldn't be allowed. */
|
|
const char *thread_name_old;
|
|
size_t sz = sizeof(thread_name_old);
|
|
expect_d_eq(mallctl("thread.prof.name", (void *)&thread_name_old, &sz,
|
|
(void *)&test_name1, sizeof(test_name1)),
|
|
EPERM, "Unexpected mallctl result from thread.prof.name");
|
|
|
|
mallctl_thread_name_set("");
|
|
}
|
|
TEST_END
|
|
|
|
static void *
|
|
thd_start(void *varg) {
|
|
unsigned thd_ind = *(unsigned *)varg;
|
|
char thread_name[16] = "";
|
|
unsigned i;
|
|
|
|
malloc_snprintf(thread_name, sizeof(thread_name), "thread %u", thd_ind);
|
|
|
|
mallctl_thread_name_get("");
|
|
mallctl_thread_name_set(thread_name);
|
|
|
|
#define NRESET 25
|
|
for (i = 0; i < NRESET; i++) {
|
|
expect_d_eq(mallctl("prof.reset", NULL, NULL, NULL, 0), 0,
|
|
"Unexpected error while resetting heap profile data");
|
|
mallctl_thread_name_get(thread_name);
|
|
}
|
|
|
|
mallctl_thread_name_set(thread_name);
|
|
mallctl_thread_name_set("");
|
|
|
|
return NULL;
|
|
#undef NRESET
|
|
}
|
|
|
|
TEST_BEGIN(test_prof_thread_name_threaded) {
|
|
test_skip_if(!config_prof);
|
|
test_skip_if(opt_prof_sys_thread_name);
|
|
|
|
#define NTHREADS 4
|
|
thd_t thds[NTHREADS];
|
|
unsigned thd_args[NTHREADS];
|
|
unsigned i;
|
|
|
|
for (i = 0; i < NTHREADS; i++) {
|
|
thd_args[i] = i;
|
|
thd_create(&thds[i], thd_start, (void *)&thd_args[i]);
|
|
}
|
|
for (i = 0; i < NTHREADS; i++) {
|
|
thd_join(thds[i], NULL);
|
|
}
|
|
#undef NTHREADS
|
|
}
|
|
TEST_END
|
|
|
|
#define NRENAMERS 8
|
|
#define NDUMPS 500
|
|
static const char *rename_names[] = {"aaaaaaaaaaaaaaa", "b"};
|
|
static char dump_buf[1u << 20];
|
|
static size_t dump_len;
|
|
static atomic_u_t renamers_ready;
|
|
static atomic_b_t renamers_stop;
|
|
|
|
static int
|
|
prof_dump_open_file_intercept(const char *filename, int mode) {
|
|
return open("/dev/null", O_WRONLY);
|
|
}
|
|
|
|
static ssize_t
|
|
prof_dump_write_file_intercept(int fd, const void *s, size_t len) {
|
|
assert_zu_lt(dump_len + len, sizeof(dump_buf), "Dump capture overflow");
|
|
memcpy(&dump_buf[dump_len], s, len);
|
|
dump_len += len;
|
|
return (ssize_t)len;
|
|
}
|
|
|
|
static void *
|
|
thd_rename_start(void *unused) {
|
|
size_t mib[3];
|
|
size_t miblen = sizeof(mib) / sizeof(mib[0]);
|
|
expect_d_eq(mallctlnametomib("thread.prof.name", mib, &miblen), 0,
|
|
"Unexpected mallctlnametomib failure");
|
|
for (unsigned i = 0; !atomic_load_b(&renamers_stop, ATOMIC_ACQUIRE);
|
|
i++) {
|
|
expect_d_eq(mallctlbymib(mib, miblen, NULL, NULL,
|
|
(void *)&rename_names[i % 2],
|
|
sizeof(const char *)),
|
|
0, "Unexpected mallctl failure writing thread.prof.name");
|
|
if (i == 0) {
|
|
atomic_fetch_add_u(&renamers_ready, 1, ATOMIC_RELEASE);
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static bool
|
|
name_is_one_of(const char *s, size_t len) {
|
|
for (unsigned i = 0; i < 2; i++) {
|
|
if (strlen(rename_names[i]) == len
|
|
&& memcmp(s, rename_names[i], len) == 0) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* The dump is complete and every named " t<uid>: ... [..: ..] <name>" line
|
|
* carries exactly one of rename_names.
|
|
*/
|
|
static bool
|
|
dump_is_intact(void) {
|
|
if (dump_len == 0 || dump_buf[dump_len - 1] != '\n') {
|
|
return false;
|
|
}
|
|
unsigned named = 0;
|
|
for (const char *line = strstr(dump_buf, "\n t"); line != NULL;
|
|
line = strstr(line + 1, "\n t")) {
|
|
const char *eol = strchr(line + 1, '\n');
|
|
const char *p = memchr(line, ']', (size_t)(eol - line));
|
|
if (p == NULL) {
|
|
return false;
|
|
}
|
|
if (p + 1 == eol) {
|
|
continue;
|
|
}
|
|
if (p[1] != ' ' || !name_is_one_of(p + 2, (size_t)(eol - (p + 2)))) {
|
|
return false;
|
|
}
|
|
named++;
|
|
}
|
|
return named == NRENAMERS;
|
|
}
|
|
|
|
TEST_BEGIN(test_prof_thread_name_dump_concurrent_rename) {
|
|
test_skip_if(!config_prof);
|
|
test_skip_if(opt_prof_sys_thread_name);
|
|
|
|
prof_dump_open_file_t *open_file_orig = prof_dump_open_file;
|
|
prof_dump_write_file_t *write_file_orig = prof_dump_write_file;
|
|
prof_dump_open_file = prof_dump_open_file_intercept;
|
|
prof_dump_write_file = prof_dump_write_file_intercept;
|
|
|
|
thd_t thds[NRENAMERS];
|
|
atomic_store_u(&renamers_ready, 0, ATOMIC_RELAXED);
|
|
atomic_store_b(&renamers_stop, false, ATOMIC_RELAXED);
|
|
for (unsigned i = 0; i < NRENAMERS; i++) {
|
|
thd_create(&thds[i], thd_rename_start, NULL);
|
|
}
|
|
while (atomic_load_u(&renamers_ready, ATOMIC_ACQUIRE) < NRENAMERS) {
|
|
}
|
|
|
|
const char *filename = "test_filename";
|
|
unsigned corrupted = 0;
|
|
for (unsigned i = 0; i < NDUMPS; i++) {
|
|
dump_len = 0;
|
|
expect_d_eq(mallctl("prof.dump", NULL, NULL, (void *)&filename,
|
|
sizeof(filename)),
|
|
0, "Unexpected mallctl failure during prof.dump");
|
|
dump_buf[dump_len] = '\0';
|
|
corrupted += !dump_is_intact();
|
|
}
|
|
|
|
atomic_store_b(&renamers_stop, true, ATOMIC_RELEASE);
|
|
for (unsigned i = 0; i < NRENAMERS; i++) {
|
|
thd_join(thds[i], NULL);
|
|
}
|
|
prof_dump_open_file = open_file_orig;
|
|
prof_dump_write_file = write_file_orig;
|
|
expect_u_eq(corrupted, 0, "%u of %u heap dumps are malformed",
|
|
corrupted, NDUMPS);
|
|
}
|
|
TEST_END
|
|
|
|
int
|
|
main(void) {
|
|
return test(test_prof_thread_name_validation,
|
|
test_prof_thread_name_threaded,
|
|
test_prof_thread_name_dump_concurrent_rename);
|
|
}
|