]> git.itanic.dy.fi Git - linux-stable/commitdiff
ext4: limit the number of retries after discarding preallocations blocks
authorTheodore Ts'o <tytso@mit.edu>
Thu, 1 Sep 2022 22:03:14 +0000 (18:03 -0400)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 28 Sep 2022 09:32:27 +0000 (11:32 +0200)
commit 80fa46d6b9e7b1527bfd2197d75431fd9c382161 upstream.

This patch avoids threads live-locking for hours when a large number
threads are competing over the last few free extents as they blocks
getting added and removed from preallocation pools.  From our bug
reporter:

   A reliable way for triggering this has multiple writers
   continuously write() to files when the filesystem is full, while
   small amounts of space are freed (e.g. by truncating a large file
   -1MiB at a time). In the local filesystem, this can be done by
   simply not checking the return code of write (0) and/or the error
   (ENOSPACE) that is set. Over NFS with an async mount, even clients
   with proper error checking will behave this way since the linux NFS
   client implementation will not propagate the server errors [the
   write syscalls immediately return success] until the file handle is
   closed. This leads to a situation where NFS clients send a
   continuous stream of WRITE rpcs which result in ERRNOSPACE -- but
   since the client isn't seeing this, the stream of writes continues
   at maximum network speed.

   When some space does appear, multiple writers will all attempt to
   claim it for their current write. For NFS, we may see dozens to
   hundreds of threads that do this.

   The real-world scenario of this is database backup tooling (in
   particular, github.com/mdkent/percona-xtrabackup) which may write
   large files (>1TiB) to NFS for safe keeping. Some temporary files
   are written, rewound, and read back -- all before closing the file
   handle (the temp file is actually unlinked, to trigger automatic
   deletion on close/crash.) An application like this operating on an
   async NFS mount will not see an error code until TiB have been
   written/read.

   The lockup was observed when running this database backup on large
   filesystems (64 TiB in this case) with a high number of block
   groups and no free space. Fragmentation is generally not a factor
   in this filesystem (~thousands of large files, mostly contiguous
   except for the parts written while the filesystem is at capacity.)

Signed-off-by: Theodore Ts'o <tytso@mit.edu>
Cc: stable@kernel.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
fs/ext4/mballoc.c

index 38e7dc2531b174ed01bc6a5e21c4a4cc5869eaa3..697af6603652426b6115a6505b9863c09c900d2f 100644 (file)
@@ -5559,6 +5559,7 @@ ext4_fsblk_t ext4_mb_new_blocks(handle_t *handle,
        ext4_fsblk_t block = 0;
        unsigned int inquota = 0;
        unsigned int reserv_clstrs = 0;
+       int retries = 0;
        u64 seq;
 
        might_sleep();
@@ -5661,7 +5662,8 @@ ext4_fsblk_t ext4_mb_new_blocks(handle_t *handle,
                        ar->len = ac->ac_b_ex.fe_len;
                }
        } else {
-               if (ext4_mb_discard_preallocations_should_retry(sb, ac, &seq))
+               if (++retries < 3 &&
+                   ext4_mb_discard_preallocations_should_retry(sb, ac, &seq))
                        goto repeat;
                /*
                 * If block allocation fails then the pa allocated above