Push error handling logic out of core dumping logic

This commit is contained in:
Yinan Zhang 2020-03-31 10:00:37 -07:00
parent f541871f5d
commit 5d292b5660
6 changed files with 346 additions and 224 deletions

View File

@ -228,6 +228,7 @@ TESTS_UNIT := \
$(srcroot)test/unit/prof_gdump.c \ $(srcroot)test/unit/prof_gdump.c \
$(srcroot)test/unit/prof_idump.c \ $(srcroot)test/unit/prof_idump.c \
$(srcroot)test/unit/prof_log.c \ $(srcroot)test/unit/prof_log.c \
$(srcroot)test/unit/prof_mdump.c \
$(srcroot)test/unit/prof_recent.c \ $(srcroot)test/unit/prof_recent.c \
$(srcroot)test/unit/prof_reset.c \ $(srcroot)test/unit/prof_reset.c \
$(srcroot)test/unit/prof_tctx.c \ $(srcroot)test/unit/prof_tctx.c \

View File

@ -98,7 +98,7 @@ typedef int (prof_dump_open_file_t)(const char *, int);
extern prof_dump_open_file_t *JET_MUTABLE prof_dump_open_file; extern prof_dump_open_file_t *JET_MUTABLE prof_dump_open_file;
typedef ssize_t (prof_dump_write_file_t)(int, const void *, size_t); typedef ssize_t (prof_dump_write_file_t)(int, const void *, size_t);
extern prof_dump_write_file_t *JET_MUTABLE prof_dump_write_file; extern prof_dump_write_file_t *JET_MUTABLE prof_dump_write_file;
typedef bool (prof_dump_header_t)(tsdn_t *, bool, const prof_cnt_t *); typedef void (prof_dump_header_t)(tsdn_t *, const prof_cnt_t *);
extern prof_dump_header_t *JET_MUTABLE prof_dump_header; extern prof_dump_header_t *JET_MUTABLE prof_dump_header;
typedef int (prof_dump_open_maps_t)(); typedef int (prof_dump_open_maps_t)();
extern prof_dump_open_maps_t *JET_MUTABLE prof_dump_open_maps; extern prof_dump_open_maps_t *JET_MUTABLE prof_dump_open_maps;

View File

@ -55,6 +55,20 @@ static ckh_t bt2gctx;
*/ */
static prof_tdata_tree_t tdatas; static prof_tdata_tree_t tdatas;
/* The following are needed for dumping and are protected by prof_dump_mtx. */
/*
* Whether there has been an error in the dumping process, which could have
* happened either in file opening or in file writing. When an error has
* already occurred, we will stop further writing to the file.
*/
static bool prof_dump_error;
/*
* Whether error should be handled locally: if true, then we print out error
* message as well as abort (if opt_abort is true) when an error occurred, and
* we also report the error back to the caller in the end; if false, then we
* only report the error back to the caller in the end.
*/
static bool prof_dump_handle_error_locally;
/* /*
* This buffer is rather large for stack allocation, so use a single buffer for * This buffer is rather large for stack allocation, so use a single buffer for
* all profile dumps. * all profile dumps.
@ -459,6 +473,30 @@ prof_bt_count(void) {
return bt_count; return bt_count;
} }
static void
prof_dump_check_possible_error(bool err_cond, const char *format, ...) {
assert(!prof_dump_error);
if (!err_cond) {
return;
}
prof_dump_error = true;
if (!prof_dump_handle_error_locally) {
return;
}
va_list ap;
char buf[PROF_PRINTF_BUFSIZE];
va_start(ap, format);
malloc_vsnprintf(buf, sizeof(buf), format, ap);
va_end(ap);
malloc_write(buf);
if (opt_abort) {
abort();
}
}
static int static int
prof_dump_open_file_impl(const char *filename, int mode) { prof_dump_open_file_impl(const char *filename, int mode) {
return creat(filename, mode); return creat(filename, mode);
@ -466,61 +504,37 @@ prof_dump_open_file_impl(const char *filename, int mode) {
prof_dump_open_file_t *JET_MUTABLE prof_dump_open_file = prof_dump_open_file_t *JET_MUTABLE prof_dump_open_file =
prof_dump_open_file_impl; prof_dump_open_file_impl;
static int static void
prof_dump_open(bool propagate_err, const char *filename) { prof_dump_open(const char *filename) {
int fd; prof_dump_fd = prof_dump_open_file(filename, 0644);
prof_dump_check_possible_error(prof_dump_fd == -1,
fd = prof_dump_open_file(filename, 0644); "<jemalloc>: failed to open \"%s\"\n", filename);
if (fd == -1 && !propagate_err) {
malloc_printf("<jemalloc>: failed to open \"%s\"\n", filename);
if (opt_abort) {
abort();
}
}
return fd;
} }
prof_dump_write_file_t *JET_MUTABLE prof_dump_write_file = malloc_write_fd; prof_dump_write_file_t *JET_MUTABLE prof_dump_write_file = malloc_write_fd;
static bool static void
prof_dump_flush(bool propagate_err) { prof_dump_flush() {
bool ret = false;
ssize_t err;
cassert(config_prof); cassert(config_prof);
if (!prof_dump_error) {
err = prof_dump_write_file(prof_dump_fd, prof_dump_buf, ssize_t err = prof_dump_write_file(prof_dump_fd, prof_dump_buf,
prof_dump_buf_end); prof_dump_buf_end);
if (err == -1) { prof_dump_check_possible_error(err == -1,
if (!propagate_err) { "<jemalloc>: failed to write during heap profile flush\n");
malloc_write("<jemalloc>: failed to write during heap "
"profile flush\n");
if (opt_abort) {
abort();
}
}
ret = true;
} }
prof_dump_buf_end = 0; prof_dump_buf_end = 0;
return ret;
} }
static bool static void
prof_dump_close(bool propagate_err) { prof_dump_close() {
bool ret; if (prof_dump_fd != -1) {
prof_dump_flush();
assert(prof_dump_fd != -1); close(prof_dump_fd);
ret = prof_dump_flush(propagate_err); }
close(prof_dump_fd);
prof_dump_fd = -1;
return ret;
} }
static bool static void
prof_dump_write(bool propagate_err, const char *s) { prof_dump_write(const char *s) {
size_t i, slen, n; size_t i, slen, n;
cassert(config_prof); cassert(config_prof);
@ -530,9 +544,7 @@ prof_dump_write(bool propagate_err, const char *s) {
while (i < slen) { while (i < slen) {
/* Flush the buffer if it is full. */ /* Flush the buffer if it is full. */
if (prof_dump_buf_end == PROF_DUMP_BUFSIZE) { if (prof_dump_buf_end == PROF_DUMP_BUFSIZE) {
if (prof_dump_flush(propagate_err) && propagate_err) { prof_dump_flush();
return true;
}
} }
if (prof_dump_buf_end + slen - i <= PROF_DUMP_BUFSIZE) { if (prof_dump_buf_end + slen - i <= PROF_DUMP_BUFSIZE) {
@ -547,23 +559,18 @@ prof_dump_write(bool propagate_err, const char *s) {
i += n; i += n;
} }
assert(i == slen); assert(i == slen);
return false;
} }
JEMALLOC_FORMAT_PRINTF(2, 3) JEMALLOC_FORMAT_PRINTF(1, 2)
static bool static void
prof_dump_printf(bool propagate_err, const char *format, ...) { prof_dump_printf(const char *format, ...) {
bool ret;
va_list ap; va_list ap;
char buf[PROF_PRINTF_BUFSIZE]; char buf[PROF_PRINTF_BUFSIZE];
va_start(ap, format); va_start(ap, format);
malloc_vsnprintf(buf, sizeof(buf), format, ap); malloc_vsnprintf(buf, sizeof(buf), format, ap);
va_end(ap); va_end(ap);
ret = prof_dump_write(propagate_err, buf); prof_dump_write(buf);
return ret;
} }
static void static void
@ -630,17 +637,10 @@ prof_tctx_merge_iter(prof_tctx_tree_t *tctxs, prof_tctx_t *tctx, void *arg) {
return NULL; return NULL;
} }
struct prof_tctx_dump_iter_arg_s {
tsdn_t *tsdn;
bool propagate_err;
};
static prof_tctx_t * static prof_tctx_t *
prof_tctx_dump_iter(prof_tctx_tree_t *tctxs, prof_tctx_t *tctx, void *opaque) { prof_tctx_dump_iter(prof_tctx_tree_t *tctxs, prof_tctx_t *tctx, void *arg) {
struct prof_tctx_dump_iter_arg_s *arg = tsdn_t *tsdn = (tsdn_t *)arg;
(struct prof_tctx_dump_iter_arg_s *)opaque; malloc_mutex_assert_owner(tsdn, tctx->gctx->lock);
malloc_mutex_assert_owner(arg->tsdn, tctx->gctx->lock);
switch (tctx->state) { switch (tctx->state) {
case prof_tctx_state_initializing: case prof_tctx_state_initializing:
@ -649,13 +649,11 @@ prof_tctx_dump_iter(prof_tctx_tree_t *tctxs, prof_tctx_t *tctx, void *opaque) {
break; break;
case prof_tctx_state_dumping: case prof_tctx_state_dumping:
case prof_tctx_state_purgatory: case prof_tctx_state_purgatory:
if (prof_dump_printf(arg->propagate_err, prof_dump_printf(
" t%"FMTu64": %"FMTu64": %"FMTu64" [%"FMTu64": " " t%"FMTu64": %"FMTu64": %"FMTu64" [%"FMTu64": "
"%"FMTu64"]\n", tctx->thr_uid, tctx->dump_cnts.curobjs, "%"FMTu64"]\n", tctx->thr_uid, tctx->dump_cnts.curobjs,
tctx->dump_cnts.curbytes, tctx->dump_cnts.accumobjs, tctx->dump_cnts.curbytes, tctx->dump_cnts.accumobjs,
tctx->dump_cnts.accumbytes)) { tctx->dump_cnts.accumbytes);
return tctx;
}
break; break;
default: default:
not_reached(); not_reached();
@ -817,53 +815,37 @@ prof_tdata_merge_iter(prof_tdata_tree_t *tdatas, prof_tdata_t *tdata,
static prof_tdata_t * static prof_tdata_t *
prof_tdata_dump_iter(prof_tdata_tree_t *tdatas, prof_tdata_t *tdata, prof_tdata_dump_iter(prof_tdata_tree_t *tdatas, prof_tdata_t *tdata,
void *arg) { void *unused) {
bool propagate_err = *(bool *)arg;
if (!tdata->dumping) { if (!tdata->dumping) {
return NULL; return NULL;
} }
if (prof_dump_printf(propagate_err, prof_dump_printf(
" t%"FMTu64": %"FMTu64": %"FMTu64" [%"FMTu64": %"FMTu64"]%s%s\n", " t%"FMTu64": %"FMTu64": %"FMTu64" [%"FMTu64": %"FMTu64"]%s%s\n",
tdata->thr_uid, tdata->cnt_summed.curobjs, tdata->thr_uid, tdata->cnt_summed.curobjs,
tdata->cnt_summed.curbytes, tdata->cnt_summed.accumobjs, tdata->cnt_summed.curbytes, tdata->cnt_summed.accumobjs,
tdata->cnt_summed.accumbytes, tdata->cnt_summed.accumbytes,
(tdata->thread_name != NULL) ? " " : "", (tdata->thread_name != NULL) ? " " : "",
(tdata->thread_name != NULL) ? tdata->thread_name : "")) { (tdata->thread_name != NULL) ? tdata->thread_name : "");
return tdata;
}
return NULL; return NULL;
} }
static bool static void
prof_dump_header_impl(tsdn_t *tsdn, bool propagate_err, prof_dump_header_impl(tsdn_t *tsdn, const prof_cnt_t *cnt_all) {
const prof_cnt_t *cnt_all) { prof_dump_printf("heap_v2/%"FMTu64"\n"
bool ret;
if (prof_dump_printf(propagate_err,
"heap_v2/%"FMTu64"\n"
" t*: %"FMTu64": %"FMTu64" [%"FMTu64": %"FMTu64"]\n", " t*: %"FMTu64": %"FMTu64" [%"FMTu64": %"FMTu64"]\n",
((uint64_t)1U << lg_prof_sample), cnt_all->curobjs, ((uint64_t)1U << lg_prof_sample), cnt_all->curobjs,
cnt_all->curbytes, cnt_all->accumobjs, cnt_all->accumbytes)) { cnt_all->curbytes, cnt_all->accumobjs, cnt_all->accumbytes);
return true;
}
malloc_mutex_lock(tsdn, &tdatas_mtx); malloc_mutex_lock(tsdn, &tdatas_mtx);
ret = (tdata_tree_iter(&tdatas, NULL, prof_tdata_dump_iter, tdata_tree_iter(&tdatas, NULL, prof_tdata_dump_iter, NULL);
(void *)&propagate_err) != NULL);
malloc_mutex_unlock(tsdn, &tdatas_mtx); malloc_mutex_unlock(tsdn, &tdatas_mtx);
return ret;
} }
prof_dump_header_t *JET_MUTABLE prof_dump_header = prof_dump_header_impl; prof_dump_header_t *JET_MUTABLE prof_dump_header = prof_dump_header_impl;
static bool static void
prof_dump_gctx(tsdn_t *tsdn, bool propagate_err, prof_gctx_t *gctx, prof_dump_gctx(tsdn_t *tsdn, prof_gctx_t *gctx, const prof_bt_t *bt,
const prof_bt_t *bt, prof_gctx_tree_t *gctxs) { prof_gctx_tree_t *gctxs) {
bool ret;
unsigned i;
struct prof_tctx_dump_iter_arg_s prof_tctx_dump_iter_arg;
cassert(config_prof); cassert(config_prof);
malloc_mutex_assert_owner(tsdn, gctx->lock); malloc_mutex_assert_owner(tsdn, gctx->lock);
@ -874,42 +856,21 @@ prof_dump_gctx(tsdn_t *tsdn, bool propagate_err, prof_gctx_t *gctx,
assert(gctx->cnt_summed.curbytes == 0); assert(gctx->cnt_summed.curbytes == 0);
assert(gctx->cnt_summed.accumobjs == 0); assert(gctx->cnt_summed.accumobjs == 0);
assert(gctx->cnt_summed.accumbytes == 0); assert(gctx->cnt_summed.accumbytes == 0);
ret = false; return;
goto label_return;
} }
if (prof_dump_printf(propagate_err, "@")) { prof_dump_write("@");
ret = true; for (unsigned i = 0; i < bt->len; i++) {
goto label_return; prof_dump_printf(" %#"FMTxPTR, (uintptr_t)bt->vec[i]);
}
for (i = 0; i < bt->len; i++) {
if (prof_dump_printf(propagate_err, " %#"FMTxPTR,
(uintptr_t)bt->vec[i])) {
ret = true;
goto label_return;
}
} }
if (prof_dump_printf(propagate_err, prof_dump_printf(
"\n" "\n t*: %"FMTu64": %"FMTu64" [%"FMTu64": %"FMTu64"]\n",
" t*: %"FMTu64": %"FMTu64" [%"FMTu64": %"FMTu64"]\n",
gctx->cnt_summed.curobjs, gctx->cnt_summed.curbytes, gctx->cnt_summed.curobjs, gctx->cnt_summed.curbytes,
gctx->cnt_summed.accumobjs, gctx->cnt_summed.accumbytes)) { gctx->cnt_summed.accumobjs, gctx->cnt_summed.accumbytes);
ret = true;
goto label_return;
}
prof_tctx_dump_iter_arg.tsdn = tsdn; tctx_tree_iter(&gctx->tctxs, NULL, prof_tctx_dump_iter,
prof_tctx_dump_iter_arg.propagate_err = propagate_err; (void *)tsdn);
if (tctx_tree_iter(&gctx->tctxs, NULL, prof_tctx_dump_iter,
(void *)&prof_tctx_dump_iter_arg) != NULL) {
ret = true;
goto label_return;
}
ret = false;
label_return:
return ret;
} }
#ifndef _WIN32 #ifndef _WIN32
@ -959,45 +920,26 @@ prof_dump_open_maps_impl() {
prof_dump_open_maps_t *JET_MUTABLE prof_dump_open_maps = prof_dump_open_maps_t *JET_MUTABLE prof_dump_open_maps =
prof_dump_open_maps_impl; prof_dump_open_maps_impl;
static bool static void
prof_dump_maps(bool propagate_err) { prof_dump_maps() {
bool ret;
int mfd = prof_dump_open_maps(); int mfd = prof_dump_open_maps();
if (mfd == -1) {
return;
}
if (mfd != -1) { prof_dump_write("\nMAPPED_LIBRARIES:\n");
ssize_t nread; ssize_t nread = 0;
do {
if (prof_dump_write(propagate_err, "\nMAPPED_LIBRARIES:\n") && prof_dump_buf_end += nread;
propagate_err) { if (prof_dump_buf_end == PROF_DUMP_BUFSIZE) {
ret = true; /* Make space in prof_dump_buf before read(). */
goto label_return; prof_dump_flush();
} }
nread = 0; nread = malloc_read_fd(mfd, &prof_dump_buf[prof_dump_buf_end],
do { PROF_DUMP_BUFSIZE - prof_dump_buf_end);
prof_dump_buf_end += nread; } while (nread > 0);
if (prof_dump_buf_end == PROF_DUMP_BUFSIZE) {
/* Make space in prof_dump_buf before read(). */
if (prof_dump_flush(propagate_err) &&
propagate_err) {
ret = true;
goto label_return;
}
}
nread = malloc_read_fd(mfd,
&prof_dump_buf[prof_dump_buf_end], PROF_DUMP_BUFSIZE
- prof_dump_buf_end);
} while (nread > 0);
} else {
ret = true;
goto label_return;
}
ret = false; close(mfd);
label_return:
if (mfd != -1) {
close(mfd);
}
return ret;
} }
/* /*
@ -1035,29 +977,13 @@ prof_leakcheck(const prof_cnt_t *cnt_all, size_t leak_ngctx,
#endif #endif
} }
struct prof_gctx_dump_iter_arg_s {
tsdn_t *tsdn;
bool propagate_err;
};
static prof_gctx_t * static prof_gctx_t *
prof_gctx_dump_iter(prof_gctx_tree_t *gctxs, prof_gctx_t *gctx, void *opaque) { prof_gctx_dump_iter(prof_gctx_tree_t *gctxs, prof_gctx_t *gctx, void *opaque) {
prof_gctx_t *ret; tsdn_t *tsdn = (tsdn_t *)opaque;
struct prof_gctx_dump_iter_arg_s *arg = malloc_mutex_lock(tsdn, gctx->lock);
(struct prof_gctx_dump_iter_arg_s *)opaque; prof_dump_gctx(tsdn, gctx, &gctx->bt, gctxs);
malloc_mutex_unlock(tsdn, gctx->lock);
malloc_mutex_lock(arg->tsdn, gctx->lock); return NULL;
if (prof_dump_gctx(arg->tsdn, arg->propagate_err, gctx, &gctx->bt,
gctxs)) {
ret = gctx;
goto label_return;
}
ret = NULL;
label_return:
malloc_mutex_unlock(arg->tsdn, gctx->lock);
return ret;
} }
static void static void
@ -1104,43 +1030,23 @@ prof_dump_prep(tsd_t *tsd, prof_tdata_t *tdata,
static bool static bool
prof_dump_file(tsd_t *tsd, bool propagate_err, const char *filename, prof_dump_file(tsd_t *tsd, bool propagate_err, const char *filename,
bool leakcheck, prof_tdata_t *tdata, bool leakcheck, prof_tdata_t *tdata, const prof_cnt_t *cnt_all,
struct prof_tdata_merge_iter_arg_s *prof_tdata_merge_iter_arg,
struct prof_gctx_merge_iter_arg_s *prof_gctx_merge_iter_arg,
struct prof_gctx_dump_iter_arg_s *prof_gctx_dump_iter_arg,
prof_gctx_tree_t *gctxs) { prof_gctx_tree_t *gctxs) {
prof_dump_error = false;
prof_dump_handle_error_locally = !propagate_err;
/* Create dump file. */ /* Create dump file. */
if ((prof_dump_fd = prof_dump_open(propagate_err, filename)) == -1) { prof_dump_open(filename);
return true;
}
/* Dump profile header. */ /* Dump profile header. */
if (prof_dump_header(tsd_tsdn(tsd), propagate_err, prof_dump_header(tsd_tsdn(tsd), cnt_all);
&prof_tdata_merge_iter_arg->cnt_all)) {
goto label_write_error;
}
/* Dump per gctx profile stats. */ /* Dump per gctx profile stats. */
prof_gctx_dump_iter_arg->tsdn = tsd_tsdn(tsd); gctx_tree_iter(gctxs, NULL, prof_gctx_dump_iter, (void *)tsd_tsdn(tsd));
prof_gctx_dump_iter_arg->propagate_err = propagate_err;
if (gctx_tree_iter(gctxs, NULL, prof_gctx_dump_iter,
(void *)prof_gctx_dump_iter_arg) != NULL) {
goto label_write_error;
}
/* Dump /proc/<pid>/maps if possible. */ /* Dump /proc/<pid>/maps if possible. */
if (prof_dump_maps(propagate_err)) { prof_dump_maps();
goto label_write_error; /* Close dump file. */
} prof_dump_close();
if (prof_dump_close(propagate_err)) { return prof_dump_error;
return true;
}
return false;
label_write_error:
prof_dump_close(propagate_err);
return true;
} }
bool bool
@ -1160,12 +1066,10 @@ prof_dump(tsd_t *tsd, bool propagate_err, const char *filename,
prof_gctx_tree_t gctxs; prof_gctx_tree_t gctxs;
struct prof_tdata_merge_iter_arg_s prof_tdata_merge_iter_arg; struct prof_tdata_merge_iter_arg_s prof_tdata_merge_iter_arg;
struct prof_gctx_merge_iter_arg_s prof_gctx_merge_iter_arg; struct prof_gctx_merge_iter_arg_s prof_gctx_merge_iter_arg;
struct prof_gctx_dump_iter_arg_s prof_gctx_dump_iter_arg;
prof_dump_prep(tsd, tdata, &prof_tdata_merge_iter_arg, prof_dump_prep(tsd, tdata, &prof_tdata_merge_iter_arg,
&prof_gctx_merge_iter_arg, &gctxs); &prof_gctx_merge_iter_arg, &gctxs);
bool err = prof_dump_file(tsd, propagate_err, filename, leakcheck, tdata, bool err = prof_dump_file(tsd, propagate_err, filename, leakcheck,
&prof_tdata_merge_iter_arg, &prof_gctx_merge_iter_arg, tdata, &prof_tdata_merge_iter_arg.cnt_all, &gctxs);
&prof_gctx_dump_iter_arg, &gctxs);
prof_gctx_finish(tsd, &gctxs); prof_gctx_finish(tsd, &gctxs);
malloc_mutex_unlock(tsd_tsdn(tsd), &prof_dump_mtx); malloc_mutex_unlock(tsd_tsdn(tsd), &prof_dump_mtx);

214
test/unit/prof_mdump.c Normal file
View File

@ -0,0 +1,214 @@
#include "test/jemalloc_test.h"
static const char *test_filename = "test_filename";
static bool did_prof_dump_open;
static int
prof_dump_open_file_intercept(const char *filename, int mode) {
int fd;
did_prof_dump_open = true;
/*
* Stronger than a strcmp() - verifying that we internally directly use
* the caller supplied char pointer.
*/
expect_ptr_eq(filename, test_filename,
"Dump file name should be \"%s\"", test_filename);
fd = open("/dev/null", O_WRONLY);
assert_d_ne(fd, -1, "Unexpected open() failure");
return fd;
}
TEST_BEGIN(test_mdump_normal) {
test_skip_if(!config_prof);
prof_dump_open_file_t *open_file_orig = prof_dump_open_file;
void *p = mallocx(1, 0);
assert_ptr_not_null(p, "Unexpected mallocx() failure");
prof_dump_open_file = prof_dump_open_file_intercept;
did_prof_dump_open = false;
expect_d_eq(mallctl("prof.dump", NULL, NULL, (void *)&test_filename,
sizeof(test_filename)), 0,
"Unexpected mallctl failure while dumping");
expect_true(did_prof_dump_open, "Expected a profile dump");
dallocx(p, 0);
prof_dump_open_file = open_file_orig;
}
TEST_END
static int
prof_dump_open_file_error(const char *filename, int mode) {
return -1;
}
/*
* In the context of test_mdump_output_error, prof_dump_write_file_count is the
* total number of times prof_dump_write_file_error() is expected to be called.
* In the context of test_mdump_maps_error, prof_dump_write_file_count is the
* total number of times prof_dump_write_file_error() is expected to be called
* starting from the one that contains an 'M' (beginning the "MAPPED_LIBRARIES"
* header).
*/
static int prof_dump_write_file_count;
static ssize_t
prof_dump_write_file_error(int fd, const void *s, size_t len) {
--prof_dump_write_file_count;
expect_d_ge(prof_dump_write_file_count, 0,
"Write is called after error occurs");
if (prof_dump_write_file_count == 0) {
return -1;
} else {
/*
* Any non-negative number indicates success, and for
* simplicity we just use 0. When prof_dump_write_file_count
* is positive, it means that we haven't reached the write that
* we want to fail; when prof_dump_write_file_count is
* negative, it means that we've already violated the
* expect_d_ge(prof_dump_write_file_count, 0) statement above,
* but instead of aborting, we continue the rest of the test,
* and we indicate that all the writes after the failed write
* are successful.
*/
return 0;
}
}
static void
expect_write_failure(int count) {
prof_dump_write_file_count = count;
expect_d_eq(mallctl("prof.dump", NULL, NULL, (void *)&test_filename,
sizeof(test_filename)), EFAULT, "Dump should err");
expect_d_eq(prof_dump_write_file_count, 0,
"Dumping stopped after a wrong number of writes");
}
TEST_BEGIN(test_mdump_output_error) {
test_skip_if(!config_prof);
test_skip_if(!config_debug);
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_write_file = prof_dump_write_file_error;
void *p = mallocx(1, 0);
assert_ptr_not_null(p, "Unexpected mallocx() failure");
/*
* When opening the dump file fails, there shouldn't be any write, and
* mallctl() should return failure.
*/
prof_dump_open_file = prof_dump_open_file_error;
expect_write_failure(0);
/*
* When the n-th write fails, there shouldn't be any more write, and
* mallctl() should return failure.
*/
prof_dump_open_file = prof_dump_open_file_intercept;
expect_write_failure(1); /* First write fails. */
expect_write_failure(2); /* Second write fails. */
dallocx(p, 0);
prof_dump_open_file = open_file_orig;
prof_dump_write_file = write_file_orig;
}
TEST_END
static int
prof_dump_open_maps_error() {
return -1;
}
static bool started_piping_maps_file;
static ssize_t
prof_dump_write_maps_file_error(int fd, const void *s, size_t len) {
/* The main dump doesn't contain any capital 'M'. */
if (!started_piping_maps_file && strchr(s, 'M') != NULL) {
started_piping_maps_file = true;
}
if (started_piping_maps_file) {
return prof_dump_write_file_error(fd, s, len);
} else {
/* Return success when we haven't started piping maps. */
return 0;
}
}
static void
expect_maps_write_failure(int count) {
int mfd = prof_dump_open_maps();
if (mfd == -1) {
/* No need to continue if we just can't find the maps file. */
return;
}
close(mfd);
started_piping_maps_file = false;
expect_write_failure(count);
expect_true(started_piping_maps_file, "Should start piping maps");
}
TEST_BEGIN(test_mdump_maps_error) {
test_skip_if(!config_prof);
test_skip_if(!config_debug);
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_maps_t *open_maps_orig = prof_dump_open_maps;
prof_dump_open_file = prof_dump_open_file_intercept;
prof_dump_write_file = prof_dump_write_maps_file_error;
void *p = mallocx(1, 0);
assert_ptr_not_null(p, "Unexpected mallocx() failure");
/*
* When opening the maps file fails, there shouldn't be any maps write,
* and mallctl() should return success.
*/
prof_dump_open_maps = prof_dump_open_maps_error;
started_piping_maps_file = false;
prof_dump_write_file_count = 0;
expect_d_eq(mallctl("prof.dump", NULL, NULL, (void *)&test_filename,
sizeof(test_filename)), 0,
"mallctl should not fail in case of maps file opening failure");
expect_false(started_piping_maps_file, "Shouldn't start piping maps");
expect_d_eq(prof_dump_write_file_count, 0,
"Dumping stopped after a wrong number of writes");
/*
* When the n-th maps write fails (given that we are able to find the
* maps file), there shouldn't be any more maps write, and mallctl()
* should return failure.
*/
prof_dump_open_maps = open_maps_orig;
expect_maps_write_failure(1); /* First write fails. */
expect_maps_write_failure(2); /* Second write fails. */
dallocx(p, 0);
prof_dump_open_file = open_file_orig;
prof_dump_write_file = write_file_orig;
}
TEST_END
int
main(void) {
return test(
test_mdump_normal,
test_mdump_output_error,
test_mdump_maps_error);
}

6
test/unit/prof_mdump.sh Normal file
View File

@ -0,0 +1,6 @@
#!/bin/sh
if [ "x${enable_prof}" = "x1" ] ; then
export MALLOC_CONF="prof:true,lg_prof_sample:0"
fi

View File

@ -83,13 +83,10 @@ TEST_END
bool prof_dump_header_intercepted = false; bool prof_dump_header_intercepted = false;
prof_cnt_t cnt_all_copy = {0, 0, 0, 0}; prof_cnt_t cnt_all_copy = {0, 0, 0, 0};
static bool static void
prof_dump_header_intercept(tsdn_t *tsdn, bool propagate_err, prof_dump_header_intercept(tsdn_t *tsdn, const prof_cnt_t *cnt_all) {
const prof_cnt_t *cnt_all) {
prof_dump_header_intercepted = true; prof_dump_header_intercepted = true;
memcpy(&cnt_all_copy, cnt_all, sizeof(prof_cnt_t)); memcpy(&cnt_all_copy, cnt_all, sizeof(prof_cnt_t));
return false;
} }
TEST_BEGIN(test_prof_reset_cleanup) { TEST_BEGIN(test_prof_reset_cleanup) {