diff options
| author | Liam R. Howlett (Oracle) <liam@infradead.org> | 2026-08-21 15:26:22 -0400 |
|---|---|---|
| committer | Andrew Morton <akpm@linux-foundation.org> | 2026-08-24 18:43:31 -0700 |
| commit | f1681380b5f928e147954d87d875f57a25df189c (patch) | |
| tree | ba3ceea7510485d22161eeef8513569c8d158a2f /lib | |
| parent | 3f06ef1f34a7ee3b77ee03c13a0c50db8fd3c536 (diff) | |
| download | linux-next-f1681380b5f928e147954d87d875f57a25df189c.tar.gz linux-next-f1681380b5f928e147954d87d875f57a25df189c.zip | |
maple_tree: avoid mas_erase() and mtree_erase() failures
Failures to remove entries using the two APIs to erase the entries may
result in allocation failures. The failures may go unnoticed and an
unexpected entry may remain. Instead, fall back to retrying with
GFP_KERNEL | __GFP_NOFAIL so that the entry will be removed.
Link: https://lore.kernel.org/20260821192627.4085470-15-liam@infradead.org
Signed-off-by: Liam R. Howlett (Oracle) <liam@infradead.org>
Cc: Rik van Riel <riel@surriel.com>
Cc: Jason Gunthorpe <jgg@ziepe.ca>
Cc: Boqun Feng <boqun.feng@gmail.com>
Cc: Breno Leitao <leitao@debian.org>
Cc: Chris Mason <clm@meta.com>
Cc: Chuck Lever <cel@kernel.org>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Joe Perches <joe@perches.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Waiman Long <longman@redhat.com>
Cc: Will Deacon <will@kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/maple_tree.c | 68 |
1 files changed, 50 insertions, 18 deletions
diff --git a/lib/maple_tree.c b/lib/maple_tree.c index 440863bdea26..d47d4304f781 100644 --- a/lib/maple_tree.c +++ b/lib/maple_tree.c @@ -5658,9 +5658,10 @@ EXPORT_SYMBOL_GPL(mas_find_range_rev); * Searches for @mas->index, sets @mas->index and @mas->last to the range and * erases that range. * - * Note that erase requires allocations and will use GFP_KERNEL to do so if - * necessary. If the allocation fails, the internal lock will be dropped to - * retry. + * Note that erase requires allocations and will use GFP_KERNEL | __GFP_NOFAIL + * to do so if necessary. If the allocation fails, the internal lock will be + * dropped to retry. An externally locked tree must be protected by a lock that + * allows blocking for this API. * * Return: the entry that was erased or %NULL, @mas->index and @mas->last are updated. */ @@ -5672,8 +5673,8 @@ void *mas_erase(struct ma_state *mas) /* * In low memory situations, the allocation is retried with the gfp flag - * GFP_KERNEL. The internal spinlock is dropped in mas_nomem(), however - * the external lock is not dropped. + * GFP_KERNEL | __GFP_NOFAIL. The internal spinlock is dropped in + * mas_nomem_nofail(), however the external lock is not dropped. */ if (mt_external_lock(mas->tree)) might_alloc(GFP_KERNEL); @@ -5689,16 +5690,8 @@ write_retry: /* Must reset to ensure spanning writes of last slot are detected */ mas_reset(mas); mas_wr_preallocate(&wr_mas, NULL); - if (mas_nomem(mas, GFP_KERNEL)) { - /* in case the range of entry changed when unlocked */ - mas->index = mas->last = index; + if (mas_nomem_nofail(mas, index, index)) goto write_retry; - } - - if (mas_is_err(mas)) { - entry = NULL; - goto out; - } mas_wr_store_entry(&wr_mas); out: @@ -5721,6 +5714,10 @@ bool mas_nomem(struct ma_state *mas, gfp_t gfp) if (likely(mas->node != MA_ERROR(-ENOMEM))) return false; + /* Allocations can fail, don't do this. */ + WARN_ON_ONCE(!gfpflags_allow_blocking(gfp) && + mt_external_lock(mas->tree)); + if (gfpflags_allow_blocking(gfp) && !mt_external_lock(mas->tree)) { mtree_unlock(mas->tree); mas_alloc_nodes(mas, gfp); @@ -5731,7 +5728,9 @@ bool mas_nomem(struct ma_state *mas, gfp_t gfp) /* * Return false on zero forward progress. Partial allocations are kept - * so the retry path will attempt to get the rest. + * so the retry path will attempt to get the rest. The failure should + * not happen as we try our best to reclaim. The user would need an + * external lock with a non-blocking gfp in a low memory situation. */ if (!mas->sheaf && !mas->alloc) return false; @@ -5740,6 +5739,39 @@ bool mas_nomem(struct ma_state *mas, gfp_t gfp) return true; } +/** + * mas_nomem_nofail() - Retry allocations with __GFP_NOFAIL, if the maple state + * has stored the -ENOMEM error. + * @mas: The maple state + * @index: The start of the range for the @mas reset + * @last: The end of the range for the @mas reset + * + * Return: false if @mas isn't in an -ENOMEM state. True if the allocation + * happens, the state is reset. The internal lock will be dropped and external + * locks must allow blocking. + */ +bool mas_nomem_nofail(struct ma_state *mas, unsigned long index, + unsigned long last) + __must_hold(mas->tree->ma_lock) +{ + gfp_t gfp; + + if (likely(mas->node != MA_ERROR(-ENOMEM))) + return false; + + gfp = GFP_KERNEL | __GFP_NOFAIL; + if (!mt_external_lock(mas->tree)) { + mtree_unlock(mas->tree); + mas_alloc_nodes(mas, gfp); + mtree_lock(mas->tree); + } else { + mas_alloc_nodes(mas, gfp); + } + + mas_set_range(mas, index, last); + return true; +} + void __init maple_tree_init(void) { struct kmem_cache_args args = { @@ -6026,9 +6058,9 @@ EXPORT_SYMBOL(mtree_alloc_rrange); * Erasing is the same as a walk to an entry then a store of a NULL to that * ENTIRE range. In fact, it is implemented as such using the advanced API. * - * Note that erase requires allocations and will use GFP_KERNEL to do so if - * necessary. If the allocation fails, the internal lock will be dropped to - * retry. + * Note that erase requires allocations and will use GFP_KERNEL | __GFP_NOFAIL + * to do so if necessary. If the allocation fails, the internal lock will be + * dropped to retry. * * Return: The entry stored at the @index or %NULL */ |
