dm: restrict conflicting table loads and improve queue initialization
Subsystems (2)
v2 cover dm: restrict conflicting table loads and improve queue initialization
A mapped_device now has a specific immutable type (md->type) that is set during the initial table_load (see: patch 1/2).
The DM device's request_queue will only have an elevator allocated and exposed via sysfs if it is request-based (see: patch 2/2).
Please see the individual patch headers for more details. (Kiyoshi, if you approve please reply with your "Acked-by" :)
This patchset is based on agk's DM "editing" tree:
http://www.kernel.org/pub/linux/kernel/people/agk/patches/2.6/editing/
(apply all patches listed in the series file up to NEXT_PATCHES_END)
This also patchset depends on the following linux-2.6-block.git 'for-linus' branch commits:
01effb0 block: allow initialization of previously allocated request_queue e36f724 block: Adjust elv_iosched_show to return "none" for bio-based DM
I've made a full quilt series available here (builds on 2.6.34):
http://people.redhat.com/msnitzer/patches/dm-queue-init/latest/
A monolithic patch is also available here:
http://people.redhat.com/msnitzer/patches/dm-queue-init/latest/monolithic.patch
I welcome all comments/review.
v2 changes:
- removed do_resume and table_clear interlock
- rebased to agk's latest "editing" tree
- dropped the dm_get_verified_mdptr and find_device_noinit patches that were at the end of v1; can revisit that code duplication cleanup later
Mike Snitzer (2):
dm: prevent table type changes after initial table load
dm: only initialize full request_queue for request-based device
drivers/md/dm-ioctl.c | 40 ++++++++++ drivers/md/dm.c | 189 ++++++++++++++++++++++++++++++++++++++++-------- drivers/md/dm.h | 8 ++ 3 files changed, 205 insertions(+), 32 deletions(-)
PATCH v2 1/2 dm: prevent table type changes after initial table load
Before table_load() stages an inactive table atomically check if its type conflicts with a previously established device type (md->type).
Introduce 'type_lock' in mapped_device structure and use it to protect md->type access. table_load() sets md->type without concern for:
- another table_load() racing to set conflicting md->type.
- do_resume() making a conflicting table live.
Allowed transitions:UNKNOWN_MD_TYPE => BIO_BASED_MD_TYPEUNKNOWN_MD_TYPE => REQUEST_BASED_MD_TYPE
Once a table load occurs the DM device's type is completely immutable.
This prevents a table reload from switching the inactive table directly to a conflicting type (even if the table is explicitly cleared before load).
--- drivers/md/dm-ioctl.c | 29 ++++++++++++++++++++ drivers/md/dm.c | 71 +++++++++++++++++++++++++++++++++++++++++++++----- drivers/md/dm.h | 6 ++++ 3 files changed, 99 insertions(+), 7 deletions(-)
drivers/md/dm-ioctl.c +29 -0
@@ -1176,12 +1176,39 @@ static int table_load(struct dm_ioctl *p goto out; } +/* +* Protect md->type against concurrent table loads. +* Locking strategy: +* + Leverage fact that md's type cannot change after initial table load. +* - Only protect type in table_load() -- not in do_resume(). +* +* + Protect type while working to stage an inactive table: +* - check if table's type conflicts with md->type +* (holding: md->type_lock) +* - stage inactive table (hc->new_map) +* (holding: md->type_lock + _hash_lock) +*/ +dm_lock_md_type (md); + +if (dm_unknown_md_type (md)) { +/* initial table load, set md's type based on table's type */ +dm_set_md_type (md, t); + }else if (!dm_md_type_matches_table (md, t)) { +DMWARN ("can't change device type after initial table load." ); +dm_table_destroy (t); +dm_unlock_md_type (md); + r = -EINVAL ; + goto out; + } + +/* stage inactive table */ down_write (&_hash_lock); hc =dm_get_mdptr (md);if (!hc || hc->md != md) {DMWARN ("device has been removed from the dev hash table." );dm_table_destroy (t);up_write (&_hash_lock); +dm_unlock_md_type (md); r = -ENXIO ; goto out; }
@@ -1191,6 +1218,8 @@ static int table_load(struct dm_ioctl *p hc->new_map = t;up_write (&_hash_lock); +dm_unlock_md_type (md); + param->flags |=DM_INACTIVE_PRESENT_FLAG ;__dev_status (md, param);
drivers/md/dm.c +64 -7
103 lines not on this page · raw message
drivers/md/dm.h +6 -0
@@ -66,6 +66,12 @@ int dm_table_alloc_md_mempools(struct dmvoid dm_table_free_md_mempools (struct dm_table *t);struct dm_md_mempools *dm_table_get_md_mempools (struct dm_table *t); +void dm_lock_md_type (struct mapped_device *md); +void dm_unlock_md_type (struct mapped_device *md); +void dm_set_md_type (struct mapped_device *md,struct dm_table * t); +bool dm_unknown_md_type (struct mapped_device *md); +bool dm_md_type_matches_table (struct mapped_device *md,struct dm_table * t); +/* * To check the return value from dm_table_find_target(). */
Re: [PATCH v2 1/2] dm: prevent table type changes after initial table load
Hi Mike,
Quoted 12 lines · Mike Snitzer
Before
table_load()stages an inactive table atomically check if its type conflicts with a previously established device type (md->type).Introduce '
type_lock' inmapped_devicestructure and use it to protectmd->typeaccess.table_load()setsmd->typewithout concern for:
- anothertable_load()racing to set conflictingmd->type.
-do_resume()making a conflicting table live.Allowed transitions:
UNKNOWN_MD_TYPE=>BIO_BASED_MD_TYPEUNKNOWN_MD_TYPE=>REQUEST_BASED_MD_TYPE
Quoted 7 lines
Once a table load occurs the DM device's type is completely immutable.
This prevents a table reload from switching the inactive table directly to a conflicting type (even if the table is explicitly cleared before load).
Signed-off-by Mike Snitzer <[email protected]>
I basically agree with this design.
But I have some comments. Please see below.
By the way, I can't make enough time to review until tomorrow. So the following comments may not be all.
Sorry for the inconvenience.
Quoted 10 lines
Index:
linux-2.6/drivers/md/dm-ioctl.c
===================================================================drivers/md/dm-ioctl.c +2
-0@@ -1176,12 +1176,39 @@ static int table_load(struct dm_ioctl *p goto out; } +/* +* Protect md->type against concurrent table loads.
drivers/md/dm-ioctl.c static int table_load(struct dm_ioctl *p · line 1190
Quoted 10 lines
drivers/md/dm-ioctl.c +10
-0+* Locking strategy: +* + Leverage fact that md's type cannot change after initial table load. +* - Only protect type in table_load() -- not in do_resume(). +* +* + Protect type while working to stage an inactive table: +* - check if table's type conflicts with md->type +* (holding: md->type_lock) +* - stage inactive table (hc->new_map) +* (holding: md->type_lock + _hash_lock) +*/
I think you don't need to hold md->type_lock while you set the table to hc->new_map; If 2 tables of the same type are racing here, we can have either table. If they are different type, the former one will win and the latter one will be rejected.
drivers/md/dm-ioctl.c static int table_load(struct dm_ioctl *p · line 1202
Quoted 12 lines
drivers/md/dm-ioctl.c +12
-0+dm_lock_md_type (md); + +if (dm_unknown_md_type (md)) { +/* initial table load, set md's type based on table's type */ +dm_set_md_type (md, t); + }else if (!dm_md_type_matches_table (md, t)) { +DMWARN ("can't change device type after initial table load." ); +dm_table_destroy (t); +dm_unlock_md_type (md); + r = -EINVAL ; + goto out; + }
So you should be able to unlock md->type_lock here.
Quoted 5 lines
Index:
linux-2.6/drivers/md/dm.c
===================================================================
drivers/md/dm.c EXPORT_SYMBOL_GPL(dm_get_rq_mapinfo); · line 120
Quoted 10 lines
drivers/md/dm.c +7
-0@@ -111,6 +111,15 @@ EXPORT_SYMBOL_GPL(dm_get_rq_mapinfo);#define DMF_QUEUE_IO_TO_THREAD 6/* +* Type for md->type field. +*/ +enum mapped_device_type { +UNKNOWN_MD_TYPE , +BIO_BASED_MD_TYPE , +REQUEST_BASED_MD_TYPE , +};
You can use the defined types in drivers/md/dm.h below instead; /*
* Type of table and mapped_device's mempool
*/
#define DM_TYPE_NONE 0 #define DM_TYPE_BIO_BASED 1 #define DM_TYPE_REQUEST_BASED 2
Using them should make type matching easier than making another definition. E.g. see below;
drivers/md/dm.c int dm_create(int minor, struct mapped_d · line 2193
Quoted 9 lines
drivers/md/dm.c +9
-0+bool dm_md_type_matches_table (struct mapped_device *md,struct dm_table * t) +{ +if (dm_request_based_md_type (md)) +return dm_table_request_based (t); +else if (dm_bio_based_md_type (md)) +return !dm_table_request_based (t); + +return 0 ; +}
You can match type using "md->type == dm_table_get_type(t)" if you use the already defined types above.
Thanks,
Kiyoshi Ueda
Re: [PATCH v2 1/2] dm: prevent table type changes after initial table load
Quoted 21 lines · Kiyoshi Ueda
Hi Mike,
Quoted 18 lines · Mike Snitzer
Before
table_load()stages an inactive table atomically check if its type conflicts with a previously established device type (md->type).Introduce '
type_lock' inmapped_devicestructure and use it to protectmd->typeaccess.table_load()setsmd->typewithout concern for:
- anothertable_load()racing to set conflictingmd->type.
-do_resume()making a conflicting table live.Allowed transitions:
UNKNOWN_MD_TYPE=>BIO_BASED_MD_TYPEUNKNOWN_MD_TYPE=>REQUEST_BASED_MD_TYPEOnce a table load occurs the DM device's type is completely immutable.
This prevents a table reload from switching the inactive table directly to a conflicting type (even if the table is explicitly cleared before load).
Quoted 8 lines
Quoted 1 line
Signed-off-by Mike Snitzer <[email protected]>I basically agree with this design.
But I have some comments. Please see below.By the way, I can't make enough time to review until tomorrow. So the following comments may not be all.
Sorry for the inconvenience.
No problem, thanks again for all your review.
Quoted 17 lines
Quoted 17 lines
Index:
linux-2.6/drivers/md/dm-ioctl.c
===================================================================drivers/md/dm-ioctl.c +9
-0@@ -1176,12 +1176,39 @@ static int table_load(struct dm_ioctl *p goto out; } +/* +* Protect md->type against concurrent table loads. +* Locking strategy: +* + Leverage fact that md's type cannot change after initial table load. +* - Only protect type in table_load() -- not in do_resume(). +* +* + Protect type while working to stage an inactive table: +* - check if table's type conflicts with md->type +* (holding: md->type_lock)
Quoted 8 lines
drivers/md/dm-ioctl.c static int table_load(struct dm_ioctl *p · line 1190
Quoted 3 lines
drivers/md/dm-ioctl.c +3
-0+* - stage inactive table (hc->new_map) +* (holding: md->type_lock + _hash_lock) +*/ I think you don't need to hold
md->type_lockwhile you set the table tohc->new_map; If 2 tables of the same type are racing here, we can have either table. If they are different type, the former one will win and the latter one will be rejected.
Correct. This v2 patchset has significantly reduced the complexity (no longer need to be concerned with md->type transitioning back to UNKNOWN_MD_TYPE). I just missed the fact that the locking could be simplified a bit too.
Quoted 6 lines
Quoted 6 lines
drivers/md/dm-ioctl.c +6
-0+dm_lock_md_type (md); + +if (dm_unknown_md_type (md)) { +/* initial table load, set md's type based on table's type */ +dm_set_md_type (md, t); + }else if (!dm_md_type_matches_table (md, t)) {
Quoted 8 lines
drivers/md/dm-ioctl.c static int table_load(struct dm_ioctl *p · line 1202
Quoted 6 lines
drivers/md/dm-ioctl.c +6
-0+DMWARN ("can't change device type after initial table load." ); +dm_table_destroy (t); +dm_unlock_md_type (md); + r = -EINVAL ; + goto out; + }So you should be able to unlock
md->type_lockhere.
Yes.
Quoted 27 lines
Quoted 5 lines
Index:
linux-2.6/drivers/md/dm.c
===================================================================drivers/md/dm.c EXPORT_SYMBOL_GPL(dm_get_rq_mapinfo); · line 120
Quoted 10 lines
drivers/md/dm.c +7
-0@@ -111,6 +111,15 @@ EXPORT_SYMBOL_GPL(dm_get_rq_mapinfo);#define DMF_QUEUE_IO_TO_THREAD 6/* +* Type for md->type field. +*/ +enum mapped_device_type { +UNKNOWN_MD_TYPE , +BIO_BASED_MD_TYPE , +REQUEST_BASED_MD_TYPE , +};You can use the defined types in
drivers/md/dm.hbelow instead; /*
* Type of table and mapped_device's mempool
*/#define DM_TYPE_NONE 0 #define DM_TYPE_BIO_BASED 1 #define DM_TYPE_REQUEST_BASED 2Using them should make type matching easier than making another definition. E.g. see below;
Quoted 12 lines
drivers/md/dm.c int dm_create(int minor, struct mapped_d · line 2193
Quoted 9 lines
drivers/md/dm.c +9
-0+bool dm_md_type_matches_table (struct mapped_device *md,struct dm_table * t) +{ +if (dm_request_based_md_type (md)) +return dm_table_request_based (t); +else if (dm_bio_based_md_type (md)) +return !dm_table_request_based (t); + +return 0 ; +}You can match type using "
md->type==dm_table_get_type(t)" if you use the already defined types above.
Ah, yes that'll clean things up nicely. No idea how I missed the existing definitions.
Thanks,
Mike
PATCH v2 2/2 dm: only initialize full request_queue for request-based device
In reply to Mike SnitzerAllocate a minimalist request_queue structure initially (needed for both bio and request-based DM). A bio-based DM device no longer defaults to having a fully initialized request_queue (request_fn, elevator, etc). So bio-based DM devices no longer register elevator sysfs attributes ('iosched/' tree or 'scheduler' other than "none").
Initialization of a full request_queue (request_fn, elevator, etc) is deferred until it is known that the DM device is request-based -- at the end of the table load sequence.
Factor DM device's request_queue initialization:
- common to both request-based and bio-based into dm_init_md_queue().
- specific to request-based into dm_init_request_based_queue().
md->type_lock is also used to protect md->queue during table_load(). md->queue is setup without concern for:
- another table_load() racing to setup conflicting queue state.
- do_resume() making a conflicting table live.
--- drivers/md/dm-ioctl.c | 15 +++++- drivers/md/dm.c | 120 +++++++++++++++++++++++++++++++++++++++----------- drivers/md/dm.h | 2 3 files changed, 109 insertions(+), 28 deletions(-)
drivers/md/dm-ioctl.c +13 -2
@@ -1177,14 +1177,16 @@ static int table_load(struct dm_ioctl *p }/* -* Protect md->type against concurrent table loads. +* Protect md->type and md->queue against concurrent table loads. * Locking strategy: * + Leverage fact that md's type cannot change after initial table load. * - Only protect type in table_load() -- not in do_resume(). * -* + Protect type while working to stage an inactive table: +* + Protect type and queue while working to stage an inactive table: * - check if table's type conflicts with md->type * (holding: md->type_lock) +* - setup md->queue based on md->type +* (holding: md->type_lock) * - stage inactive table (hc->new_map) * (holding: md->type_lock + _hash_lock) */
@@ -1201,6 +1203,15 @@ static int table_load(struct dm_ioctl *p goto out; } +/* setup md->queue to reflect md's and table's type (may block) */ + r =dm_setup_md_queue (md); +if (r) { +DMWARN ("unable to setup device queue for this table." ); +dm_table_destroy (t); +dm_unlock_md_type (md); + goto out; + } +/* stage inactive table */ down_write (&_hash_lock); hc =dm_get_mdptr (md);
drivers/md/dm.c +94 -26
147 lines not on this page · raw message
drivers/md/dm.h +2 -0
@@ -72,6 +72,8 @@ void dm_set_md_type(struct mapped_devicebool dm_unknown_md_type (struct mapped_device *md);bool dm_md_type_matches_table (struct mapped_device *md,struct dm_table * t); +int dm_setup_md_queue (struct mapped_device *md); +/* * To check the return value from dm_table_find_target(). */
Re: [PATCH v2 2/2 "v9"] dm: only initialize full request_queue for request-based device
Hi Mike,
Quoted 7 lines · Mike Snitzer
Index:
linux-2.6/drivers/md/dm-ioctl.c
===================================================================drivers/md/dm-ioctl.c +0
-0@@ -1201,6 +1203,15 @@ static int table_load(struct dm_ioctl *p goto out; }
drivers/md/dm-ioctl.c static int table_load(struct dm_ioctl *p · line 1214
Quoted 10 lines
drivers/md/dm-ioctl.c +9
-0+/* setup md->queue to reflect md's and table's type (may block) */ + r =dm_setup_md_queue (md); +if (r) { +DMWARN ("unable to setup device queue for this table." ); +dm_table_destroy (t); +dm_unlock_md_type (md); + goto out; + } +
dm_setup_md_queue() should be put just after dm_set_md_type() of patch#1. Then, you can make sure that dm_setup_md_queue() is called only once and dm_setup_md_queue() should be much simpler.
drivers/md/dm-ioctl.c static int table_load(struct dm_ioctl *p
Quoted 13 lines
drivers/md/dm-ioctl.c +13
-0+/* +* Fully initialize a request-based queue (->elevator, ->request_fn, etc). +*/ +static int dm_init_request_based_queue (struct mapped_device *md) +{ +struct request_queue *q =NULL ; + +/* Avoid re-initializing the queue if already fully initialized */ +if (!md->queue->elevator) { +/* Fully initialize the queue */ + q =blk_init_allocated_queue (md->queue, dm_request_fn,NULL ); +if (!q) +return 0 ;
When blk_init_allocated_queue() fails, the block-layer seems not to guarantee that the queue is still available.
You should create appropriate block-layer interfaces and/or take proper recovery action here.
However, if the failure is not realistic, rather than complicating this patch, you can just put a big comment and WARN_ON() for now, and fix it in a separate patch-set.
Quoted 18 lines
drivers/md/dm-ioctl.c +18
-0+static void dm_clear_request_based_queue (struct mapped_device *md) +{ +if (dm_bio_based_md_queue (md)) +return ;/* queue already bio-based */ + +/* Unregister elevator from sysfs and clear ->request_fn */ +elv_unregister_queue (md->queue); + md->queue->request_fn =NULL ; +} + +/* +* Setup the DM device's queue based on md's type +*/ +int dm_setup_md_queue (struct mapped_device *md) +{ +BUG_ON (!mutex_is_locked (&md->type_lock)); +BUG_ON (dm_unknown_md_type (md)); +
drivers/md/dm-ioctl.c
Quoted 10 lines
drivers/md/dm-ioctl.c +10
-0+if (dm_request_based_md_type (md)) { +if (!dm_init_request_based_queue (md)) { +DMWARN ("Cannot initialize queue for Request-based dm" ); +return -EINVAL ; + } + }else if (dm_bio_based_md_type (md)) +dm_clear_request_based_queue (md); + +return 0 ; +}
These unregister/clear works shouldn't be needed if dm_setup_md_queue() is called only once as I mentioned above.
Thanks,
Kiyoshi Ueda
Re: [PATCH v2 2/2 "v9"] dm: only initialize full request_queue for request-based device
Quoted 11 lines · Kiyoshi Ueda
Hi Mike,
Quoted 8 lines · Mike Snitzer
Index:
linux-2.6/drivers/md/dm-ioctl.c
===================================================================drivers/md/dm-ioctl.c +0
-0@@ -1201,6 +1203,15 @@ static int table_load(struct dm_ioctl *p goto out; }
Quoted 13 lines
drivers/md/dm-ioctl.c static int table_load(struct dm_ioctl *p · line 1214
Quoted 9 lines
drivers/md/dm-ioctl.c +9
-0+/* setup md->queue to reflect md's and table's type (may block) */ + r =dm_setup_md_queue (md); +if (r) { +DMWARN ("unable to setup device queue for this table." ); +dm_table_destroy (t); +dm_unlock_md_type (md); + goto out; + } +
dm_setup_md_queue()should be put just afterdm_set_md_type()of patch#1. Then, you can make sure thatdm_setup_md_queue()is called only once anddm_setup_md_queue()should be much simpler.
Good idea.
Quoted 8 lines
Quoted 8 lines
drivers/md/dm-ioctl.c +8
-0+/* +* Fully initialize a request-based queue (->elevator, ->request_fn, etc). +*/ +static int dm_init_request_based_queue (struct mapped_device *md) +{ +struct request_queue *q =NULL ; + +/* Avoid re-initializing the queue if already fully initialized */
Quoted 8 lines
drivers/md/dm-ioctl.c
Quoted 5 lines
drivers/md/dm-ioctl.c +5
-0+if (!md->queue->elevator) { +/* Fully initialize the queue */ + q =blk_init_allocated_queue (md->queue, dm_request_fn,NULL ); +if (!q) +return 0 ;When
blk_init_allocated_queue()fails, the block-layer seems not to guarantee that the queue is still available.
Ouch, yes this portion of blk_init_allocated_queue_node() is certainly problematic:
if (blk_init_free_list(q)) {
kmem_cache_free(blk_requestq_cachep, q);
return NULL;
}Cc'ing Jens as I think it would be advantageous for us to push the above kmem_cache_free() into the callers where it really makes sense, e.g.:blk_init_queue_node().
So on blk_init_allocated_queue_node() failure blk_init_queue_node() will take care to cleanup the queue that it assumes it is managing completely.
My patch (linux-2.6-block.git's commit: 01effb0) that split out blk_init_allocated_queue_node() from blk_init_queue_node() opened up this issue. I'm fairly confident we'll get it fixed by the time 2.6.35 ships.
Quoted 5 lines
You should create appropriate block-layer interfaces and/or take proper recovery action here.
However, if the failure is not realistic, rather than complicating this patch, you can just put a big comment andWARN_ON()for now, and fix it in a separate patch-set.
I'll hold off on the WARN_ON(). I think we need the request_queue to remain allocated even after blk_init_allocated_queue_node() failure.
Quoted 23 lines
Quoted 23 lines
drivers/md/dm-ioctl.c +23
-0+static void dm_clear_request_based_queue (struct mapped_device *md) +{ +if (dm_bio_based_md_queue (md)) +return ;/* queue already bio-based */ + +/* Unregister elevator from sysfs and clear ->request_fn */ +elv_unregister_queue (md->queue); + md->queue->request_fn =NULL ; +} + +/* +* Setup the DM device's queue based on md's type +*/ +int dm_setup_md_queue (struct mapped_device *md) +{ +BUG_ON (!mutex_is_locked (&md->type_lock)); +BUG_ON (dm_unknown_md_type (md)); + +if (dm_request_based_md_type (md)) { +if (!dm_init_request_based_queue (md)) { +DMWARN ("Cannot initialize queue for Request-based dm" ); +return -EINVAL ; + }
Quoted 8 lines
drivers/md/dm-ioctl.c
Quoted 5 lines
drivers/md/dm-ioctl.c +5
-0+ }else if (dm_bio_based_md_type (md)) +dm_clear_request_based_queue (md); + +return 0 ; +}These unregister/clear works shouldn't be needed if
dm_setup_md_queue()is called only once as I mentioned above.
Indeed. I'll get v3 of the patchset out with appropriate edits.
Thanks,
Mike
PATCH 1/1 block: avoid unconditionally freeing previously allocated request_queue
Quoted 32 lines · Mike Snitzer
Quoted 8 lines · Kiyoshi Ueda
Quoted 8 lines
(quoted diff) +8
-0+/* + * Fully initialize a request-based queue (->elevator, ->request_fn, etc). + */ +static int dm_init_request_based_queue(struct mapped_device *md) +{ + struct request_queue *q = NULL; + + /* Avoid re-initializing the queue if already fully initialized */Quoted 8 lines
Review
Quoted 5 lines
(quoted diff) +5
-0+ if (!md->queue->elevator) { + /* Fully initialize the queue */ + q = blk_init_allocated_queue(md->queue, dm_request_fn, NULL); + if (!q) + return 0;When
blk_init_allocated_queue()fails, the block-layer seems not to guarantee that the queue is still available.Ouch, yes this portion of
blk_init_allocated_queue_node()is certainly problematic:if (blk_init_free_list(q)) { kmem_cache_free(blk_requestq_cachep, q); return NULL; }Cc'ing Jens as I think it would be advantageous for us to push the above
kmem_cache_free()into the callers where it really makes sense, e.g.:blk_init_queue_node().
Quoted 8 lines
So on
blk_init_allocated_queue_node()failureblk_init_queue_node()will take care to cleanup the queue that it assumes it is managing completely.My patch (linux-2.6-block.git's commit: 01effb0) that split out
blk_init_allocated_queue_node()fromblk_init_queue_node()opened up this issue. I'm fairly confident we'll get it fixed by the time 2.6.35 ships.
Jens,
How about something like the following?
block: avoid unconditionally freeing previously allocated request_queue
On blk_init_allocated_queue_node failure, only free request_queue if it is wasn't previously allocated outside the block layer (e.g. blk_init_queue_node was blk_init_allocated_queue_node caller).
This addresses a regression introduced by the following commit:
01effb0 block: allow initialization of previously allocated request_queue
Otherwise the request_queue may be free'd out from underneath a caller that is managing the request_queue directly (e.g. caller uses blk_alloc_queue + blk_init_allocated_queue_node).
--- block/blk-core.c | 31 ++++++++++++++++++++++++++----- 1 files changed, 26 insertions(+), 5 deletions(-)
block/blk-core.c +26 -5
@@ -528,6 +528,24 @@ struct request_queue *blk_alloc_queue_node(gfp_t gfp_mask, int node_id) }EXPORT_SYMBOL (blk_alloc_queue_node); +static void blk_free_partial_queue (struct request_queue *q) +{ +/* Free q if blk_init_queue failed early enough. */ +int free_request_queue =0 ; +struct request_list *rl; + +if (!q) +return ; + +/* Was blk_init_free_list the cause for failure? */ + rl = &q->rq; +if (!rl->rq_pool) + free_request_queue =1 ; + +if (free_request_queue) +kmem_cache_free (blk_requestq_cachep, q); +} +/** * blk_init_queue - prepare a request queue for use with a block device * @rfn: The function to be called to process requests that have been
@@ -570,9 +588,14 @@ EXPORT_SYMBOL(blk_init_queue);struct request_queue *blk_init_queue_node (request_fn_proc *rfn,spinlock_t *lock,int node_id) { -struct request_queue *q =blk_alloc_queue_node (GFP_KERNEL , node_id); +struct request_queue *uninit_q, *q; + + uninit_q =blk_alloc_queue_node (GFP_KERNEL , node_id); + q =blk_init_allocated_queue_node (uninit_q, rfn, lock, node_id); +if (!q) +blk_free_partial_queue (uninit_q); -return blk_init_allocated_queue_node (q, rfn, lock, node_id); +return q; }EXPORT_SYMBOL (blk_init_queue_node);
@@ -592,10 +615,8 @@ blk_init_allocated_queue_node(struct request_queue *q, request_fn_proc *rfn,return NULL ; q->node = node_id; -if (blk_init_free_list (q)) { -kmem_cache_free (blk_requestq_cachep, q); +if (blk_init_free_list (q))return NULL ; - } q->request_fn = rfn; q->prep_rq_fn =NULL ;
[PATCH 2/1] block: make blk_init_free_list and elevator_init idempotent
blk_init_allocated_queue_node may fail and the caller _could_ retry. Accommodate the unlikely event that blk_init_allocated_queue_node is called on an already initialized (possibly partially) request_queue.
--- block/blk-core.c | 3 +++ block/elevator.c | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-)
block/blk-core.c +3 -0
@@ -467,6 +467,9 @@ static int blk_init_free_list(struct request_queue *q) {struct request_list *rl = &q->rq; +if (unlikely (rl->rq_pool)) +return 0 ; + rl->count[BLK_RW_SYNC ] = rl->count[BLK_RW_ASYNC ] =0 ; rl->starved[BLK_RW_SYNC ] = rl->starved[BLK_RW_ASYNC ] =0 ; rl->elvpriv =0 ;
block/elevator.c +4 -2
@@ -242,9 +242,11 @@ int elevator_init(struct request_queue *q, char *name) {struct elevator_type *e =NULL ;struct elevator_queue *eq; -int ret =0 ;void *data; +if (unlikely (q->elevator)) +return 0 ; +INIT_LIST_HEAD (&q->queue_head); q->last_merge =NULL ; q->end_sector =0 ;
@@ -284,7 +286,7 @@ int elevator_init(struct request_queue *q, char *name) }elevator_attach (q, eq, data); -return ret; +return 0 ; }EXPORT_SYMBOL (elevator_init);
Re: [PATCH] block: avoid unconditionally freeing previously allocated request_queue
In reply to Mike SnitzerHi Mike,
Quoted 19 lines · Mike Snitzer
Quoted 18 lines · Mike Snitzer
Quoted 16 lines · Kiyoshi Ueda
Review
Quoted 13 lines
(quoted diff) +13
-0+/* + * Fully initialize a request-based queue (->elevator, ->request_fn, etc). + */ +static int dm_init_request_based_queue(struct mapped_device *md) +{ + struct request_queue *q = NULL; + + /* Avoid re-initializing the queue if already fully initialized */ + if (!md->queue->elevator) { + /* Fully initialize the queue */ + q = blk_init_allocated_queue(md->queue, dm_request_fn, NULL); + if (!q) + return 0;When
blk_init_allocated_queue()fails, the block-layer seems not to guarantee that the queue is still available.
Quoted 7 lines
Quoted 7 lines
Ouch, yes this portion of
blk_init_allocated_queue_node()is certainly problematic:if (blk_init_free_list(q)) { kmem_cache_free(blk_requestq_cachep, q); return NULL; }
Not only that. The blk_put_queue() in blk_init_allocated_queue_node() will also free the queue:
if (!elevator_init(q, NULL)) {
blk_queue_congestion_threshold(q);
return q;
}
blk_put_queue(q);
return NULL;Thanks,
Kiyoshi Ueda
Re: block: avoid unconditionally freeing previously allocated request_queue
Quoted 33 lines · Kiyoshi Ueda
Hi Mike,
Quoted 19 lines · Mike Snitzer
Quoted 18 lines · Mike Snitzer
Quoted 16 lines · Kiyoshi Ueda
Review
Quoted 13 lines
(quoted diff) +13
-0+/* + * Fully initialize a request-based queue (->elevator, ->request_fn, etc). + */ +static int dm_init_request_based_queue(struct mapped_device *md) +{ + struct request_queue *q = NULL; + + /* Avoid re-initializing the queue if already fully initialized */ + if (!md->queue->elevator) { + /* Fully initialize the queue */ + q = blk_init_allocated_queue(md->queue, dm_request_fn, NULL); + if (!q) + return 0;When
blk_init_allocated_queue()fails, the block-layer seems not to guarantee that the queue is still available.Quoted 7 lines
Quoted 7 lines
Ouch, yes this portion of
blk_init_allocated_queue_node()is certainly problematic:if (blk_init_free_list(q)) { kmem_cache_free(blk_requestq_cachep, q); return NULL; }Not only that. The
blk_put_queue()inblk_init_allocated_queue_node()will also free the queue:
Quoted 7 lines
if (!elevator_init(q, NULL)) {
blk_queue_congestion_threshold(q);
return q;
}
blk_put_queue(q);
return NULL;OK, I'll post v2 that addresses this and we'll see what Jens says.
Mike
PATCH v2 1/1 block: avoid unconditionally freeing previously allocated request_queue
In reply to Mike SnitzerOn blk_init_allocated_queue_node failure, only free request_queue if it is wasn't previously allocated outside the block layer (e.g. blk_init_queue_node was blk_init_allocated_queue_node caller).
This addresses an interface bug introduced by the following commit:
01effb0 block: allow initialization of previously allocated request_queue
Otherwise the request_queue may be free'd out from underneath a caller that is managing the request_queue directly (e.g. caller uses blk_alloc_queue + blk_init_allocated_queue_node).
--- block/blk-core.c | 33 +++++++++++++++++++++++++++------ 1 files changed, 27 insertions(+), 6 deletions(-)
block/blk-core.c +27 -6
@@ -528,6 +528,25 @@ struct request_queue *blk_alloc_queue_node(gfp_t gfp_mask, int node_id) }EXPORT_SYMBOL (blk_alloc_queue_node); +static void blk_free_partial_queue (struct request_queue *q) +{ +struct request_list *rl; + +if (!q) +return ; + +/* Was blk_init_free_list the cause for failure? */ + rl = &q->rq; +if (!rl->rq_pool) { +kmem_cache_free (blk_requestq_cachep, q); +return ; + } + +/* Or was elevator_init? */ +if (!q->elevator) +blk_put_queue (q); +} +/** * blk_init_queue - prepare a request queue for use with a block device * @rfn: The function to be called to process requests that have been
@@ -570,9 +589,14 @@ EXPORT_SYMBOL(blk_init_queue);struct request_queue *blk_init_queue_node (request_fn_proc *rfn,spinlock_t *lock,int node_id) { -struct request_queue *q =blk_alloc_queue_node (GFP_KERNEL , node_id); +struct request_queue *uninit_q, *q; + + uninit_q =blk_alloc_queue_node (GFP_KERNEL , node_id); + q =blk_init_allocated_queue_node (uninit_q, rfn, lock, node_id); +if (!q) +blk_free_partial_queue (uninit_q); -return blk_init_allocated_queue_node (q, rfn, lock, node_id); +return q; }EXPORT_SYMBOL (blk_init_queue_node);
@@ -592,10 +616,8 @@ blk_init_allocated_queue_node(struct request_queue *q, request_fn_proc *rfn,return NULL ; q->node = node_id; -if (blk_init_free_list (q)) { -kmem_cache_free (blk_requestq_cachep, q); +if (blk_init_free_list (q))return NULL ; - } q->request_fn = rfn; q->prep_rq_fn =NULL ;
@@ -618,7 +640,6 @@ blk_init_allocated_queue_node(struct request_queue *q, request_fn_proc *rfn,return q; } -blk_put_queue (q);return NULL ; }EXPORT_SYMBOL (blk_init_allocated_queue_node);
PATCH v3 1/1 block: avoid unconditionally freeing previously allocated request_queue
On blk_init_allocated_queue_node failure, only free the request_queue if it is wasn't previously allocated outside the block layer (e.g. blk_init_queue_node was blk_init_allocated_queue_node caller).
This addresses an interface bug introduced by the following commit:
01effb0 block: allow initialization of previously allocated request_queue
Otherwise the request_queue may be free'd out from underneath a caller that is managing the request_queue directly (e.g. caller uses blk_alloc_queue + blk_init_allocated_queue_node).
--- block/blk-core.c | 14 ++++++++------ 1 files changed, 8 insertions(+), 6 deletions(-)
v3: leverage fact that blk_cleanup_queue will properly free all memory
associated with a request_queue (e.g.: q->rq_pool and q->elevator)
block/blk-core.c +8 -6
@@ -570,9 +570,14 @@ EXPORT_SYMBOL(blk_init_queue);struct request_queue *blk_init_queue_node (request_fn_proc *rfn,spinlock_t *lock,int node_id) { -struct request_queue *q =blk_alloc_queue_node (GFP_KERNEL , node_id); +struct request_queue *uninit_q, *q; -return blk_init_allocated_queue_node (q, rfn, lock, node_id); + uninit_q =blk_alloc_queue_node (GFP_KERNEL , node_id); + q =blk_init_allocated_queue_node (uninit_q, rfn, lock, node_id); +if (!q) +blk_cleanup_queue (uninit_q); + +return q; }EXPORT_SYMBOL (blk_init_queue_node);
@@ -592,10 +597,8 @@ blk_init_allocated_queue_node(struct request_queue *q, request_fn_proc *rfn,return NULL ; q->node = node_id; -if (blk_init_free_list (q)) { -kmem_cache_free (blk_requestq_cachep, q); +if (blk_init_free_list (q))return NULL ; - } q->request_fn = rfn; q->prep_rq_fn =NULL ;
@@ -618,7 +621,6 @@ blk_init_allocated_queue_node(struct request_queue *q, request_fn_proc *rfn,return q; } -blk_put_queue (q);return NULL ; }EXPORT_SYMBOL (blk_init_allocated_queue_node);
PATCH v4 1/1 block: avoid unconditionally freeing previously allocated request_queue
block: avoid unconditionally freeing previously allocated request_queue
On blk_init_allocated_queue_node failure, only free the request_queue if it is wasn't previously allocated outside the block layer (e.g. blk_init_queue_node was blk_init_allocated_queue_node caller).
This addresses an interface bug introduced by the following commit:
01effb0 block: allow initialization of previously allocated request_queue
Otherwise the request_queue may be free'd out from underneath a caller that is managing the request_queue directly (e.g. caller uses blk_alloc_queue + blk_init_allocated_queue_node).
--- block/blk-core.c | 17 +++++++++++------ 1 files changed, 11 insertions(+), 6 deletions(-)
v4: eliminate potential for NULL pointer in call to blk_cleanup_queue
v3: leverage fact that blk_cleanup_queue will properly free all memory
associated with a request_queue (e.g.: q->rq_pool and q->elevator)
block/blk-core.c +11 -6
@@ -570,9 +570,17 @@ EXPORT_SYMBOL(blk_init_queue);struct request_queue *blk_init_queue_node (request_fn_proc *rfn,spinlock_t *lock,int node_id) { -struct request_queue *q =blk_alloc_queue_node (GFP_KERNEL , node_id); +struct request_queue *uninit_q, *q; -return blk_init_allocated_queue_node (q, rfn, lock, node_id); + uninit_q =blk_alloc_queue_node (GFP_KERNEL , node_id); +if (!uninit_q) +return NULL ; + + q =blk_init_allocated_queue_node (uninit_q, rfn, lock, node_id); +if (!q) +blk_cleanup_queue (uninit_q); + +return q; }EXPORT_SYMBOL (blk_init_queue_node);
@@ -592,10 +600,8 @@ blk_init_allocated_queue_node(struct request_queue *q, request_fn_proc *rfn,return NULL ; q->node = node_id; -if (blk_init_free_list (q)) { -kmem_cache_free (blk_requestq_cachep, q); +if (blk_init_free_list (q))return NULL ; - } q->request_fn = rfn; q->prep_rq_fn =NULL ;
@@ -618,7 +624,6 @@ blk_init_allocated_queue_node(struct request_queue *q, request_fn_proc *rfn,return q; } -blk_put_queue (q);return NULL ; }EXPORT_SYMBOL (blk_init_allocated_queue_node);
Re: [PATCH v4] block: avoid unconditionally freeing previously allocated request_queue
Quoted 6 lines · Mike Snitzer
block: avoid unconditionally freeing previously allocated
request_queueOn
blk_init_allocated_queue_nodefailure, only free therequest_queueif it is wasn't previously allocated outside the block layer (e.g.blk_init_queue_nodewasblk_init_allocated_queue_nodecaller).
Quoted 7 lines
This addresses an interface bug introduced by the following commit:
01effb0 block: allow initialization of previously allocatedrequest_queueOtherwise the
request_queuemay be free'd out from underneath a caller that is managing therequest_queuedirectly (e.g. caller usesblk_alloc_queue+blk_init_allocated_queue_node).
Thanks Mike, this looks a lot better. I have applied this one and 2/2 of the original posting.
Signature · 1 line
Jens Axboe