Skip to contentSkip to conversation

mm/damon: cleanup, clarify, and add/improve test

RFC PATCH Superseded SJ Park 4 participants 26 messages Sep 12 – Sep 15

Subsystems (5)

v1 of 2 Superseded Latest is v2 →

lore
Share & tools
lore

SJ Park

author maintainer
#0 lore raw

cover mm/damon: cleanup, clarify, and add/improve test

Yet another batch of DAMON overall cleanup.

SJ Park (9):
mm/damon/api: remove NR_DAMOS_FILTER_TYPES
mm/damon/core: use abs_diff() in damon_feed_loop_next_input() mm/damon/core: use mult_frac() in damon_feed_loop_next_input() mm/damon/core: use damos_quota_is_set() in damos_adjust_quota() mm/damon/core: document damon_call()/damon_start() race hang issue mm/damon/paddr: remove pa parameter from damon_pa_filter_pass() selftests/damon/sysfs_memcg_path_leak: fail only for real DAMON leak mm/damon/tests/core-kunit: test eligible_mem_bp commitment Docs/mm/damon/design: clarify bp is basis point

 Documentation/mm/damon/design.rst                |  5 +++--
 include/linux/damon.h                            |  2 --
 mm/damon/core.c                                  | 16 ++++++----------
 mm/damon/paddr.c                                 |  5 ++---
 mm/damon/tests/core-kunit.h                      | 10 ++++++++++
 .../selftests/damon/sysfs_memcg_path_leak.sh     |  7 +++++++
 6 files changed, 28 insertions(+), 17 deletions(-)

base-commit: f0cc06ae989195d7280e0e7eff9d963ddc056d27

Signature · 1 line
2.47.3
author maintainer
#1 lore raw

RFC PATCH 1/9 mm/damon/api: remove NR_DAMOS_FILTER_TYPES

Nobody uses NR_DAMOS_FILTER_TYPES. Remove it.

---
 include/linux/damon.h | 2 --
 1 file changed, 2 deletions(-)
include/linux/damon.h +0 -2
index 1606edace52e4..754752d891ca2 100644
@@ -402,7 +402,6 @@ struct damos_stat {
  * @DAMOS_FILTER_TYPE_ADDR:	Address range.
  * @DAMOS_FILTER_TYPE_TARGET:	Data Access Monitoring target.
  * @DAMOS_FILTER_TYPE_PROBE_HITS_WSUM:	probe_hits weighted sum range.
- * @NR_DAMOS_FILTER_TYPES:	Number of filter types.
  *
  * All types except &DAMOS_FILTER_TYPE_ADDR, &DAMOS_FILTER_TYPE_TARGET and
  * &DAMOS_FILTER_TYPE_PROBE_HITS_WSUM are handled by the underlying &struct
@@ -424,7 +423,6 @@ enum damos_filter_type {
 	DAMOS_FILTER_TYPE_ADDR,
 	DAMOS_FILTER_TYPE_TARGET,
 	DAMOS_FILTER_TYPE_PROBE_HITS_WSUM,
-	NR_DAMOS_FILTER_TYPES,
 };
 
 /**
Signature · 1 line
2.47.3
#2 lore raw

Re: [RFC PATCH 1/9] mm/damon/api: remove NR_DAMOS_FILTER_TYPES

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 3 lines

Nobody uses NR_DAMOS_FILTER_TYPES. Remove it.

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
author maintainer
#3 lore raw

RFC PATCH 2/9 mm/damon/core: use abs_diff() in damon_feed_loop_next_input()

In reply to SJ Park

damon_feed_loop_next_input() is open-coding absolute diff calculation instead of the dedicated helper, abs_diff(), for no good reason. Use the dedicated helper.

---
 mm/damon/core.c | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)
mm/damon/core.c +1 -4
index 373339f961967..61bb25674cd23 100644
@@ -2920,10 +2920,7 @@ static unsigned long damon_feed_loop_next_input(unsigned long last_input,
 	if (score >= goal * 2)
 		return min_input;
 
-	if (over_achieving)
-		score_goal_diff = score - goal;
-	else
-		score_goal_diff = goal - score;
+	score_goal_diff = abs_diff(score, goal);
 
 	if (last_input < ULONG_MAX / score_goal_diff)
 		compensation = last_input * score_goal_diff / goal;
Signature · 1 line
2.47.3
#4 lore raw

Re: [RFC PATCH 2/9] mm/damon/core: use abs_diff() in damon_feed_loop_next_input()

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 5 lines

damon_feed_loop_next_input() is open-coding absolute diff calculation instead of the dedicated helper, abs_diff(), for no good reason. Use the dedicated helper.

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
author maintainer
#5 lore raw

RFC PATCH 3/9 mm/damon/core: use mult_frac() in damon_feed_loop_next_input()

In reply to SJ Park

damon_feed_loop_next_input() does its best effort overflow protection. score_goal_diff is always smaller than goal (10,000). Hence the calculation can be replaced to use mult_frac() without concerning the overflow. Use mult_frac().

---
 mm/damon/core.c | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)
mm/damon/core.c +1 -5
index 61bb25674cd23..cf10f6d725c19 100644
@@ -2921,11 +2921,7 @@ static unsigned long damon_feed_loop_next_input(unsigned long last_input,
 		return min_input;
 
 	score_goal_diff = abs_diff(score, goal);
-
-	if (last_input < ULONG_MAX / score_goal_diff)
-		compensation = last_input * score_goal_diff / goal;
-	else
-		compensation = last_input / goal * score_goal_diff;
+	compensation = mult_frac(last_input, score_goal_diff, goal);
 
 	if (over_achieving)
 		return max(last_input - compensation, min_input);
Signature · 1 line
2.47.3
#6 lore raw

Re: [RFC PATCH 3/9] mm/damon/core: use mult_frac() in damon_feed_loop_next_input()

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 6 lines

damon_feed_loop_next_input() does its best effort overflow protection. score_goal_diff is always smaller than goal (10,000). Hence the calculation can be replaced to use mult_frac() without concerning the overflow. Use mult_frac().

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=3
author maintainer
#7 lore raw

RFC PATCH 4/9 mm/damon/core: use damos_quota_is_set() in damos_adjust_quota()

In reply to SJ Park

damos_adjust_quota() is manually checking if the user set the DAMOS quota. There is a dedicated helper, damos_quota_is_set(), for the purpose. Use the helper.

---
 mm/damon/core.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
mm/damon/core.c +1 -1
index cf10f6d725c19..dd27068cb83e3 100644
@@ -3344,7 +3344,7 @@ static void damos_adjust_quota(struct damon_ctx *c, struct damos *s)
 	unsigned long cumulated_sz, cached_esz;
 	unsigned int score, max_score = 0;
 
-	if (!quota->ms && !quota->sz && list_empty(&quota->goals))
+	if (!damos_quota_is_set(quota))
 		return;
 
 	/* First charge window */
Signature · 1 line
2.47.3
#8 lore raw

Re: [RFC PATCH 4/9] mm/damon/core: use damos_quota_is_set() in damos_adjust_quota()

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 5 lines

damos_adjust_quota() is manually checking if the user set the DAMOS quota. There is a dedicated helper, damos_quota_is_set(), for the purpose. Use the helper.

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=4
#9 lore raw

Re: [RFC PATCH 4/9] mm/damon/core: use damos_quota_is_set() in damos_adjust_quota()

In reply to SJ Park
Quoted 14 lines · On Sat, 12 Sep 2026 13:08:05 -0700 SJ Park <[email protected]> wrote:

damos_adjust_quota() is manually checking if the user set the DAMOS quota. There is a dedicated helper, damos_quota_is_set(), for the purpose. Use the helper.

---
 mm/damon/core.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
mm/damon/core.c +0 -0
index cf10f6d725c19..dd27068cb83e3 100644

mm/damon/core.c static void damos_adjust_quota(struct damon_ctx *c, struct damos *s) · line 3347

Quoted 5 lines
mm/damon/core.c +1 -1
@@ -3344,7 +3344,7 @@ static void damos_adjust_quota(struct damon_ctx *c, struct damos *s)
 	unsigned long cumulated_sz, cached_esz;
 	unsigned int score, max_score = 0;
 
-	if (!quota->ms && !quota->sz && list_empty(&quota->goals))
+	if (!damos_quota_is_set(quota))

Hi SJ,
This is more than a cleanup, because damos_quota_is_set() also checks quota->esz, which the original condition did not.

The path that makes this matter: damos_commit_quota() copies ms and sz from sysfs but not esz. So if a scheme runs with a non-zero ms, then the user sets ms=0 and commits, the running scheme gets ms=0 while esz keeps its old value.

The old check would then return early, leaving esz stale. That stale esz is visible to damos_valid_target() and damos_quota_is_full(), which also use damos_quota_is_set().

With the helper, damos_adjust_quota() proceeds into damos_set_effective_quota(), where the first branch resets esz from sz when ms and goals are unset.

The code change is correct, but worth noting the behavioral change in the commit message.

Thanks,
Kunwu

Quoted 6 lines
mm/damon/core.c +0 -0
 		return;
 
 	/* First charge window */
Signature · 1 line
2.47.3

Sent using hkml (https://github.com/sjp38/hackermail)

author maintainer
#10 lore raw

Re: [RFC PATCH 4/9] mm/damon/core: use damos_quota_is_set() in damos_adjust_quota()

Quoted 35 lines · On Mon, 14 Sep 2026 23:01:48 +0800 Kunwu Chan <[email protected]> wrote:
Quoted 14 lines · On Sat, 12 Sep 2026 13:08:05 -0700 SJ Park <[email protected]> wrote:

damos_adjust_quota() is manually checking if the user set the DAMOS quota. There is a dedicated helper, damos_quota_is_set(), for the purpose. Use the helper.

---
 mm/damon/core.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
mm/damon/core.c +0 -0
index cf10f6d725c19..dd27068cb83e3 100644

mm/damon/core.c static void damos_adjust_quota(struct damon_ctx *c, struct damos *s) · line 3347

Quoted 5 lines
mm/damon/core.c +1 -1
@@ -3344,7 +3344,7 @@ static void damos_adjust_quota(struct damon_ctx *c, struct damos *s)
 	unsigned long cumulated_sz, cached_esz;
 	unsigned int score, max_score = 0;
 
-	if (!quota->ms && !quota->sz && list_empty(&quota->goals))
+	if (!damos_quota_is_set(quota))

Hi SJ,
This is more than a cleanup, because damos_quota_is_set() also checks quota->esz, which the original condition did not.

The path that makes this matter: damos_commit_quota() copies ms and sz from sysfs but not esz. So if a scheme runs with a non-zero ms, then the user sets ms=0 and commits, the running scheme gets ms=0 while esz keeps its old value.

The old check would then return early, leaving esz stale. That stale esz is visible to damos_valid_target() and damos_quota_is_full(), which also use damos_quota_is_set().

Quoted 6 lines

With the helper, damos_adjust_quota() proceeds into damos_set_effective_quota(), where the first branch resets esz from sz when ms and goals are unset.

The code change is correct, but worth noting the behavioral change in the commit message.

So, if user sets only time quota, and then commits the quota to zero while the esz is non-zero, esz is never reset to zero. As a result, DAMOS keeps applying the action with the esz forever. Correct?

If so, this may deserve Cc-ing stable@. Kunwu, what do you think?

Thanks,
SJ

[...]

#11 lore raw

Re: [RFC PATCH 4/9] mm/damon/core: use damos_quota_is_set() in damos_adjust_quota()

Quoted 42 lines · SJ Park
Quoted 39 lines · On Mon, 14 Sep 2026 23:01:48 +0800 Kunwu Chan <[email protected]> wrote:
Quoted 14 lines · On Sat, 12 Sep 2026 13:08:05 -0700 SJ Park <[email protected]> wrote:

damos_adjust_quota() is manually checking if the user set the DAMOS quota. There is a dedicated helper, damos_quota_is_set(), for the purpose. Use the helper.

---
 mm/damon/core.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
mm/damon/core.c +0 -0
index cf10f6d725c19..dd27068cb83e3 100644

mm/damon/core.c static void damos_adjust_quota(struct damon_ctx *c, struct damos *s) · line 3347

Quoted 5 lines
mm/damon/core.c +1 -1
@@ -3344,7 +3344,7 @@ static void damos_adjust_quota(struct damon_ctx *c, struct damos *s)
    unsigned long cumulated_sz, cached_esz;
    unsigned int score, max_score = 0;
 
-   if (!quota->ms && !quota->sz && list_empty(&quota->goals))
+   if (!damos_quota_is_set(quota))

Hi SJ,
This is more than a cleanup, because damos_quota_is_set() also checks quota->esz, which the original condition did not.

The path that makes this matter: damos_commit_quota() copies ms and sz from sysfs but not esz. So if a scheme runs with a non-zero ms, then the user sets ms=0 and commits, the running scheme gets ms=0 while esz keeps its old value.

The old check would then return early, leaving esz stale. That stale esz is visible to damos_valid_target() and damos_quota_is_full(), which also use damos_quota_is_set().

With the helper, damos_adjust_quota() proceeds into damos_set_effective_quota(), where the first branch resets esz from sz when ms and goals are unset.

Quoted 6 lines
Quoted 2 lines

The code change is correct, but worth noting the behavioral change in the commit message.

So, if user sets only time quota, and then commits the quota to zero while the esz is non-zero, esz is never reset to zero. As a result, DAMOS keeps applying the action with the esz forever. Correct?

Yes, the stale esz path is confirmed.

One clarification, though: it does not keep applying the action forever. While charged_sz < stale_esz, damos_adjust_quota() still returns early, but damos_apply_target() continues to apply the scheme and damos_charge_quota() keeps increasing charged_sz.

Once charged_sz >= stale_esz, damos_quota_is_full() returns true, so damon_do_apply_schemes() skips the scheme. Since the early return also prevents the charge-window handling from running, charged_sz is never reset, and the scheme remains permanently inactive.

So clearing the quota can result in the scheme applying up to the stale esz, and then stopping permanently.

Quoted 2 lines

If so, this may deserve Cc-ing stable@. Kunwu, what do you think?

I think this is a functional bug in the existing code, and Cc'ing stable makes sense.

Thanks,
Kunwu

Quoted 6 lines

Thanks,
SJ

[...]

author maintainer
#12 lore raw

Re: [RFC PATCH 4/9] mm/damon/core: use damos_quota_is_set() in damos_adjust_quota()

Quoted 5 lines · On Tue, 15 Sep 2026 10:19:43 +0800 KunWu Chan <[email protected]> wrote:
Quoted 4 lines · SJ Park
Quoted 1 line · On Mon, 14 Sep 2026 23:01:48 +0800 Kunwu Chan <[email protected]> wrote:

On Sat, 12 Sep 2026 13:08:05 -0700 SJ Park <[email protected]> wrote:

[...]

Quoted 11 lines
Quoted 3 lines

So, if user sets only time quota, and then commits the quota to zero while the esz is non-zero, esz is never reset to zero. As a result, DAMOS keeps applying the action with the esz forever. Correct?

Yes, the stale esz path is confirmed.

One clarification, though: it does not keep applying the action forever. While charged_sz < stale_esz, damos_adjust_quota() still returns early, but damos_apply_target() continues to apply the scheme and damos_charge_quota() keeps increasing charged_sz.

Quoted 8 lines

Once charged_sz >= stale_esz, damos_quota_is_full() returns true, so damon_do_apply_schemes() skips the scheme. Since the early return also prevents the charge-window handling from running, charged_sz is never reset, and the scheme remains permanently inactive.

So clearing the quota can result in the scheme applying up to the stale esz, and then stopping permanently.

Thank you for detailed clarification, Kunwu!

Quoted 6 lines
Quoted 3 lines

If so, this may deserve Cc-ing stable@. Kunwu, what do you think?

I think this is a functional bug in the existing code, and Cc'ing stable makes sense.

Agreed. I will post this as a hotfix soon.

Thanks,
SJ

[...]

author maintainer
#13 lore raw

RFC PATCH 5/9 mm/damon/core: document damon_call()/damon_start() race hang issue

In reply to SJ Park

Let's suppose damon_start() and damon_call() are executed in parallel for the same DAMON context. Then, damon_call() could show ctx->damon_calls_obsolte set while ctx->kdamond is unset. If damon_start() sets ctx->kdamond before damon_call() starts the cancelling, damon_call() can indefinitely hang. No DAMON API caller does such parallel execution of damon_start() and damon_call(), so the issue doesn't exist. But who knows what will happen in future. Add a clarification comment for caution.

---
 mm/damon/core.c | 3 +++
 1 file changed, 3 insertions(+)
mm/damon/core.c +3 -0
index dd27068cb83e3..080355b1f2d9f 100644
@@ -2251,6 +2251,9 @@ int damon_kdamond_pid(struct damon_ctx *ctx)
  *
  * When this function is failed, the @ctx is guaranteed to be stopped.
  *
+ * This function should not be called in parallel to damon_start() for the
+ * @ctx.  In the case, this function could indefinitely hang.
+ *
  * Return: 0 on success, negative error code otherwise.
  */
 int damon_call(struct damon_ctx *ctx, struct damon_call_control *control)
Signature · 1 line
2.47.3
#14 lore raw

Re: [RFC PATCH 5/9] mm/damon/core: document damon_call()/damon_start() race hang issue

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:

Pre-existing issues:
- [High] Brittle cancellation polling loop in damon_call() can cause indefinite hangs on rapid context restarts.
- [High] Missing initialization of walk_control_obsolete in damon_new_ctx() causes unrecoverable hangs in damos_walk(). --

commit edbd3bc657324e35d324500107e2e30a1ffabc31
Author: SJ Park <[email protected]>

mm/damon/core: document damon_call()/damon_start() race hang issue

This commit documents a potential race condition and indefinite hang between damon_start() and damon_call() if they are executed in parallel for the same DAMON context. It adds a cautionary comment to damon_call() to warn API users about this behavior.

mm/damon/core.c int damon_kdamond_pid(struct damon_ctx *ctx) · line 2223

Quoted 11 lines
mm/damon/core.c +3 -0
index 6e4b0144c3dd1..f304813a08460 100644
@@ -2218,6 +2218,9 @@ int damon_kdamond_pid(struct damon_ctx *ctx)
  *
  * When this function is failed, the @ctx is guaranteed to be stopped.
  *
+ * This function should not be called in parallel to damon_start() for the
+ * @ctx.  In the case, this function could indefinitely hang.
+ *

[Severity: High]
This is a pre-existing issue, but does the cancellation polling loop in damon_call() cause indefinite hangs on rapid context restarts?

If Thread A is blocked in damon_call(), and Thread B stops the DAMON context (e.g., via module parameters), the monitoring thread cancels the call and exits.

If Thread A is preempted before evaluating the canceled polling loop, and Thread C concurrently restarts the context, Thread A resumes, evaluates damon_is_running(ctx) to true (due to the newly spawned thread), and hangs forever waiting for the new thread to stop:

mm/damon/core.c:damon_call() {
    ...
canceled:
        while (damon_is_running(ctx))
                schedule_timeout_idle(msecs_to_jiffies(100));
        return -ECANCELED;
}

This race can be triggered by root via concurrent sysfs or module parameter writes (e.g., in damon_reclaim).

[Severity: High]
This is a pre-existing issue, but does the missing initialization of walk_control_obsolete in damon_new_ctx() cause unrecoverable hangs in damos_walk()?

damon_new_ctx() explicitly initializes call_controls_obsolete = true to protect damon_call() from hanging if invoked before the context is started. However, it fails to initialize walk_control_obsolete:

mm/damon/core.c:damon_new_ctx() {
    ...
        ctx->call_controls_obsolete = true;
        prandom_seed_state(&ctx->rnd_state, get_random_u64());
    ...
}

If damos_walk() is called before damon_start(), it bypasses the safety check and blocks forever on wait_for_completion(), because no thread exists to signal it:

mm/damon/core.c:damos_walk() {
    ...
        mutex_lock(&ctx->walk_control_lock);
        if (ctx->walk_control_obsolete) {
                mutex_unlock(&ctx->walk_control_lock);
                return -ECANCELED;
        }
    ...
}

Even though it requires erroneous API usage, the API intends to gracefully reject this via the obsolete flag. The missing initialization breaks this boundary, leading to an unkillable hang.

Quoted 3 lines
mm/damon/core.c +0 -0
  * Return: 0 on success, negative error code otherwise.
  */
 int damon_call(struct damon_ctx *ctx, struct damon_call_control *control)
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=5
author maintainer
#15 lore raw

Re: [RFC PATCH 5/9] mm/damon/core: document damon_call()/damon_start() race hang issue

Quoted 50 lines · On Sat, 12 Sep 2026 20:19:44 +0000 [email protected] wrote:

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:

Pre-existing issues:
- [High] Brittle cancellation polling loop in damon_call() can cause indefinite hangs on rapid context restarts.
- [High] Missing initialization of walk_control_obsolete in damon_new_ctx() causes unrecoverable hangs in damos_walk(). --

commit edbd3bc657324e35d324500107e2e30a1ffabc31
Author: SJ Park <[email protected]>

mm/damon/core: document damon_call()/damon_start() race hang issue

This commit documents a potential race condition and indefinite hang between damon_start() and damon_call() if they are executed in parallel for the same DAMON context. It adds a cautionary comment to damon_call() to warn API users about this behavior.

mm/damon/core.c int damon_kdamond_pid(struct damon_ctx *ctx) · line 2223

Quoted 11 lines
mm/damon/core.c +3 -0
index 6e4b0144c3dd1..f304813a08460 100644
@@ -2218,6 +2218,9 @@ int damon_kdamond_pid(struct damon_ctx *ctx)
  *
  * When this function is failed, the @ctx is guaranteed to be stopped.
  *
+ * This function should not be called in parallel to damon_start() for the
+ * @ctx.  In the case, this function could indefinitely hang.
+ *

[Severity: High]
This is a pre-existing issue, but does the cancellation polling loop in damon_call() cause indefinite hangs on rapid context restarts?

If Thread A is blocked in damon_call(), and Thread B stops the DAMON context (e.g., via module parameters), the monitoring thread cancels the call and exits.

If Thread A is preempted before evaluating the canceled polling loop, and Thread C concurrently restarts the context, Thread A resumes, evaluates damon_is_running(ctx) to true (due to the newly spawned thread), and hangs forever waiting for the new thread to stop:

mm/damon/core.c:damon_call() {
    ...
canceled:
        while (damon_is_running(ctx))
                schedule_timeout_idle(msecs_to_jiffies(100));
        return -ECANCELED;
}
Quoted 2 lines

This race can be triggered by root via concurrent sysfs or module parameter writes (e.g., in damon_reclaim).

DAMON_SYSFS does not reuse same damon_ctx, so there is no such issue.

DAMON_RECLAIM and DAMON_LRU_SORT reuse same damon_ctx, but their damon_call(), damon_start() and damon_stop() calls are done only in init and param write callback, which holds param lock. So such a race cannot happen.

Quoted 31 lines

[Severity: High]
This is a pre-existing issue, but does the missing initialization of walk_control_obsolete in damon_new_ctx() cause unrecoverable hangs in damos_walk()?

damon_new_ctx() explicitly initializes call_controls_obsolete = true to protect damon_call() from hanging if invoked before the context is started. However, it fails to initialize walk_control_obsolete:

mm/damon/core.c:damon_new_ctx() {
    ...
        ctx->call_controls_obsolete = true;
        prandom_seed_state(&ctx->rnd_state, get_random_u64());
    ...
}

If damos_walk() is called before damon_start(), it bypasses the safety check and blocks forever on wait_for_completion(), because no thread exists to signal it:

mm/damon/core.c:damos_walk() {
    ...
        mutex_lock(&ctx->walk_control_lock);
        if (ctx->walk_control_obsolete) {
                mutex_unlock(&ctx->walk_control_lock);
                return -ECANCELED;
        }
    ...
}
Quoted 3 lines

Even though it requires erroneous API usage, the API intends to gracefully reject this via the obsolete flag. The missing initialization breaks this boundary, leading to an unkillable hang.

There is no such API usage, so such issue cannot happen. I will consider adding a comment or the initialization for possible future bug. But no blocker of this patch.

Quoted 7 lines
Quoted 4 lines
mm/damon/core.c +0 -0
  * Return: 0 on success, negative error code otherwise.
  */
 int damon_call(struct damon_ctx *ctx, struct damon_call_control *control)
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=5

Thanks,
SJ

author maintainer
#16 lore raw

RFC PATCH 6/9 mm/damon/paddr: remove pa parameter from damon_pa_filter_pass()

In reply to SJ Park

damon_pa_filter_pass() receives the 'pa' parameter, but doesn't use it. Remove the parameter from the function signature.

---
 mm/damon/paddr.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)
mm/damon/paddr.c +2 -3
index 5abfabaa339e0..2cfdc356b4157 100644
@@ -163,8 +163,7 @@ static bool damon_pa_filter_match(struct damon_filter *filter,
 	return matched == filter->matching;
 }
 
-static bool damon_pa_filter_pass(phys_addr_t pa, struct folio *folio,
-		struct damon_probe *p)
+static bool damon_pa_filter_pass(struct folio *folio, struct damon_probe *p)
 {
 	struct damon_filter *f;
 	bool pass = true;
@@ -200,7 +199,7 @@ static unsigned int damon_pa_apply_probes(struct damon_ctx *ctx,
 					ctx->addr_unit);
 			folio = damon_get_folio(PHYS_PFN(pa));
 			damon_for_each_probe(p, ctx) {
-				if (damon_pa_filter_pass(pa, folio, p))
+				if (damon_pa_filter_pass(folio, p))
 					r->probe_hits[i]++;
 				i++;
 			}
Signature · 1 line
2.47.3
#17 lore raw

Re: [RFC PATCH 6/9] mm/damon/paddr: remove pa parameter from damon_pa_filter_pass()

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 4 lines

damon_pa_filter_pass() receives the 'pa' parameter, but doesn't use it. Remove the parameter from the function signature.

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=6
author maintainer
#18 lore raw

RFC PATCH 7/9 selftests/damon/sysfs_memcg_path_leak: fail only for real DAMON leak

In reply to SJ Park

The selftest can fail for any leak if it happens while the test is running. Remove the false positive test failures by further checking if the expected leaking function is called out on the report.

---
 tools/testing/selftests/damon/sysfs_memcg_path_leak.sh | 7 +++++++
 1 file changed, 7 insertions(+)
tools/testing/selftests/damon/sysfs_memcg_path_leak.sh +7 -0
index 33a7ff43ed6cc..6c2d8bb3fe570 100755
@@ -41,5 +41,12 @@ if [ "$kmemleak_report" = "" ]
 then
 	exit 0
 fi
+if ! echo "$kmemleak_report" | grep "memcg_path_store" --quiet
+then
+	echo "[WARN] memleak found; apparenty not from DAMON, though"
+	echo "$kmemleak_report"
+	exit 0
+fi
+
 echo "$kmemleak_report"
 exit 1
Signature · 1 line
2.47.3
#19 lore raw

Re: [RFC PATCH 7/9] selftests/damon/sysfs_memcg_path_leak: fail only for real DAMON leak

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [Medium] Use of the non-standard `--quiet` flag in `grep` can cause the test to silently mask real memory…

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] Use of the non-standard --quiet flag in grep can cause the test to silently mask real memory leaks on systems with strict POSIX grep implementations.
- [Low] Typo in the warning message: 'apparenty' instead of 'apparently'.
--

commit afc83fe4758b4c2e76cc3da97cb99e2a990b6b83
Author: SJ Park <[email protected]>

selftests/damon/sysfs_memcg_path_leak: fail only for real DAMON leak

The selftest can fail for any leak if it happens while the test is running. Remove the false positive test failures by further checking if the expected leaking function is called out on the report.

tools/testing/selftests/damon/sysfs_memcg_path_leak.sh if [ "$kmemleak_report" = "" ] · line 44

Quoted 9 lines
tools/testing/selftests/damon/sysfs_memcg_path_leak.sh +1 -0
index 33a7ff43ed6cc..6c2d8bb3fe570 100755
@@ -41,5 +41,12 @@ if [ "$kmemleak_report" = "" ]
 then
 	exit 0
 fi
+if ! echo "$kmemleak_report" | grep "memcg_path_store" --quiet

[Severity: Medium]
Could the --quiet flag cause the test to accidentally mask real memory leaks?

If the test runs in an environment with a strict POSIX grep implementation that does not support the GNU extension --quiet, grep will fail with an unrecognized option error.

Because of the negated condition, a non-zero exit code from grep causes the script to evaluate to true, print a warning, and exit 0. Should the standard POSIX -q flag be used instead to ensure compatibility?

tools/testing/selftests/damon/sysfs_memcg_path_leak.sh if [ "$kmemleak_report" = "" ] · line 46

Quoted 2 lines
tools/testing/selftests/damon/sysfs_memcg_path_leak.sh +2 -0
+then
+	echo "[WARN] memleak found; apparenty not from DAMON, though"

[Severity: Low]
This isn't a bug, but there is a typo in the warning message (apparenty instead of apparently).

Quoted 6 lines
tools/testing/selftests/damon/sysfs_memcg_path_leak.sh +4 -0
+	echo "$kmemleak_report"
+	exit 0
+fi
+
 echo "$kmemleak_report"
 exit 1
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=7
author maintainer
#20 lore raw

Re: [RFC PATCH 7/9] selftests/damon/sysfs_memcg_path_leak: fail only for real DAMON leak

Quoted 27 lines · On Sat, 12 Sep 2026 20:15:18 +0000 [email protected] wrote:

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] Use of the non-standard --quiet flag in grep can cause the test to silently mask real memory leaks on systems with strict POSIX grep implementations.
- [Low] Typo in the warning message: 'apparenty' instead of 'apparently'.
--

commit afc83fe4758b4c2e76cc3da97cb99e2a990b6b83
Author: SJ Park <[email protected]>

selftests/damon/sysfs_memcg_path_leak: fail only for real DAMON leak

The selftest can fail for any leak if it happens while the test is running. Remove the false positive test failures by further checking if the expected leaking function is called out on the report.

tools/testing/selftests/damon/sysfs_memcg_path_leak.sh if [ "$kmemleak_report" = "" ] · line 44

Quoted 9 lines
tools/testing/selftests/damon/sysfs_memcg_path_leak.sh +1 -0
index 33a7ff43ed6cc..6c2d8bb3fe570 100755
@@ -41,5 +41,12 @@ if [ "$kmemleak_report" = "" ]
 then
 	exit 0
 fi
+if ! echo "$kmemleak_report" | grep "memcg_path_store" --quiet

[Severity: Medium]
Could the --quiet flag cause the test to accidentally mask real memory leaks?

Quoted 7 lines

If the test runs in an environment with a strict POSIX grep implementation that does not support the GNU extension --quiet, grep will fail with an unrecognized option error.

Because of the negated condition, a non-zero exit code from grep causes the script to evaluate to true, print a warning, and exit 0. Should the standard POSIX -q flag be used instead to ensure compatibility?

I believe kselftest runs on systems modern enough to have --quiet option. Let me know if I'm wrong.

Quoted 7 lines

tools/testing/selftests/damon/sysfs_memcg_path_leak.sh if [ "$kmemleak_report" = "" ] · line 46

Quoted 3 lines
tools/testing/selftests/damon/sysfs_memcg_path_leak.sh +2 -0
+then
+	echo "[WARN] memleak found; apparenty not from DAMON, though"

[Severity: Low]
This isn't a bug, but there is a typo in the warning message (apparenty instead of apparently).

Good eyes, I will fix in the next spin.

Quoted 10 lines
Quoted 7 lines
tools/testing/selftests/damon/sysfs_memcg_path_leak.sh +4 -0
+	echo "$kmemleak_report"
+	exit 0
+fi
+
 echo "$kmemleak_report"
 exit 1
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=7

Thanks,
SJ

author maintainer
#21 lore raw

RFC PATCH 8/9 mm/damon/tests/core-kunit: test eligible_mem_bp commitment

In reply to SJ Park

There was a DAMOS quota goal commit bug [1] that doesn't update the nid field for DAMOS_QUOTA_NODE_ELIGIBLE_MEM_BP metric goal. Add a kunit test case for confirming nid commitment.

[1] https://lore.kkernel.org/[email protected]

---
 mm/damon/tests/core-kunit.h | 10 ++++++++++
 1 file changed, 10 insertions(+)
mm/damon/tests/core-kunit.h +10 -0
index 5e8ead5ef8728..c2da4a3fbcafb 100644
@@ -836,6 +836,9 @@ static void damos_test_commit_quota_goal_for(struct kunit *test,
 		KUNIT_EXPECT_EQ(test, dst->nid, src->nid);
 		KUNIT_EXPECT_EQ(test, dst->memcg_id, src->memcg_id);
 		break;
+	case DAMOS_QUOTA_NODE_ELIGIBLE_MEM_BP:
+		KUNIT_EXPECT_EQ(test, dst->nid, src->nid);
+		break;
 	default:
 		break;
 	}
@@ -900,6 +903,13 @@ static void damos_test_commit_quota_goal(struct kunit *test)
 			.current_value = 345,
 			.last_psi_total = 567,
 			});
+	damos_test_commit_quota_goal_for(test, &dst,
+			&(struct damos_quota_goal){
+			.metric = DAMOS_QUOTA_NODE_ELIGIBLE_MEM_BP,
+			.target_value = 12,
+			.current_value = 345,
+			.nid = 6,
+			});
 }
 
 static void damos_test_commit_quota_goals_for(struct kunit *test,
Signature · 1 line
2.47.3
#22 lore raw

Re: [RFC PATCH 8/9] mm/damon/tests/core-kunit: test eligible_mem_bp commitment

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 7 lines

There was a DAMOS quota goal commit bug [1] that doesn't update the nid field for DAMOS_QUOTA_NODE_ELIGIBLE_MEM_BP metric goal. Add a kunit test case for confirming nid commitment.

[1] https://lore.kkernel.org/[email protected]

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=8
author maintainer
#23 lore raw

RFC PATCH 9/9 Docs/mm/damon/design: clarify bp is basis point

In reply to SJ Park

DAMON design document uses "bp" for "basis point" in multiple places. Because it is not clearly mentioned, it is difficult to understand what "bp" stands for. Add the clarification.

---
 Documentation/mm/damon/design.rst | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
Documentation/mm/damon/design.rst +3 -2
index 0a86792f90a18..e82390e77a70a 100644
@@ -445,7 +445,7 @@ users to set the aimed amount of access events to observe via DAMON within
 given time interval.  The target can be specified by the user as a ratio of
 DAMON-observed access events to the theoretical maximum amount of the events
 (``access_bp``) that measured within a given number of aggregations
-(``aggrs``).
+(``aggrs``).  The ratio is in basis point (bp or 1/10,000).
 
 The DAMON-observed access events are calculated in byte granularity based on
 DAMON :ref:`region assumption <damon_design_region_based_sampling>`.  For
@@ -717,7 +717,8 @@ mechanism tries to make ``current_value`` of ``target_metric`` be same to
   in microseconds that measured from last quota reset to next quota reset.
   DAMOS does the measurement on its own, so only ``target_value`` need to be
   set by users at the initial time.  In other words, DAMOS does self-feedback.
-- ``node_mem_used_bp``: Specific NUMA node's used memory ratio in bp (1/10,000).
+- ``node_mem_used_bp``: Specific NUMA node's used memory ratio in basis point
+  (bp or 1/10,000).
 - ``node_mem_free_bp``: Specific NUMA node's free memory ratio in bp (1/10,000).
 - ``node_memcg_used_bp``: Specific cgroup's node used memory ratio for a
   specific NUMA node, in bp (1/10,000).
Signature · 1 line
2.47.3
#24 lore raw

Re: [RFC PATCH 9/9] Docs/mm/damon/design: clarify bp is basis point

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 7 lines

DAMON design document uses "bp" for "basis point" in multiple places. Because it is not clearly mentioned, it is difficult to understand what "bp" stands for. Add the clarification.

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=9
reviewer Acked-by on v1 9/9
#25 lore raw

Re: [RFC PATCH 9/9] Docs/mm/damon/design: clarify bp is basis point

In reply to SJ Park
Quoted 7 lines · SJ Park

DAMON design document uses "bp" for "basis point" in multiple places. Because it is not clearly mentioned, it is difficult to understand what "bp" stands for. Add the clarification.

Thanks.

Quoted 27 lines
---
 Documentation/mm/damon/design.rst | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
Documentation/mm/damon/design.rst +3 -2
index 0a86792f90a18..e82390e77a70a 100644
@@ -445,7 +445,7 @@ users to set the aimed amount of access events to observe via DAMON within
 given time interval.  The target can be specified by the user as a ratio of
 DAMON-observed access events to the theoretical maximum amount of the events
 (``access_bp``) that measured within a given number of aggregations
-(``aggrs``).
+(``aggrs``).  The ratio is in basis point (bp or 1/10,000).
 
 The DAMON-observed access events are calculated in byte granularity based on
 DAMON :ref:`region assumption <damon_design_region_based_sampling>`.  For
@@ -717,7 +717,8 @@ mechanism tries to make ``current_value`` of ``target_metric`` be same to
   in microseconds that measured from last quota reset to next quota reset.
   DAMOS does the measurement on its own, so only ``target_value`` need to be
   set by users at the initial time.  In other words, DAMOS does self-feedback.
-- ``node_mem_used_bp``: Specific NUMA node's used memory ratio in bp (1/10,000).
+- ``node_mem_used_bp``: Specific NUMA node's used memory ratio in basis point
+  (bp or 1/10,000).
 - ``node_mem_free_bp``: Specific NUMA node's free memory ratio in bp (1/10,000).
 - ``node_memcg_used_bp``: Specific cgroup's node used memory ratio for a
   specific NUMA node, in bp (1/10,000).
Signature · 1 line
~Randy