Commit cee4cfd6 authored by Filipe Manana's avatar Filipe Manana Committed by David Sterba
Browse files

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: default avatarFilipe Manana <fdmanana@suse.com>
Reviewed-by: default avatarDavid Sterba <dsterba@suse.com>
Signed-off-by: default avatarDavid Sterba <dsterba@suse.com>
parent e0a85137
Loading
Loading
Loading
Loading
+30 −0
Original line number Diff line number Diff line
@@ -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) {