]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
btrfs: prevent direct reclaim during compressed readahead
authorJP Kobryn (Meta) <jp.kobryn@linux.dev>
Sat, 28 Mar 2026 21:46:19 +0000 (14:46 -0700)
committerDavid Sterba <dsterba@suse.com>
Tue, 7 Apr 2026 16:56:08 +0000 (18:56 +0200)
Under memory pressure, direct reclaim can kick in during compressed
readahead. This puts the associated task into D-state. Then shrink_lruvec()
disables interrupts when acquiring the LRU lock. Under heavy pressure,
we've observed reclaim can run long enough that the CPU becomes prone to
CSD lock stalls since it cannot service incoming IPIs. Although the CSD
lock stalls are the worst case scenario, we have found many more subtle
occurrences of this latency on the order of seconds, over a minute in some
cases.

Prevent direct reclaim during compressed readahead. This is achieved by
using different GFP flags at key points when the bio is marked for
readahead.

There are two functions that allocate during compressed readahead:
btrfs_alloc_compr_folio() and add_ra_bio_pages(). Both currently use
GFP_NOFS which includes __GFP_DIRECT_RECLAIM.

For the internal API call btrfs_alloc_compr_folio(), the signature changes
to accept an additional gfp_t parameter. At the readahead call site, it
gets flags similar to GFP_NOFS but stripped of __GFP_DIRECT_RECLAIM.
__GFP_NOWARN is added since these allocations are allowed to fail. Demand
reads still use full GFP_NOFS and will enter reclaim if needed. All other
existing call sites of btrfs_alloc_compr_folio() now explicitly pass
GFP_NOFS to retain their current behavior.

add_ra_bio_pages() gains a bool parameter which allows callers to specify
if they want to allow direct reclaim or not. In either case, the
__GFP_NOWARN flag was added unconditionally since the allocations are
speculative.

There has been some previous work done on calling add_ra_bio_pages() [0].
This patch is complementary: where that patch reduces call frequency, this
patch reduces the latency associated with those calls.

[0] https://lore.kernel.org/linux-btrfs/656838ec1232314a2657716e59f4f15a8eadba64.1751492111.git.boris@bur.io/

Reviewed-by: Mark Harmstone <mark@harmstone.com>
Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: JP Kobryn (Meta) <jp.kobryn@linux.dev>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
fs/btrfs/compression.c
fs/btrfs/compression.h
fs/btrfs/inode.c
fs/btrfs/lzo.c
fs/btrfs/zlib.c
fs/btrfs/zstd.c

index e897342bece1f697fa5748ec1ed7e7e61c71da79..c5783ac1b64623e150c8f749b4cf900188e0192a 100644 (file)
@@ -180,7 +180,7 @@ static unsigned long btrfs_compr_pool_scan(struct shrinker *sh, struct shrink_co
 /*
  * Common wrappers for page allocation from compression wrappers
  */
-struct folio *btrfs_alloc_compr_folio(struct btrfs_fs_info *fs_info)
+struct folio *btrfs_alloc_compr_folio(struct btrfs_fs_info *fs_info, gfp_t gfp)
 {
        struct folio *folio = NULL;
 
@@ -200,7 +200,7 @@ struct folio *btrfs_alloc_compr_folio(struct btrfs_fs_info *fs_info)
                return folio;
 
 alloc:
-       return folio_alloc(GFP_NOFS, fs_info->block_min_order);
+       return folio_alloc(gfp, fs_info->block_min_order);
 }
 
 void btrfs_free_compr_folio(struct folio *folio)
@@ -368,7 +368,8 @@ struct compressed_bio *btrfs_alloc_compressed_write(struct btrfs_inode *inode,
 static noinline int add_ra_bio_pages(struct inode *inode,
                                     u64 compressed_end,
                                     struct compressed_bio *cb,
-                                    int *memstall, unsigned long *pflags)
+                                    int *memstall, unsigned long *pflags,
+                                    bool direct_reclaim)
 {
        struct btrfs_fs_info *fs_info = inode_to_fs_info(inode);
        pgoff_t end_index;
@@ -376,6 +377,7 @@ static noinline int add_ra_bio_pages(struct inode *inode,
        u64 cur = cb->orig_bbio->file_offset + orig_bio->bi_iter.bi_size;
        u64 isize = i_size_read(inode);
        int ret;
+       gfp_t constraint_gfp, cache_gfp;
        struct folio *folio;
        struct extent_map *em;
        struct address_space *mapping = inode->i_mapping;
@@ -405,6 +407,19 @@ static noinline int add_ra_bio_pages(struct inode *inode,
 
        end_index = (i_size_read(inode) - 1) >> PAGE_SHIFT;
 
+       /*
+        * Avoid direct reclaim when the caller does not allow it.  Since
+        * add_ra_bio_pages() is always speculative, suppress allocation warnings
+        * in either case.
+        */
+       if (!direct_reclaim) {
+               constraint_gfp = ~(__GFP_FS | __GFP_DIRECT_RECLAIM) | __GFP_NOWARN;
+               cache_gfp = (GFP_NOFS & ~__GFP_DIRECT_RECLAIM) | __GFP_NOWARN;
+       } else {
+               constraint_gfp = (~__GFP_FS) | __GFP_NOWARN;
+               cache_gfp = GFP_NOFS | __GFP_NOWARN;
+       }
+
        while (cur < compressed_end) {
                pgoff_t page_end;
                pgoff_t pg_index = cur >> PAGE_SHIFT;
@@ -434,12 +449,12 @@ static noinline int add_ra_bio_pages(struct inode *inode,
                        continue;
                }
 
-               folio = filemap_alloc_folio(mapping_gfp_constraint(mapping, ~__GFP_FS),
+               folio = filemap_alloc_folio(mapping_gfp_constraint(mapping, constraint_gfp),
                                            0, NULL);
                if (!folio)
                        break;
 
-               if (filemap_add_folio(mapping, folio, pg_index, GFP_NOFS)) {
+               if (filemap_add_folio(mapping, folio, pg_index, cache_gfp)) {
                        /* There is already a page, skip to page end */
                        cur += folio_size(folio);
                        folio_put(folio);
@@ -532,6 +547,7 @@ void btrfs_submit_compressed_read(struct btrfs_bio *bbio)
        unsigned int compressed_len;
        const u32 min_folio_size = btrfs_min_folio_size(fs_info);
        u64 file_offset = bbio->file_offset;
+       gfp_t gfp;
        u64 em_len;
        u64 em_start;
        struct extent_map *em;
@@ -539,6 +555,17 @@ void btrfs_submit_compressed_read(struct btrfs_bio *bbio)
        int memstall = 0;
        int ret;
 
+       /*
+        * If this is a readahead bio, prevent direct reclaim. This is done to
+        * avoid stalling on speculative allocations when memory pressure is
+        * high. The demand fault will retry with GFP_NOFS and enter direct
+        * reclaim if needed.
+        */
+       if (bbio->bio.bi_opf & REQ_RAHEAD)
+               gfp = (GFP_NOFS & ~__GFP_DIRECT_RECLAIM) | __GFP_NOWARN;
+       else
+               gfp = GFP_NOFS;
+
        /* we need the actual starting offset of this extent in the file */
        read_lock(&em_tree->lock);
        em = btrfs_lookup_extent_mapping(em_tree, file_offset, fs_info->sectorsize);
@@ -569,7 +596,7 @@ void btrfs_submit_compressed_read(struct btrfs_bio *bbio)
                struct folio *folio;
                u32 cur_len = min(compressed_len - i * min_folio_size, min_folio_size);
 
-               folio = btrfs_alloc_compr_folio(fs_info);
+               folio = btrfs_alloc_compr_folio(fs_info, gfp);
                if (!folio) {
                        ret = -ENOMEM;
                        goto out_free_bio;
@@ -585,7 +612,7 @@ void btrfs_submit_compressed_read(struct btrfs_bio *bbio)
        ASSERT(cb->bbio.bio.bi_iter.bi_size == compressed_len);
 
        add_ra_bio_pages(&inode->vfs_inode, em_start + em_len, cb, &memstall,
-                        &pflags);
+                        &pflags, !(bbio->bio.bi_opf & REQ_RAHEAD));
 
        cb->len = bbio->bio.bi_iter.bi_size;
        cb->bbio.bio.bi_iter.bi_sector = bbio->bio.bi_iter.bi_sector;
index 973530e9ce6c28ebe8fd12e9392ad0afbd0a645c..1022dc53ec51ea498e97352e9765d46a35bb064b 100644 (file)
@@ -98,7 +98,7 @@ void btrfs_submit_compressed_read(struct btrfs_bio *bbio);
 
 int btrfs_compress_str2level(unsigned int type, const char *str, int *level_ret);
 
-struct folio *btrfs_alloc_compr_folio(struct btrfs_fs_info *fs_info);
+struct folio *btrfs_alloc_compr_folio(struct btrfs_fs_info *fs_info, gfp_t gfp);
 void btrfs_free_compr_folio(struct folio *folio);
 
 struct workspace_manager {
index 1a4e6a9239aed303113d318035d0ccca18e3a180..769e96dbe6391bb843860b33564233acbc4e70f0 100644 (file)
@@ -9980,7 +9980,7 @@ ssize_t btrfs_do_encoded_write(struct kiocb *iocb, struct iov_iter *from,
                size_t bytes = min(min_folio_size, iov_iter_count(from));
                char *kaddr;
 
-               folio = btrfs_alloc_compr_folio(fs_info);
+               folio = btrfs_alloc_compr_folio(fs_info, GFP_NOFS);
                if (!folio) {
                        ret = -ENOMEM;
                        goto out_cb;
index 3e62e3e6490725f30f27baee3efc6ed9fdf2b73e..2de18c7b563afdbae9d8f2e099775db6a5617778 100644 (file)
@@ -202,7 +202,7 @@ static int copy_compressed_data_to_bio(struct btrfs_fs_info *fs_info,
        ASSERT((old_size >> sectorsize_bits) == (old_size + LZO_LEN - 1) >> sectorsize_bits);
 
        if (!*out_folio) {
-               *out_folio = btrfs_alloc_compr_folio(fs_info);
+               *out_folio = btrfs_alloc_compr_folio(fs_info, GFP_NOFS);
                if (!*out_folio)
                        return -ENOMEM;
        }
@@ -229,7 +229,7 @@ static int copy_compressed_data_to_bio(struct btrfs_fs_info *fs_info,
                        return -E2BIG;
 
                if (!*out_folio) {
-                       *out_folio = btrfs_alloc_compr_folio(fs_info);
+                       *out_folio = btrfs_alloc_compr_folio(fs_info, GFP_NOFS);
                        if (!*out_folio)
                                return -ENOMEM;
                }
@@ -280,7 +280,7 @@ int lzo_compress_bio(struct list_head *ws, struct compressed_bio *cb)
        ASSERT(bio->bi_iter.bi_size == 0);
        ASSERT(len);
 
-       folio_out = btrfs_alloc_compr_folio(fs_info);
+       folio_out = btrfs_alloc_compr_folio(fs_info, GFP_NOFS);
        if (!folio_out)
                return -ENOMEM;
 
index 9ebc90267e260aa5e9a0c4afc6a5ac33e855f8be..486b52db583ecbdb725d5c73b260511ca6133c57 100644 (file)
@@ -172,7 +172,7 @@ int zlib_compress_bio(struct list_head *ws, struct compressed_bio *cb)
        workspace->strm.total_in = 0;
        workspace->strm.total_out = 0;
 
-       out_folio = btrfs_alloc_compr_folio(fs_info);
+       out_folio = btrfs_alloc_compr_folio(fs_info, GFP_NOFS);
        if (out_folio == NULL) {
                ret = -ENOMEM;
                goto out;
@@ -254,7 +254,7 @@ int zlib_compress_bio(struct list_head *ws, struct compressed_bio *cb)
                                goto out;
                        }
 
-                       out_folio = btrfs_alloc_compr_folio(fs_info);
+                       out_folio = btrfs_alloc_compr_folio(fs_info, GFP_NOFS);
                        if (out_folio == NULL) {
                                ret = -ENOMEM;
                                goto out;
@@ -291,7 +291,7 @@ int zlib_compress_bio(struct list_head *ws, struct compressed_bio *cb)
                                goto out;
                        }
                        /* Get another folio for the stream end. */
-                       out_folio = btrfs_alloc_compr_folio(fs_info);
+                       out_folio = btrfs_alloc_compr_folio(fs_info, GFP_NOFS);
                        if (out_folio == NULL) {
                                ret = -ENOMEM;
                                goto out;
index 128646521ea8521ed87406220e1f1b8f7dd88571..86919293fd546b1e858517713d525ab8891f5704 100644 (file)
@@ -437,7 +437,7 @@ int zstd_compress_bio(struct list_head *ws, struct compressed_bio *cb)
        workspace->in_buf.size = btrfs_calc_input_length(in_folio, end, start);
 
        /* Allocate and map in the output buffer. */
-       out_folio = btrfs_alloc_compr_folio(fs_info);
+       out_folio = btrfs_alloc_compr_folio(fs_info, GFP_NOFS);
        if (out_folio == NULL) {
                ret = -ENOMEM;
                goto out;
@@ -480,7 +480,7 @@ int zstd_compress_bio(struct list_head *ws, struct compressed_bio *cb)
                                goto out;
                        }
 
-                       out_folio = btrfs_alloc_compr_folio(fs_info);
+                       out_folio = btrfs_alloc_compr_folio(fs_info, GFP_NOFS);
                        if (out_folio == NULL) {
                                ret = -ENOMEM;
                                goto out;
@@ -553,7 +553,7 @@ int zstd_compress_bio(struct list_head *ws, struct compressed_bio *cb)
                        ret = -E2BIG;
                        goto out;
                }
-               out_folio = btrfs_alloc_compr_folio(fs_info);
+               out_folio = btrfs_alloc_compr_folio(fs_info, GFP_NOFS);
                if (out_folio == NULL) {
                        ret = -ENOMEM;
                        goto out;