Skip to content

Commit

Permalink
btrfs: only run the extent map shrinker from kswapd tasks
Browse files Browse the repository at this point in the history
Currently the extent map shrinker can be run by any task when attempting
to allocate memory and there's enough memory pressure to trigger it.

To avoid too much latency we stop iterating over extent maps and removing
them once the task needs to reschudle. This logic was introduced in commit
b3ebb9b ("btrfs: stop extent map shrinker if reschedule is needed").

While that solved high latency problems for some use cases, it's still
not enough because with a too high number of tasks entering the extent map
shrinker code, either due to memory allocations or because they are a
kswapd task, we end up having a very high level of contention on some
spin locks, namely:

1) The fs_info->fs_roots_radix_lock spin lock, which we need to find
   roots to iterate over their inodes;

2) The spin lock of the xarray used to track open inodes for a root
   (struct btrfs_root::inodes) - on 6.10 kernels and below, it used to
   be a red black tree and the spin lock was root->inode_lock;

3) The fs_info->delayed_iput_lock spin lock since the shrinker adds
   delayed iputs (calls btrfs_add_delayed_iput()).

Instead of allowing the extent map shrinker to be run by any task, make
it run only by kswapd tasks. This still solves the problem of running
into OOM situations due to an unbounded extent map creation, which is
simple to trigger by direct IO writes, as described in the changelog
of commit 956a17d ("btrfs: add a shrinker for extent maps"), and
by a similar case when doing buffered IO on files with a very large
number of holes (keeping the file open and creating many holes, whose
extent maps are only released when the file is closed).

Reported-by: kzd <[email protected]>
Link: https://bugzilla.kernel.org/show_bug.cgi?id=219121
Reported-by: Octavia Togami <[email protected]>
Link: https://lore.kernel.org/linux-btrfs/CAHPNGSSt-a4ZZWrtJdVyYnJFscFjP9S7rMcvEMaNSpR556DdLA@mail.gmail.com/
Fixes: 956a17d ("btrfs: add a shrinker for extent maps")
CC: [email protected] # 6.10+
Tested-by: kzd <[email protected]>
Tested-by: Octavia Togami <[email protected]>
Signed-off-by: Filipe Manana <[email protected]>
Cherry-picked-for: https://gitlab.archlinux.org/archlinux/packaging/packages/linux/-/issues/70
  • Loading branch information
fdmanana authored and heftig committed Aug 19, 2024
1 parent 6dd7406 commit 97c7039
Show file tree
Hide file tree
Showing 2 changed files with 16 additions and 16 deletions.
22 changes: 6 additions & 16 deletions fs/btrfs/extent_map.c
Original file line number Diff line number Diff line change
Expand Up @@ -1065,8 +1065,7 @@ static long btrfs_scan_inode(struct btrfs_inode *inode, struct btrfs_em_shrink_c
return 0;

/*
* We want to be fast because we can be called from any path trying to
* allocate memory, so if the lock is busy we don't want to spend time
* We want to be fast so if the lock is busy we don't want to spend time
* waiting for it - either some task is about to do IO for the inode or
* we may have another task shrinking extent maps, here in this code, so
* skip this inode.
Expand Down Expand Up @@ -1109,9 +1108,7 @@ static long btrfs_scan_inode(struct btrfs_inode *inode, struct btrfs_em_shrink_c
/*
* Stop if we need to reschedule or there's contention on the
* lock. This is to avoid slowing other tasks trying to take the
* lock and because the shrinker might be called during a memory
* allocation path and we want to avoid taking a very long time
* and slowing down all sorts of tasks.
* lock.
*/
if (need_resched() || rwlock_needbreak(&tree->lock))
break;
Expand Down Expand Up @@ -1139,12 +1136,7 @@ static long btrfs_scan_root(struct btrfs_root *root, struct btrfs_em_shrink_ctx
if (ctx->scanned >= ctx->nr_to_scan)
break;

/*
* We may be called from memory allocation paths, so we don't
* want to take too much time and slowdown tasks.
*/
if (need_resched())
break;
cond_resched();

inode = btrfs_find_first_inode(root, min_ino);
}
Expand Down Expand Up @@ -1202,14 +1194,12 @@ long btrfs_free_extent_maps(struct btrfs_fs_info *fs_info, long nr_to_scan)
ctx.last_ino);
}

/*
* We may be called from memory allocation paths, so we don't want to
* take too much time and slowdown tasks, so stop if we need reschedule.
*/
while (ctx.scanned < ctx.nr_to_scan && !need_resched()) {
while (ctx.scanned < ctx.nr_to_scan) {
struct btrfs_root *root;
unsigned long count;

cond_resched();

spin_lock(&fs_info->fs_roots_radix_lock);
count = radix_tree_gang_lookup(&fs_info->fs_roots_radix,
(void **)&root,
Expand Down
10 changes: 10 additions & 0 deletions fs/btrfs/super.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include <linux/btrfs.h>
#include <linux/security.h>
#include <linux/fs_parser.h>
#include <linux/swap.h>
#include "messages.h"
#include "delayed-inode.h"
#include "ctree.h"
Expand Down Expand Up @@ -2394,6 +2395,15 @@ static long btrfs_free_cached_objects(struct super_block *sb, struct shrink_cont
const long nr_to_scan = min_t(unsigned long, LONG_MAX, sc->nr_to_scan);
struct btrfs_fs_info *fs_info = btrfs_sb(sb);

/*
* We may be called from any task trying to allocate memory and we don't
* want to slow it down with scanning and dropping extent maps. It would
* also cause heavy lock contention if many tasks concurrently enter
* here. Therefore only allow kswapd tasks to scan and drop extent maps.
*/
if (!current_is_kswapd())
return 0;

return btrfs_free_extent_maps(fs_info, nr_to_scan);
}

Expand Down

0 comments on commit 97c7039

Please sign in to comment.