Fix missing release of acquired neighbor edata in extent_try_coalesce_impl

When emap_try_acquire_edata_neighbor returned a non-NULL neighbor but
the size check failed, the neighbor was never released from
extent_state_merging, making it permanently invisible to future
allocation and coalescing operations.

Release the neighbor when it doesn't meet the size requirement,
matching the pattern used in extent_recycle_extract.
This commit is contained in:
Slobodan Predolac 2026-03-27 10:04:43 -07:00
parent 3f6e63e86a
commit 675ab079e7
2 changed files with 73 additions and 18 deletions

View file

@ -916,9 +916,13 @@ extent_try_coalesce_impl(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks,
size_t max_next_neighbor = max_size > edata_size_get(edata)
? max_size - edata_size_get(edata)
: 0;
if (next != NULL && edata_size_get(next) <= max_next_neighbor) {
if (!extent_coalesce(
tsdn, pac, ehooks, ecache, edata, next, true)) {
if (next != NULL) {
if (edata_size_get(next) > max_next_neighbor) {
emap_release_edata(
tsdn, pac->emap, next, ecache->state);
} else {
if (!extent_coalesce(tsdn, pac, ehooks, ecache,
edata, next, true)) {
if (ecache->delay_coalesce) {
/* Do minimal coalescing. */
*coalesced = true;
@ -927,6 +931,7 @@ extent_try_coalesce_impl(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks,
again = true;
}
}
}
/* Try to coalesce backward. */
edata_t *prev = emap_try_acquire_edata_neighbor(tsdn, pac->emap,
@ -934,9 +939,13 @@ extent_try_coalesce_impl(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks,
size_t max_prev_neighbor = max_size > edata_size_get(edata)
? max_size - edata_size_get(edata)
: 0;
if (prev != NULL && edata_size_get(prev) <= max_prev_neighbor) {
if (!extent_coalesce(tsdn, pac, ehooks, ecache, edata,
prev, false)) {
if (prev != NULL) {
if (edata_size_get(prev) > max_prev_neighbor) {
emap_release_edata(
tsdn, pac->emap, prev, ecache->state);
} else {
if (!extent_coalesce(tsdn, pac, ehooks, ecache,
edata, prev, false)) {
edata = prev;
if (ecache->delay_coalesce) {
/* Do minimal coalescing. */
@ -946,6 +955,7 @@ extent_try_coalesce_impl(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks,
again = true;
}
}
}
} while (again);
if (ecache->delay_coalesce) {

View file

@ -121,7 +121,52 @@ TEST_BEGIN(test_alloc_free_purge_thds) {
}
TEST_END
TEST_BEGIN(test_failed_coalesce_releases_neighbor) {
test_skip_if(!maps_coalesce);
test_data_t *test_data = init_test_data(-1, -1);
size_t old_lg_extent_max_active_fit = opt_lg_extent_max_active_fit;
opt_lg_extent_max_active_fit = 0;
bool deferred_work_generated = false;
size_t unit = SC_LARGE_MINCLASS;
size_t alloc_size = 4 * unit;
edata_t *edata = pa_alloc(TSDN_NULL, &test_data->shard, alloc_size,
PAGE,
/* slab */ false, sz_size2index(alloc_size), /* zero */ false,
/* guarded */ false, &deferred_work_generated);
expect_ptr_not_null(edata, "Unexpected pa_alloc() failure");
void *tail_addr = (void *)((uintptr_t)edata_base_get(edata) + unit);
expect_false(pa_shrink(TSDN_NULL, &test_data->shard, edata, alloc_size,
unit, sz_size2index(unit), &deferred_work_generated),
"Unexpected pa_shrink() failure");
edata_t *tail = emap_edata_lookup(
TSDN_NULL, &test_data->emap, tail_addr);
expect_ptr_not_null(tail, "Expected dirty tail extent after shrink");
expect_ptr_eq(
edata_base_get(tail), tail_addr, "Unexpected tail extent address");
expect_zu_eq(
edata_size_get(tail), 3 * unit, "Unexpected tail extent size");
expect_d_eq(edata_state_get(tail), extent_state_dirty,
"Expected tail extent to start dirty");
pa_dalloc(
TSDN_NULL, &test_data->shard, edata, &deferred_work_generated);
tail = emap_edata_lookup(TSDN_NULL, &test_data->emap, tail_addr);
expect_ptr_not_null(
tail, "Expected oversized dirty neighbor to remain discoverable");
expect_d_eq(edata_state_get(tail), extent_state_dirty,
"Failed coalesce must release oversized dirty neighbor");
opt_lg_extent_max_active_fit = old_lg_extent_max_active_fit;
}
TEST_END
int
main(void) {
return test(test_alloc_free_purge_thds);
return test(
test_alloc_free_purge_thds, test_failed_coalesce_releases_neighbor);
}