aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDmitry Ilvokhin <d@ilvokhin.com>2024-04-04 10:02:08 -0700
committerQi Wang <interwq@gmail.com>2024-04-30 13:46:32 -0700
commit47d69b4eabae199fa8b5d948f0043effccfbc31e (patch)
tree92e381fc0c920003855bd26b45d59407d6acd2cb
parentfa451de17fff73cc03c31ec8cd817d62927d1ff9 (diff)
downloadjemalloc_new-upstream-dev.tar.gz
HPA: Fix infinite purging loopupstream-dev
One of the condition to start purging is `hpa_hugify_blocked_by_ndirty` function call returns true. This can happen in cases where we have no dirty memory for this shard at all. In this case purging loop will be an infinite loop. `hpa_hugify_blocked_by_ndirty` was introduced at 0f6c420, but at that time purging loop has different form and additional `break` was not required. Purging loop form was re-written at 6630c5989, but additional exit condition wasn't added there at the time. Repo code was shared by Patrik Dokoupil at [1], I stripped it down to minimum to reproduce issue in jemalloc unit tests. [1]: https://github.com/jemalloc/jemalloc/pull/2533
-rw-r--r--src/hpa.c11
-rw-r--r--test/unit/hpa.c50
2 files changed, 57 insertions, 4 deletions
diff --git a/src/hpa.c b/src/hpa.c
index 99d1f033..6b1ae2ce 100644
--- a/src/hpa.c
+++ b/src/hpa.c
@@ -537,9 +537,16 @@ hpa_shard_maybe_do_deferred_work(tsdn_t *tsdn, hpa_shard_t *shard,
purged = false;
while (hpa_should_purge(tsdn, shard) && nops < max_ops) {
purged = hpa_try_purge(tsdn, shard);
- if (purged) {
- nops++;
+ if (!purged) {
+ /*
+ * It is fine if we couldn't purge as sometimes
+ * we try to purge just to unblock
+ * hugification, but there is maybe no dirty
+ * pages at all at the moment.
+ */
+ break;
}
+ nops++;
}
hugified = hpa_try_hugify(tsdn, shard);
if (hugified) {
diff --git a/test/unit/hpa.c b/test/unit/hpa.c
index 9e3160b4..a8a26e13 100644
--- a/test/unit/hpa.c
+++ b/test/unit/hpa.c
@@ -24,7 +24,7 @@ struct test_data_s {
static hpa_shard_opts_t test_hpa_shard_opts_default = {
/* slab_max_alloc */
ALLOC_MAX,
- /* hugification threshold */
+ /* hugification_threshold */
HUGEPAGE,
/* dirty_mult */
FXP_INIT_PERCENT(25),
@@ -36,6 +36,21 @@ static hpa_shard_opts_t test_hpa_shard_opts_default = {
5 * 1000,
};
+static hpa_shard_opts_t test_hpa_shard_opts_purge = {
+ /* slab_max_alloc */
+ HUGEPAGE,
+ /* hugification_threshold */
+ 0.9 * HUGEPAGE,
+ /* dirty_mult */
+ FXP_INIT_PERCENT(11),
+ /* deferral_allowed */
+ true,
+ /* hugify_delay_ms */
+ 0,
+ /* min_purge_interval_ms */
+ 5 * 1000,
+};
+
static hpa_shard_t *
create_test_data(const hpa_hooks_t *hooks, hpa_shard_opts_t *opts) {
bool err;
@@ -452,6 +467,36 @@ TEST_BEGIN(test_defer_time) {
}
TEST_END
+TEST_BEGIN(test_purge_no_infinite_loop) {
+ test_skip_if(!hpa_supported());
+
+ hpa_shard_t *shard = create_test_data(&hpa_hooks_default,
+ &test_hpa_shard_opts_purge);
+ tsdn_t *tsdn = tsd_tsdn(tsd_fetch());
+
+ /*
+ * This is not arbitrary value, it is chosen to met hugification
+ * criteria for huge page and at the same time do not allow hugify page
+ * without triggering a purge.
+ */
+ const size_t npages =
+ test_hpa_shard_opts_purge.hugification_threshold / PAGE + 1;
+ const size_t size = npages * PAGE;
+
+ bool deferred_work_generated = false;
+ edata_t *edata = pai_alloc(tsdn, &shard->pai, size, PAGE,
+ /* zero */ false, /* guarded */ false, /* frequent_reuse */ false,
+ &deferred_work_generated);
+ expect_ptr_not_null(edata, "Unexpected alloc failure");
+
+ hpa_shard_do_deferred_work(tsdn, shard);
+
+ /* hpa_shard_do_deferred_work should not stuck in a purging loop */
+
+ destroy_test_data(shard);
+}
+TEST_END
+
int
main(void) {
/*
@@ -470,5 +515,6 @@ main(void) {
test_alloc_max,
test_stress,
test_alloc_dalloc_batch,
- test_defer_time);
+ test_defer_time,
+ test_purge_no_infinite_loop);
}