From: sashiko-bot@kernel.org
To: "Thomas Hellström" <thomas.hellstrom@linux.intel.com>
Cc: intel-xe@lists.freedesktop.org
Subject: Re: [PATCH v3 2/3] drm/xe: Split the bo shrinker into purgeable and non-purgeable shrinkers
Date: Thu, 08 Oct 2026 12:46:01 +0000 [thread overview]
Message-ID: <sashiko-outbox-164172@kernel.org> (raw)
In-Reply-To: <20261008123024.38522-3-thomas.hellstrom@linux.intel.com>
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] drm/xe: split shrinkers fail to isolate purgeable objects
- [High] drm/xe: purgeable shrinker permanently stalls on non-purgeable LRU objects
--
commit 03a2b4c22a7c807ab5b2d97ec7177c503b0367aa
Author: Thomas Hellström <thomas.hellstrom@linux.intel.com>
drm/xe: Split the bo shrinker into purgeable and non-purgeable shrinkers
Purgeable objects are reclaimed by simply discarding their pages, with
no writeback required. They are therefore much cheaper to shrink than
regular shrinkable (non-purgeable) objects, whose reclaim may need to
wait for the GPU or write dirty compressed contents back to a backup
store, and whose contents need to be read back on next use.
Split the single xe_shrinker into two independent struct shrinker
instances registered against the shrinker core, sharing the same
underlying accounting (shrinkable_pages, purgeable_pages,
populated_tts) and locking:
[ ... ]
> --- a/drivers/gpu/drm/xe/xe_shrinker.c
> +++ b/drivers/gpu/drm/xe/xe_shrinker.c
> @@ -213,63 +235,52 @@ static bool xe_shrinker_runtime_pm_get(struct xe_shrinker *shrinker, bool force,
[ ... ]
> -static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_control *sc)
> +static unsigned long xe_shrinker_scan_purgeable(struct shrinker *shrink,
> + struct shrink_control *sc)
> {
> struct xe_shrinker *shrinker = to_xe_shrinker(shrink);
> struct ttm_operation_ctx ctx = {
> .interruptible = false,
> .no_wait_gpu = ttm_bo_shrink_avoid_wait(),
> };
> - unsigned long nr_to_scan, nr_scanned = 0, freed = 0;
> + unsigned long nr_to_scan = sc->nr_to_scan, nr_scanned = 0, freed = 0;
> struct xe_bo_shrink_flags shrink_flags = {
> .purge = true,
> - /* Don't request writeback without __GFP_IO. */
> - .writeback = !ctx.no_wait_gpu && (sc->gfp_mask & __GFP_IO),
> };
> - bool runtime_pm;
> - bool purgeable;
> - bool can_backup = !!(sc->gfp_mask & __GFP_FS);
> -
> - nr_to_scan = sc->nr_to_scan;
> -
> - read_lock(&shrinker->lock);
> - purgeable = !!shrinker->purgeable_pages;
> - read_unlock(&shrinker->lock);
>
> - /* Might need runtime PM. Try to wake early if it looks like it. */
> - runtime_pm = xe_shrinker_runtime_pm_get(shrinker, false, nr_to_scan, can_backup);
> -
> - if (purgeable && nr_scanned < nr_to_scan)
> - xe_shrinker_walk(shrinker, &ctx, shrink_flags,
> - nr_to_scan, &nr_scanned, &freed);
> + xe_shrinker_walk(shrinker, &ctx, shrink_flags, nr_to_scan, &nr_scanned, &freed);
[Severity: High]
Does this standalone purgeable shrinker permanently stall when it encounters
non-purgeable LRU objects?
When the core mm invokes scan_objects for the purgeable shrinker, it
traverses the TTM LRU list which mixes both purgeable and non-purgeable
objects. If a non-purgeable object is encountered, xe_bo_shrink returns
-EBUSY:
drivers/gpu/drm/xe/xe_bo.c:xe_bo_shrink() {
...
if (!(tt->page_flags & TTM_TT_FLAG_EXTERNAL_MAPPABLE) ||
(flags.purge && !xe_tt->purgeable))
return -EBUSY;
...
}
The LRU walk in __xe_shrinker_walk treats this negative return as a fatal
error and aborts the entire scan:
drivers/gpu/drm/xe/xe_shrinker.c:__xe_shrinker_walk() {
...
lret = xe_bo_shrink(ctx, ttm_bo, flags, scanned);
if (lret < 0) {
ret = lret;
goto out;
}
...
}
Because the shrinkers are now decoupled, this shrinker can no longer rely
on the non-purgeable shrinker running immediately afterwards in the same
context to clear the obstruction. Any memory pressure triggering the shrinker
when a non-purgeable object happens to be at the LRU head might cause this
to abort without freeing any pages.
>
> sc->nr_scanned = nr_scanned;
> - if (nr_scanned >= nr_to_scan || !can_backup)
> - goto out;
>
> - /* If we didn't wake before, try to do it now if needed. */
> - if (!runtime_pm)
> - runtime_pm = xe_shrinker_runtime_pm_get(shrinker, true, 0, can_backup);
> + return nr_scanned ? freed : SHRINK_STOP;
> +}
>
> - shrink_flags.purge = false;
> +static unsigned long xe_shrinker_scan(struct shrinker *shrink, struct shrink_control *sc)
> +{
> + struct xe_shrinker *shrinker = to_xe_shrinker(shrink);
> + struct ttm_operation_ctx ctx = {
> + .interruptible = false,
> + .no_wait_gpu = ttm_bo_shrink_avoid_wait(),
> + };
> + unsigned long nr_to_scan = sc->nr_to_scan, nr_scanned = 0, freed = 0;
> + struct xe_bo_shrink_flags shrink_flags = {
> + .purge = false,
> + /* Don't request writeback without __GFP_IO. */
> + .writeback = !ctx.no_wait_gpu && (sc->gfp_mask & __GFP_IO),
> + };
[Severity: High]
Does the non-purgeable shrinker fail to isolate purgeable objects?
Since this shrinker scans with flags.purge = false, xe_bo_shrink does not
skip purgeable objects:
drivers/gpu/drm/xe/xe_bo.c:xe_bo_shrink() {
...
if (xe_tt->purgeable) {
if (bo->resource->mem_type != XE_PL_SYSTEM)
lret = xe_bo_move_notify(xe_bo, ctx);
if (!lret)
lret = xe_bo_shrink_purge(ctx, bo, scanned);
...
}
Will this cause the non-purgeable shrinker, which has a higher scan target
due to higher seeks, to fulfill its scan quota by stealing cheap purgeable
objects instead of forcing writeback on non-purgeable objects? If so, the
proportional reclaim mechanism might be defeated.
> + bool runtime_pm;
> +
> + /* Might need runtime PM to access backed-up or CCS-compressed data. */
> + runtime_pm = xe_shrinker_runtime_pm_get(shrinker);
>
> - xe_shrinker_walk(shrinker, &ctx, shrink_flags,
> - nr_to_scan, &nr_scanned, &freed);
> + xe_shrinker_walk(shrinker, &ctx, shrink_flags, nr_to_scan, &nr_scanned, &freed);
>
> sc->nr_scanned = nr_scanned;
> -out:
> xe_shrinker_runtime_pm_put(shrinker, runtime_pm);
> +
> return nr_scanned ? freed : SHRINK_STOP;
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261008123024.38522-1-thomas.hellstrom@linux.intel.com?part=2
next prev parent reply other threads:[~2026-10-08 12:46 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-08 12:30 [PATCH v3 0/3] drm/xe: Adjust the bo shrinker batch size Thomas Hellström
2026-10-08 12:30 ` [PATCH v3 1/3] drm/xe: Track number of populated ttm_tts in the shrinker Thomas Hellström
2026-10-08 12:44 ` sashiko-bot
2026-10-08 12:30 ` [PATCH v3 2/3] drm/xe: Split the bo shrinker into purgeable and non-purgeable shrinkers Thomas Hellström
2026-10-08 12:46 ` sashiko-bot [this message]
2026-10-08 12:30 ` [PATCH v3 3/3] drm/xe: Update non-purgeable shrinker batch size based on average BO size Thomas Hellström
2026-10-08 13:17 ` ✓ CI.KUnit: success for drm/xe: Adjust the bo shrinker batch size (rev3) Patchwork
2026-10-08 14:00 ` ✓ Xe.CI.BAT: " Patchwork
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=sashiko-outbox-164172@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=intel-xe@lists.freedesktop.org \
--cc=sashiko-reviews@lists.linux.dev \
--cc=thomas.hellstrom@linux.intel.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.