summaryrefslogtreecommitdiffstats
path: root/drivers/md/dm-mpath.c
diff options
context:
space:
mode:
authorMike Snitzer <snitzer@redhat.com>2020-08-24 20:19:55 +0200
committerMike Snitzer <snitzer@redhat.com>2020-09-01 22:41:56 +0200
commitc322ee9320eaa4013ca3620b1130992916b19b31 (patch)
tree2f7a395244b925895261a7386467a18054b3fc8c /drivers/md/dm-mpath.c
parentdm writecache: handle DAX to partitions on persistent memory correctly (diff)
downloadlinux-c322ee9320eaa4013ca3620b1130992916b19b31.tar.xz
linux-c322ee9320eaa4013ca3620b1130992916b19b31.zip
dm mpath: fix racey management of PG initialization
Commit 935fcc56abc3 ("dm mpath: only flush workqueue when needed") changed flush_multipath_work() to avoid needless workqueue flushing (of a multipath global workqueue). But that change didn't realize the surrounding flush_multipath_work() code should also only run if 'pg_init_in_progress' is set. Fix this by only doing all of flush_multipath_work()'s PG init related work if 'pg_init_in_progress' is set. Otherwise multipath_wait_for_pg_init_completion() will run unconditionally but the preceeding flush_workqueue(kmpath_handlerd) may not. This could lead to deadlock (though only if kmpath_handlerd never runs a corresponding work to decrement 'pg_init_in_progress'). It could also be, though highly unlikely, that the kmpath_handlerd work that does PG init completes before 'pg_init_in_progress' is set, and then an intervening DM table reload's multipath_postsuspend() triggers flush_multipath_work(). Fixes: 935fcc56abc3 ("dm mpath: only flush workqueue when needed") Cc: stable@vger.kernel.org Reported-by: Ben Marzinski <bmarzins@redhat.com> Signed-off-by: Mike Snitzer <snitzer@redhat.com>
Diffstat (limited to 'drivers/md/dm-mpath.c')
-rw-r--r--drivers/md/dm-mpath.c22
1 files changed, 15 insertions, 7 deletions
diff --git a/drivers/md/dm-mpath.c b/drivers/md/dm-mpath.c
index e3283d35c7fd..de4da825ade6 100644
--- a/drivers/md/dm-mpath.c
+++ b/drivers/md/dm-mpath.c
@@ -1287,17 +1287,25 @@ static void multipath_wait_for_pg_init_completion(struct multipath *m)
static void flush_multipath_work(struct multipath *m)
{
if (m->hw_handler_name) {
- set_bit(MPATHF_PG_INIT_DISABLED, &m->flags);
- smp_mb__after_atomic();
+ unsigned long flags;
+
+ if (!atomic_read(&m->pg_init_in_progress))
+ goto skip;
+
+ spin_lock_irqsave(&m->lock, flags);
+ if (atomic_read(&m->pg_init_in_progress) &&
+ !test_and_set_bit(MPATHF_PG_INIT_DISABLED, &m->flags)) {
+ spin_unlock_irqrestore(&m->lock, flags);
- if (atomic_read(&m->pg_init_in_progress))
flush_workqueue(kmpath_handlerd);
- multipath_wait_for_pg_init_completion(m);
+ multipath_wait_for_pg_init_completion(m);
- clear_bit(MPATHF_PG_INIT_DISABLED, &m->flags);
- smp_mb__after_atomic();
+ spin_lock_irqsave(&m->lock, flags);
+ clear_bit(MPATHF_PG_INIT_DISABLED, &m->flags);
+ }
+ spin_unlock_irqrestore(&m->lock, flags);
}
-
+skip:
if (m->queue_mode == DM_TYPE_BIO_BASED)
flush_work(&m->process_queued_bios);
flush_work(&m->trigger_event);