Commit acfe0ad7 authored by Mikulas Patocka's avatar Mikulas Patocka Committed by Mike Snitzer

dm: allocate a special workqueue for deferred device removal

The commit 2c140a24 ("dm: allow remove to be deferred") introduced a
deferred removal feature for the device mapper.  When this feature is
used (by passing a flag DM_DEFERRED_REMOVE to DM_DEV_REMOVE_CMD ioctl)
and the user tries to remove a device that is currently in use, the
device will be removed automatically in the future when the last user
closes it.

Device mapper used the system workqueue to perform deferred removals.
However, some targets (dm-raid1, dm-mpath, dm-stripe) flush work items
scheduled for the system workqueue from their destructor.  If the
destructor itself is called from the system workqueue during deferred
removal, it introduces a possible deadlock - the workqueue tries to flush
itself.

Fix this possible deadlock by introducing a new workqueue for deferred
removals.  We allocate just one workqueue for all dm targets.  The
ability of dm targets to process IOs isn't dependent on deferred removal
of unused targets, so a deadlock due to shared workqueue isn't possible.

Also, cleanup local_init() to eliminate potential for returning success
on failure.
Signed-off-by: default avatarMikulas Patocka <mpatocka@redhat.com>
Signed-off-by: default avatarDan Carpenter <dan.carpenter@oracle.com>
Signed-off-by: default avatarMike Snitzer <snitzer@redhat.com>
Cc: stable@vger.kernel.org # 3.13+
parent 4c834452
...@@ -54,6 +54,8 @@ static void do_deferred_remove(struct work_struct *w); ...@@ -54,6 +54,8 @@ static void do_deferred_remove(struct work_struct *w);
static DECLARE_WORK(deferred_remove_work, do_deferred_remove); static DECLARE_WORK(deferred_remove_work, do_deferred_remove);
static struct workqueue_struct *deferred_remove_workqueue;
/* /*
* For bio-based dm. * For bio-based dm.
* One of these is allocated per bio. * One of these is allocated per bio.
...@@ -276,16 +278,24 @@ static int __init local_init(void) ...@@ -276,16 +278,24 @@ static int __init local_init(void)
if (r) if (r)
goto out_free_rq_tio_cache; goto out_free_rq_tio_cache;
deferred_remove_workqueue = alloc_workqueue("kdmremove", WQ_UNBOUND, 1);
if (!deferred_remove_workqueue) {
r = -ENOMEM;
goto out_uevent_exit;
}
_major = major; _major = major;
r = register_blkdev(_major, _name); r = register_blkdev(_major, _name);
if (r < 0) if (r < 0)
goto out_uevent_exit; goto out_free_workqueue;
if (!_major) if (!_major)
_major = r; _major = r;
return 0; return 0;
out_free_workqueue:
destroy_workqueue(deferred_remove_workqueue);
out_uevent_exit: out_uevent_exit:
dm_uevent_exit(); dm_uevent_exit();
out_free_rq_tio_cache: out_free_rq_tio_cache:
...@@ -299,6 +309,7 @@ static int __init local_init(void) ...@@ -299,6 +309,7 @@ static int __init local_init(void)
static void local_exit(void) static void local_exit(void)
{ {
flush_scheduled_work(); flush_scheduled_work();
destroy_workqueue(deferred_remove_workqueue);
kmem_cache_destroy(_rq_tio_cache); kmem_cache_destroy(_rq_tio_cache);
kmem_cache_destroy(_io_cache); kmem_cache_destroy(_io_cache);
...@@ -407,7 +418,7 @@ static void dm_blk_close(struct gendisk *disk, fmode_t mode) ...@@ -407,7 +418,7 @@ static void dm_blk_close(struct gendisk *disk, fmode_t mode)
if (atomic_dec_and_test(&md->open_count) && if (atomic_dec_and_test(&md->open_count) &&
(test_bit(DMF_DEFERRED_REMOVE, &md->flags))) (test_bit(DMF_DEFERRED_REMOVE, &md->flags)))
schedule_work(&deferred_remove_work); queue_work(deferred_remove_workqueue, &deferred_remove_work);
dm_put(md); dm_put(md);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment