mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-12-27 11:06:41 -05:00
dm: Fix deadlock when reloading a multipath table
Request-based devices (dm-multipath) queue I/O in blk-mq on noflush suspends. Any queued IO will make it impossible to freeze the queue. If a process attempts to update the queue limits while there is queued IO, it can be get stuck holding the limits lock, while unable to freeze the queue. If device-mapper then attempts to update the limits during a table swap, it will deadlock trying to grab the limits lock while making it impossible to flush the IO. Disallow updating the queue limits during a table swap, when updating an immutable request-based dm device (dm-multipath) during a noflush suspend. It is userspace's responsibility to make sure that the new table uses the same limits as the existing table if it asks for a noflush suspend. Signed-off-by: Benjamin Marzinski <bmarzins@redhat.com> Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
This commit is contained in:
committed by
Mikulas Patocka
parent
4929ba5c5b
commit
be4addb191
@@ -2043,6 +2043,10 @@ bool dm_table_supports_size_change(struct dm_table *t, sector_t old_size,
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function will be skipped by noflush reloads of immutable request
|
||||
* based devices (dm-mpath).
|
||||
*/
|
||||
int dm_table_set_restrictions(struct dm_table *t, struct request_queue *q,
|
||||
struct queue_limits *limits)
|
||||
{
|
||||
|
||||
@@ -4383,11 +4383,8 @@ static void thin_postsuspend(struct dm_target *ti)
|
||||
{
|
||||
struct thin_c *tc = ti->private;
|
||||
|
||||
/*
|
||||
* The dm_noflush_suspending flag has been cleared by now, so
|
||||
* unfortunately we must always run this.
|
||||
*/
|
||||
noflush_work(tc, do_noflush_stop);
|
||||
if (dm_noflush_suspending(ti))
|
||||
noflush_work(tc, do_noflush_stop);
|
||||
}
|
||||
|
||||
static int thin_preresume(struct dm_target *ti)
|
||||
|
||||
@@ -2439,7 +2439,6 @@ static struct dm_table *__bind(struct mapped_device *md, struct dm_table *t,
|
||||
{
|
||||
struct dm_table *old_map;
|
||||
sector_t size, old_size;
|
||||
int ret;
|
||||
|
||||
lockdep_assert_held(&md->suspend_lock);
|
||||
|
||||
@@ -2454,11 +2453,13 @@ static struct dm_table *__bind(struct mapped_device *md, struct dm_table *t,
|
||||
|
||||
set_capacity(md->disk, size);
|
||||
|
||||
ret = dm_table_set_restrictions(t, md->queue, limits);
|
||||
if (ret) {
|
||||
set_capacity(md->disk, old_size);
|
||||
old_map = ERR_PTR(ret);
|
||||
goto out;
|
||||
if (limits) {
|
||||
int ret = dm_table_set_restrictions(t, md->queue, limits);
|
||||
if (ret) {
|
||||
set_capacity(md->disk, old_size);
|
||||
old_map = ERR_PTR(ret);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -2836,6 +2837,7 @@ static void dm_wq_work(struct work_struct *work)
|
||||
|
||||
static void dm_queue_flush(struct mapped_device *md)
|
||||
{
|
||||
clear_bit(DMF_NOFLUSH_SUSPENDING, &md->flags);
|
||||
clear_bit(DMF_BLOCK_IO_FOR_SUSPEND, &md->flags);
|
||||
smp_mb__after_atomic();
|
||||
queue_work(md->wq, &md->work);
|
||||
@@ -2848,6 +2850,7 @@ struct dm_table *dm_swap_table(struct mapped_device *md, struct dm_table *table)
|
||||
{
|
||||
struct dm_table *live_map = NULL, *map = ERR_PTR(-EINVAL);
|
||||
struct queue_limits limits;
|
||||
bool update_limits = true;
|
||||
int r;
|
||||
|
||||
mutex_lock(&md->suspend_lock);
|
||||
@@ -2856,20 +2859,31 @@ struct dm_table *dm_swap_table(struct mapped_device *md, struct dm_table *table)
|
||||
if (!dm_suspended_md(md))
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* To avoid a potential deadlock locking the queue limits, disallow
|
||||
* updating the queue limits during a table swap, when updating an
|
||||
* immutable request-based dm device (dm-multipath) during a noflush
|
||||
* suspend. It is userspace's responsibility to make sure that the new
|
||||
* table uses the same limits as the existing table, if it asks for a
|
||||
* noflush suspend.
|
||||
*/
|
||||
if (dm_request_based(md) && md->immutable_target &&
|
||||
__noflush_suspending(md))
|
||||
update_limits = false;
|
||||
/*
|
||||
* If the new table has no data devices, retain the existing limits.
|
||||
* This helps multipath with queue_if_no_path if all paths disappear,
|
||||
* then new I/O is queued based on these limits, and then some paths
|
||||
* reappear.
|
||||
*/
|
||||
if (dm_table_has_no_data_devices(table)) {
|
||||
else if (dm_table_has_no_data_devices(table)) {
|
||||
live_map = dm_get_live_table_fast(md);
|
||||
if (live_map)
|
||||
limits = md->queue->limits;
|
||||
dm_put_live_table_fast(md);
|
||||
}
|
||||
|
||||
if (!live_map) {
|
||||
if (update_limits && !live_map) {
|
||||
r = dm_calculate_queue_limits(table, &limits);
|
||||
if (r) {
|
||||
map = ERR_PTR(r);
|
||||
@@ -2877,7 +2891,7 @@ struct dm_table *dm_swap_table(struct mapped_device *md, struct dm_table *table)
|
||||
}
|
||||
}
|
||||
|
||||
map = __bind(md, table, &limits);
|
||||
map = __bind(md, table, update_limits ? &limits : NULL);
|
||||
dm_issue_global_event();
|
||||
|
||||
out:
|
||||
@@ -2930,7 +2944,6 @@ static int __dm_suspend(struct mapped_device *md, struct dm_table *map,
|
||||
|
||||
/*
|
||||
* DMF_NOFLUSH_SUSPENDING must be set before presuspend.
|
||||
* This flag is cleared before dm_suspend returns.
|
||||
*/
|
||||
if (noflush)
|
||||
set_bit(DMF_NOFLUSH_SUSPENDING, &md->flags);
|
||||
@@ -2993,8 +3006,6 @@ static int __dm_suspend(struct mapped_device *md, struct dm_table *map,
|
||||
if (!r)
|
||||
set_bit(dmf_suspended_flag, &md->flags);
|
||||
|
||||
if (noflush)
|
||||
clear_bit(DMF_NOFLUSH_SUSPENDING, &md->flags);
|
||||
if (map)
|
||||
synchronize_srcu(&md->io_barrier);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user