summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFilipe Manana <fdmanana@suse.com>2026-03-18 16:39:51 +0300
committerDavid Sterba <dsterba@suse.com>2026-04-07 19:56:05 +0300
commitcee4cfd6cc32a272a438836113cb698e2bca452c (patch)
tree6c0cb553b8e6f1f9c28017e1ef20dc7f2372ef57
parente0a85137a882db789b1bccc1e7db06356ac8c69f (diff)
downloadlinux-cee4cfd6cc32a272a438836113cb698e2bca452c.tar.xz
btrfs: avoid taking the device_list_mutex in btrfs_run_dev_stats()
btrfs_run_dev_stats() is called during the critical section of a transaction commit and it takes the device_list_mutex, which is also acquired by fitrim, which does discard operations while holding that mutex. Most of the time, if we are on a healthy filesystem, we don't have new stat updates to persist in the device tree, so blocking on the device_list_mutex is just wasting time and making any tasks that need to start a new transaction wait longer that necessary. Since the device list is RCU safe/protected, make btrfs_run_dev_stats() do an initial check for device stat updates using RCU and quit without taking the device_list_mutex in case there are no new device stats that need to be persisted in the device tree. Also note that adding/removing devices also requires starting a transaction, and since btrfs_run_dev_stats() is called from the critical section of a transaction commit, no one can be concurrently adding or removing a device while btrfs_run_dev_stats() is called. Signed-off-by: Filipe Manana <fdmanana@suse.com> Reviewed-by: David Sterba <dsterba@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
-rw-r--r--fs/btrfs/volumes.c30
1 files changed, 30 insertions, 0 deletions
diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c
index deaf1997a3a5..33fa73668534 100644
--- a/fs/btrfs/volumes.c
+++ b/fs/btrfs/volumes.c
@@ -8250,6 +8250,36 @@ int btrfs_run_dev_stats(struct btrfs_trans_handle *trans)
struct btrfs_device *device;
int stats_cnt;
int ret = 0;
+ bool need_update_dev_stats = false;
+
+ /*
+ * Do an initial pass using RCU to see if we need to update any dev
+ * stats item. This is to avoid taking the device_list_mutex which is
+ * acquired by the fitrim operation and can take a while since it does
+ * discard operations while holding that mutex. Most of the time, if
+ * we are on a healthy filesystem, we don't have new stat updates, so
+ * this avoids blocking on that mutex, which is specially important
+ * because we are called during the critical section of a transaction
+ * commit, therefore blocking new transactions from starting while
+ * discard is running.
+ *
+ * Also note that adding/removing devices also requires starting a
+ * transaction, and since we are called from the critical section of a
+ * transaction commit, no one can be concurrently adding or removing a
+ * device.
+ */
+ rcu_read_lock();
+ list_for_each_entry_rcu(device, &fs_devices->devices, dev_list) {
+ if (device->dev_stats_valid &&
+ atomic_read(&device->dev_stats_ccnt) != 0) {
+ need_update_dev_stats = true;
+ break;
+ }
+ }
+ rcu_read_unlock();
+
+ if (!need_update_dev_stats)
+ return 0;
mutex_lock(&fs_devices->device_list_mutex);
list_for_each_entry(device, &fs_devices->devices, dev_list) {