Fix heap profiling regressions.
Remove the prof_tctx_state_destroying transitory state and instead add the tctx_uid field, so that the tuple <thr_uid, tctx_uid> uniquely identifies a tctx. This assures that tctx's are well ordered even when more than two with the same thr_uid coexist. A previous attempted fix based on prof_tctx_state_destroying was only sufficient for protecting against two coexisting tctx's, but it also introduced a new dumping race. These regressions were introduced by602c8e0971
(Implement per thread heap profiling.) and764b00023f
(Fix a heap profiling regression.).
This commit is contained in:
parent
262146dfc4
commit
04211e2266
@ -81,7 +81,6 @@ struct prof_cnt_s {
|
|||||||
typedef enum {
|
typedef enum {
|
||||||
prof_tctx_state_initializing,
|
prof_tctx_state_initializing,
|
||||||
prof_tctx_state_nominal,
|
prof_tctx_state_nominal,
|
||||||
prof_tctx_state_destroying,
|
|
||||||
prof_tctx_state_dumping,
|
prof_tctx_state_dumping,
|
||||||
prof_tctx_state_purgatory /* Dumper must finish destroying. */
|
prof_tctx_state_purgatory /* Dumper must finish destroying. */
|
||||||
} prof_tctx_state_t;
|
} prof_tctx_state_t;
|
||||||
@ -102,6 +101,21 @@ struct prof_tctx_s {
|
|||||||
/* Associated global context. */
|
/* Associated global context. */
|
||||||
prof_gctx_t *gctx;
|
prof_gctx_t *gctx;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* UID that distinguishes multiple tctx's created by the same thread,
|
||||||
|
* but coexisting in gctx->tctxs. There are two ways that such
|
||||||
|
* coexistence can occur:
|
||||||
|
* - A dumper thread can cause a tctx to be retained in the purgatory
|
||||||
|
* state.
|
||||||
|
* - Although a single "producer" thread must create all tctx's which
|
||||||
|
* share the same thr_uid, multiple "consumers" can each concurrently
|
||||||
|
* execute portions of prof_tctx_destroy(). prof_tctx_destroy() only
|
||||||
|
* gets called once each time cnts.cur{objs,bytes} drop to 0, but this
|
||||||
|
* threshold can be hit again before the first consumer finishes
|
||||||
|
* executing prof_tctx_destroy().
|
||||||
|
*/
|
||||||
|
uint64_t tctx_uid;
|
||||||
|
|
||||||
/* Linkage into gctx's tctxs. */
|
/* Linkage into gctx's tctxs. */
|
||||||
rb_node(prof_tctx_t) tctx_link;
|
rb_node(prof_tctx_t) tctx_link;
|
||||||
|
|
||||||
@ -178,6 +192,13 @@ struct prof_tdata_s {
|
|||||||
|
|
||||||
rb_node(prof_tdata_t) tdata_link;
|
rb_node(prof_tdata_t) tdata_link;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Counter used to initialize prof_tctx_t's tctx_uid. No locking is
|
||||||
|
* necessary when incrementing this field, because only one thread ever
|
||||||
|
* does so.
|
||||||
|
*/
|
||||||
|
uint64_t tctx_uid_next;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Hash of (prof_bt_t *)-->(prof_tctx_t *). Each thread tracks
|
* Hash of (prof_bt_t *)-->(prof_tctx_t *). Each thread tracks
|
||||||
* backtraces for which it has non-zero allocation/deallocation counters
|
* backtraces for which it has non-zero allocation/deallocation counters
|
||||||
|
21
src/prof.c
21
src/prof.c
@ -135,13 +135,13 @@ static char *prof_thread_name_alloc(tsd_t *tsd, const char *thread_name);
|
|||||||
JEMALLOC_INLINE_C int
|
JEMALLOC_INLINE_C int
|
||||||
prof_tctx_comp(const prof_tctx_t *a, const prof_tctx_t *b)
|
prof_tctx_comp(const prof_tctx_t *a, const prof_tctx_t *b)
|
||||||
{
|
{
|
||||||
uint64_t a_uid = a->thr_uid;
|
uint64_t a_thr_uid = a->thr_uid;
|
||||||
uint64_t b_uid = b->thr_uid;
|
uint64_t b_thr_uid = b->thr_uid;
|
||||||
int ret = (a_uid > b_uid) - (a_uid < b_uid);
|
int ret = (a_thr_uid > b_thr_uid) - (a_thr_uid < b_thr_uid);
|
||||||
if (ret == 0) {
|
if (ret == 0) {
|
||||||
prof_tctx_state_t a_state = a->state;
|
uint64_t a_tctx_uid = a->tctx_uid;
|
||||||
prof_tctx_state_t b_state = b->state;
|
uint64_t b_tctx_uid = b->tctx_uid;
|
||||||
ret = (a_state > b_state) - (a_state < b_state);
|
ret = (a_tctx_uid > b_tctx_uid) - (a_tctx_uid < b_tctx_uid);
|
||||||
}
|
}
|
||||||
return (ret);
|
return (ret);
|
||||||
}
|
}
|
||||||
@ -642,13 +642,11 @@ prof_tctx_destroy(tsd_t *tsd, prof_tctx_t *tctx)
|
|||||||
|
|
||||||
ckh_remove(tsd, &tdata->bt2tctx, &gctx->bt, NULL, NULL);
|
ckh_remove(tsd, &tdata->bt2tctx, &gctx->bt, NULL, NULL);
|
||||||
destroy_tdata = prof_tdata_should_destroy(tdata, false);
|
destroy_tdata = prof_tdata_should_destroy(tdata, false);
|
||||||
if (tctx->state == prof_tctx_state_nominal)
|
|
||||||
tctx->state = prof_tctx_state_destroying;
|
|
||||||
malloc_mutex_unlock(tdata->lock);
|
malloc_mutex_unlock(tdata->lock);
|
||||||
|
|
||||||
malloc_mutex_lock(gctx->lock);
|
malloc_mutex_lock(gctx->lock);
|
||||||
switch (tctx->state) {
|
switch (tctx->state) {
|
||||||
case prof_tctx_state_destroying:
|
case prof_tctx_state_nominal:
|
||||||
tctx_tree_remove(&gctx->tctxs, tctx);
|
tctx_tree_remove(&gctx->tctxs, tctx);
|
||||||
destroy_tctx = true;
|
destroy_tctx = true;
|
||||||
if (prof_gctx_should_destroy(gctx)) {
|
if (prof_gctx_should_destroy(gctx)) {
|
||||||
@ -795,6 +793,7 @@ prof_lookup(tsd_t *tsd, prof_bt_t *bt)
|
|||||||
ret.p->thr_uid = tdata->thr_uid;
|
ret.p->thr_uid = tdata->thr_uid;
|
||||||
memset(&ret.p->cnts, 0, sizeof(prof_cnt_t));
|
memset(&ret.p->cnts, 0, sizeof(prof_cnt_t));
|
||||||
ret.p->gctx = gctx;
|
ret.p->gctx = gctx;
|
||||||
|
ret.p->tctx_uid = tdata->tctx_uid_next++;
|
||||||
ret.p->prepared = true;
|
ret.p->prepared = true;
|
||||||
ret.p->state = prof_tctx_state_initializing;
|
ret.p->state = prof_tctx_state_initializing;
|
||||||
malloc_mutex_lock(tdata->lock);
|
malloc_mutex_lock(tdata->lock);
|
||||||
@ -1033,7 +1032,6 @@ prof_tctx_merge_tdata(prof_tctx_t *tctx, prof_tdata_t *tdata)
|
|||||||
|
|
||||||
switch (tctx->state) {
|
switch (tctx->state) {
|
||||||
case prof_tctx_state_initializing:
|
case prof_tctx_state_initializing:
|
||||||
case prof_tctx_state_destroying:
|
|
||||||
malloc_mutex_unlock(tctx->gctx->lock);
|
malloc_mutex_unlock(tctx->gctx->lock);
|
||||||
return;
|
return;
|
||||||
case prof_tctx_state_nominal:
|
case prof_tctx_state_nominal:
|
||||||
@ -1077,7 +1075,6 @@ prof_tctx_merge_iter(prof_tctx_tree_t *tctxs, prof_tctx_t *tctx, void *arg)
|
|||||||
|
|
||||||
switch (tctx->state) {
|
switch (tctx->state) {
|
||||||
case prof_tctx_state_nominal:
|
case prof_tctx_state_nominal:
|
||||||
case prof_tctx_state_destroying:
|
|
||||||
/* New since dumping started; ignore. */
|
/* New since dumping started; ignore. */
|
||||||
break;
|
break;
|
||||||
case prof_tctx_state_dumping:
|
case prof_tctx_state_dumping:
|
||||||
@ -1113,7 +1110,6 @@ prof_tctx_finish_iter(prof_tctx_tree_t *tctxs, prof_tctx_t *tctx, void *arg)
|
|||||||
|
|
||||||
switch (tctx->state) {
|
switch (tctx->state) {
|
||||||
case prof_tctx_state_nominal:
|
case prof_tctx_state_nominal:
|
||||||
case prof_tctx_state_destroying:
|
|
||||||
/* New since dumping started; ignore. */
|
/* New since dumping started; ignore. */
|
||||||
break;
|
break;
|
||||||
case prof_tctx_state_dumping:
|
case prof_tctx_state_dumping:
|
||||||
@ -1690,6 +1686,7 @@ prof_tdata_init_impl(tsd_t *tsd, uint64_t thr_uid, uint64_t thr_discrim,
|
|||||||
tdata->thread_name = thread_name;
|
tdata->thread_name = thread_name;
|
||||||
tdata->attached = true;
|
tdata->attached = true;
|
||||||
tdata->expired = false;
|
tdata->expired = false;
|
||||||
|
tdata->tctx_uid_next = 0;
|
||||||
|
|
||||||
if (ckh_new(tsd, &tdata->bt2tctx, PROF_CKH_MINITEMS,
|
if (ckh_new(tsd, &tdata->bt2tctx, PROF_CKH_MINITEMS,
|
||||||
prof_bt_hash, prof_bt_keycomp)) {
|
prof_bt_hash, prof_bt_keycomp)) {
|
||||||
|
Loading…
Reference in New Issue
Block a user