Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

dm: allocate a special workqueue for deferred device removal

The commit 2c140a246dc ("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: Mikulas Patocka <mpatocka@redhat.com>
Signed-off-by: Dan Carpenter <dan.carpenter@oracle.com>
Signed-off-by: Mike Snitzer <snitzer@redhat.com>
Cc: stable@vger.kernel.org # 3.13+

authored by

Mikulas Patocka and committed by
Mike Snitzer
acfe0ad7 4c834452

+13 -2
+13 -2
drivers/md/dm.c
··· 54 54 55 55 static DECLARE_WORK(deferred_remove_work, do_deferred_remove); 56 56 57 + static struct workqueue_struct *deferred_remove_workqueue; 58 + 57 59 /* 58 60 * For bio-based dm. 59 61 * One of these is allocated per bio. ··· 278 276 if (r) 279 277 goto out_free_rq_tio_cache; 280 278 279 + deferred_remove_workqueue = alloc_workqueue("kdmremove", WQ_UNBOUND, 1); 280 + if (!deferred_remove_workqueue) { 281 + r = -ENOMEM; 282 + goto out_uevent_exit; 283 + } 284 + 281 285 _major = major; 282 286 r = register_blkdev(_major, _name); 283 287 if (r < 0) 284 - goto out_uevent_exit; 288 + goto out_free_workqueue; 285 289 286 290 if (!_major) 287 291 _major = r; 288 292 289 293 return 0; 290 294 295 + out_free_workqueue: 296 + destroy_workqueue(deferred_remove_workqueue); 291 297 out_uevent_exit: 292 298 dm_uevent_exit(); 293 299 out_free_rq_tio_cache: ··· 309 299 static void local_exit(void) 310 300 { 311 301 flush_scheduled_work(); 302 + destroy_workqueue(deferred_remove_workqueue); 312 303 313 304 kmem_cache_destroy(_rq_tio_cache); 314 305 kmem_cache_destroy(_io_cache); ··· 418 407 419 408 if (atomic_dec_and_test(&md->open_count) && 420 409 (test_bit(DMF_DEFERRED_REMOVE, &md->flags))) 421 - schedule_work(&deferred_remove_work); 410 + queue_work(deferred_remove_workqueue, &deferred_remove_work); 422 411 423 412 dm_put(md); 424 413