btrfs: remove the dev stats item for replace target device

[MINOR PROBLEM]
When a running dev-replace hits some error for the target device (devid
0), there will be a DEV_STATS with error records created at the next
transaction commit.

Unfortunately that item will never to be deleted.

This means at the next dev-replace, if the replace is interrupted, then
at the next mount, the target device will suddenly inherit the old error
records from that DEV_STATS item, which can give some false alerts on
that device.

This shouldn't affect end users that much, as it requires all the
following conditions to be met, which is pretty rare:

- The initial dev-replace hits some error on the target device
  E.g. write errors, but those errors itself is already a big problem
  for a running replace.

  This is required to create the DEV_STATS item in the first place.

- The next replace is interrupted
  This is required to allow btrfs to read from the old records.

[CAUSE]
Btrfs just never deletes the DEV_STATS after a replace is finished.

[FIX]
Remove the DEV_STATS item for devid 0 after the replace is finished.

This is not going to completely fix the error, as we still have other
error paths, e.g. by somehow the fs flips RO and can not start a new
transaction for the DEV_STATS item removal.

But those corner cases will be addressed by later patches which provide
a more generic fix to DEV_STATS related problems.

Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
This commit is contained in:
Qu Wenruo
2026-04-07 19:03:58 +09:30
committed by Johannes Thumshirn
parent a6908f88c9
commit 15a12af71c
3 changed files with 41 additions and 1 deletions

View File

@@ -1013,8 +1013,15 @@ static int btrfs_dev_replace_finishing(struct btrfs_fs_info *fs_info,
/* write back the superblocks */
trans = btrfs_start_transaction(root, 0);
if (!IS_ERR(trans))
if (!IS_ERR(trans)) {
/*
* Ignore any error here, if we failed to remove the DEV_STATS
* item for devid 0, it's not a big deal. We have other ways
* to address it.
*/
btrfs_remove_dev_stat_item(trans, BTRFS_DEV_REPLACE_DEVID);
btrfs_commit_transaction(trans);
}
mutex_unlock(&dev_replace->lock_finishing_cancel_unmount);

View File

@@ -2286,6 +2286,38 @@ void btrfs_scratch_superblocks(struct btrfs_fs_info *fs_info, struct btrfs_devic
update_dev_time(rcu_dereference_raw(device->name));
}
int btrfs_remove_dev_stat_item(struct btrfs_trans_handle *trans, u64 devid)
{
BTRFS_PATH_AUTO_RELEASE(path);
struct btrfs_fs_info *fs_info = trans->fs_info;
struct btrfs_root *dev_root = fs_info->dev_root;
struct btrfs_key key;
int ret;
key.objectid = BTRFS_DEV_STATS_OBJECTID;
key.type = BTRFS_PERSISTENT_ITEM_KEY;
key.offset = devid;
ret = btrfs_search_slot(trans, dev_root, &key, &path, -1, 1);
if (ret < 0) {
btrfs_warn(fs_info,
"error %d while searching for dev_stats item for devid %llu",
ret, devid);
return ret;
}
/* The dev stats item does not exist, nothing to bother. */
if (ret > 0)
return 0;
ret = btrfs_del_item(trans, dev_root, &path);
if (ret < 0) {
btrfs_warn(fs_info,
"error %d while deleting dev_stats item for devid %llu",
ret, devid);
return ret;
}
return 0;
}
int btrfs_rm_device(struct btrfs_fs_info *fs_info,
struct btrfs_dev_lookup_args *args,
struct file **bdev_file)

View File

@@ -933,6 +933,7 @@ bool btrfs_first_pending_extent(struct btrfs_device *device, u64 start, u64 len,
u64 *pending_start, u64 *pending_end);
bool btrfs_find_hole_in_pending_extents(struct btrfs_device *device,
u64 *start, u64 *len, u64 min_hole_size);
int btrfs_remove_dev_stat_item(struct btrfs_trans_handle *trans, u64 devid);
#ifdef CONFIG_BTRFS_FS_RUN_SANITY_TESTS
struct btrfs_io_context *alloc_btrfs_io_context(struct btrfs_fs_info *fs_info,