From: Chandra Seetharaman Subject: dm: avoid destroying table in dm_any_congested References: bnc#457205 Patch-mainline: 2.6.28-rc4 Signed-off-by: Nikanth Karthikesan [PATCH 1/3] for bnc 457205. Even though this fix is almost reverted by 3rd patch in this series, this patch is used as it fixes a suble bug of calling dm_table_put only when dm_get_table succeeds. Also this makes code more inline with the mainline. mainline commit 8a57dfc6f943c92b861c9a19b0c86ddcb2aba768 dm_any_congested() just checks for the DMF_BLOCK_IO and has no code to make sure that suspend waits for dm_any_congested() to complete. This patch adds such a check. Without it, a race can occur with dm_table_put() attempting to destroying the table in the wrong thread, the one running dm_any_congested() which is meant to be quick and return immediately. Two examples of problems: 1. Sleeping functions called from congested code, the caller of which holds a spin lock. 2. An ABBA deadlock between pdflush and multipathd. The two locks in contention are inode lock and kernel lock. Signed-off-by: Chandra Seetharaman Signed-off-by: Mikulas Patocka Signed-off-by: Alasdair G Kergon Index: linux-2.6.27/drivers/md/dm.c =================================================================== --- linux-2.6.27.orig/drivers/md/dm.c +++ linux-2.6.27/drivers/md/dm.c @@ -1640,22 +1640,32 @@ static void dm_unplug_all(struct request static int dm_any_congested(void *congested_data, int bdi_bits) { - int r; + int r = bdi_bits; struct mapped_device *md = (struct mapped_device *) congested_data; - struct dm_table *map = dm_get_table(md); + struct dm_table *map; - if (!map || test_bit(DMF_BLOCK_IO, &md->flags)) - r = bdi_bits; - else if (dm_request_based(md)) - /* - * Request-based dm cares about only own queue for - * the query about congestion status of request_queue - */ - r = md->queue->backing_dev_info.state & bdi_bits; - else - r = dm_table_any_congested(map, bdi_bits); + atomic_inc(&md->pending); + + if (!test_bit(DMF_BLOCK_IO, &md->flags)) { + map = dm_get_table(md); + if (map) { + if (dm_request_based(md)) + /* + * Request-based dm cares about only own queue for + * the query about congestion status of request_queue + */ + r = md->queue->backing_dev_info.state & bdi_bits; + else + r = dm_table_any_congested(map, bdi_bits); + dm_table_put(map); + } + } + + + if (!atomic_dec_return(&md->pending)) + /* nudge anyone waiting on suspend queue */ + wake_up(&md->wait); - dm_table_put(map); return r; }