mm: avoid livelock on !__GFP_FS allocations
Subsystems (6)
PATCH 1/1 mm: avoid livelock on !__GFP_FS allocations
This patch seems to have gotten lost in the cracks and the discussion on alternatives that started here https://lkml.org/lkml/2011/10/25/24
petered out without any alternative patches being posted. Lacking a viable alternative patch, I'm reposting this patch because AFAIK, this bug still exists.
Colin Cross reported;
Under the following conditions, __alloc_pages_slowpath can loop forever:
gfp_mask & __GFP_WAIT is true
gfp_mask & __GFP_FS is false
reclaim and compaction make no progress
order <= PAGE_ALLOC_COSTLY_ORDER
These conditions happen very often during suspend and resume, when pm_restrict_gfp_mask() effectively converts all GFP_KERNEL allocations into __GFP_WAIT.
The oom killer is not run because gfp_mask & __GFP_FS is false, but should_alloc_retry will always return true when order is less than PAGE_ALLOC_COSTLY_ORDER.
In his fix, he avoided retrying the allocation if reclaim made no progress and __GFP_FS was not set. The problem is that this would result in GFP_NOIO allocations failing that previously succeeded which would be very unfortunate.
The big difference between GFP_NOIO and suspend converting GFP_KERNEL to behave like GFP_NOIO is that normally flushers will be cleaning pages and kswapd reclaims pages allowing GFP_NOIO to succeed after a short delay. The same does not necessarily apply during suspend as the storage device may be suspended. Hence, this patch special cases the suspend case to fail the page allocation if reclaim cannot make progress. This might cause suspend to abort but that is better than a livelock.
--- mm/page_alloc.c | 22 ++++++++++++++++++++++ 1 files changed, 22 insertions(+), 0 deletions(-)
mm/page_alloc.c +22 -0
@@ -127,6 +127,20 @@ void pm_restrict_gfp_mask(void) saved_gfp_mask = gfp_allowed_mask; gfp_allowed_mask &= ~GFP_IOFS; } + +static bool pm_suspending (void ) +{ +if ((gfp_allowed_mask &GFP_IOFS ) ==GFP_IOFS ) +return false; +return true; +} + +#else + +static bool pm_suspending (void ) +{ +return false; +}#endif /* CONFIG_PM_SLEEP */ #ifdef CONFIG_HUGETLB_PAGE_SIZE_VARIABLE
@@ -2214,6 +2228,14 @@ rebalance: goto restart; } + +/* +* Suspend converts GFP_KERNEL to __GFP_WAIT which can +* prevent reclaim making forward progress without +* invoking OOM. Bail if we are suspending +*/ +if (pm_suspending ()) + goto nopage; }/* Check if we should retry the allocation */
Quoted 4 lines · Mel Gorman
In his fix, he avoided retrying the allocation if reclaim made no progress and
__GFP_FSwas not set. The problem is that this would result inGFP_NOIOallocations failing that previously succeeded which would be very unfortunate.
GFP_NOFS are made by filesystems/buffers to avoid locking up on fs/vfs locking. Those also should be able to handle failure gracefully but userland is more likely to get a -ENOMEM from these (for example during direct-io) if those fs allocs fails. So clearly it sounds risky to apply the modification quoted above and risk having any GFP_NOFS fail. Said that I'm afraid we're not deadlock safe with current code that cannot fail but there's no easy solution and no way to fix it in the short term, and it's only a theoretical concern.
For !__GFP_FS allocations, __GFP_NOFAIL is the default for order <= PAGE_ALLOC_COSTLY_ORDER and __GFP_NORETRY is the default for order > PAGE_ALLOC_COSTLY_ORDER. This inconsistency is not so clean in my view. Also for GFP_KERNEL/USER/__GFP_FS regular allocations the __GFP_NOFAIL looks more like a __GFP_MAY_OOM. But if we fix that and we drop __GFP_NORETRY, and we set __GFP_NOFAIL within the GFP_NOFS/NOIO #defines (to remove the magic PAGE_ALLOC_COSTLY_ORDER check in should_alloc_retry) we may loop forever if somebody allocates several mbytes of huge contiguous RAM with GFP_NOIO. So at least there's a practical explanation for the current code.
Patch looks good to me (and safer) even if I don't like keeping infinite loops from a purely theoretical standpoint.
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Fight unfair telecom internet charges in Canada: sign http://stopthemeter.ca/
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Quoted 10 lines · Andrea Arcangeli
Quoted 4 lines · Mel Gorman
In his fix, he avoided retrying the allocation if reclaim made no progress and
__GFP_FSwas not set. The problem is that this would result inGFP_NOIOallocations failing that previously succeeded which would be very unfortunate.
GFP_NOFSare made by filesystems/buffers to avoid locking up on fs/vfs locking. Those also should be able to handle failure gracefully but userland is more likely to get a-ENOMEMfrom these (for example during direct-io) if those fs allocs fails.
I was also vaguely recalling Roland's talk at day 2 of kernel summit (reported at http://lwn.net/Articles/464500/) where he talked about error handling. One point he made was that some filesystems ran into problems in the event of memory allocation failure. I didn't audit if the block layer handles it better but one way or the other I did not want to throw the the block or filesystem layers curve balls.
Quoted 5 lines
So clearly it sounds risky
to apply the modification quoted above and risk having anyGFP_NOFSfail. Said that I'm afraid we're not deadlock safe with current code that cannot fail but there's no easy solution and no way to fix it in the short term, and it's only a theoretical concern.
It's still a valid concern. The expectation is that we are protected from deadlocks a combination of mempools and the watermarks forcing processes to stall in direct reclaim leaving a cushion of pages for reclaim using PF_MEMALLOC to always make forward progress. Patches that break how watermarks work tend to lead to deadlock.
Quoted 4 lines
For !__GFP_FS allocations,
__GFP_NOFAILis the default for order <=PAGE_ALLOC_COSTLY_ORDERand__GFP_NORETRYis the default for order >PAGE_ALLOC_COSTLY_ORDER. This inconsistency is not so clean in my view.
Is your concern that the behaviour of the allocator changes quite significantly for orders < PAGE_ALLOC_COSTLY_ORDER?
I agree with you that it would be nicer if there was a gradual scaling back of how much work the allocator did that depended on order. To date there has not been much pressure or motivation to implement it.
Quoted 8 lines
Also for GFP_KERNEL/USER/__GFP_FS regular allocations the
__GFP_NOFAILlooks more like a__GFP_MAY_OOM. But if we fix that and we drop__GFP_NORETRY, and we set__GFP_NOFAILwithin the GFP_NOFS/NOIO #defines (to remove the magicPAGE_ALLOC_COSTLY_ORDERcheck inshould_alloc_retry) we may loop forever if somebody allocates several mbytes of huge contiguous RAM withGFP_NOIO. So at least there's a practical explanation for the current code.
Yep.
Quoted 4 lines
Patch looks good to me (and safer) even if I don't like keeping infinite loops from a purely theoretical standpoint.
From a more practical point of view, I am generally more concerned
with abnormally large stalls from within the page allocator which is what patches like "Do not stall in synchronous compaction for THP allocations" address.
Signature · 2 lines
Mel Gorman SUSE Labs
Andrew Morton
maintainerQuoted 27 lines · On Mon, 14 Nov 2011 14:04:21 +0000 Mel Gorman <[email protected]> wrote:
This patch seems to have gotten lost in the cracks and the discussion on alternatives that started here https://lkml.org/lkml/2011/10/25/24
petered out without any alternative patches being posted. Lacking a viable alternative patch, I'm reposting this patch because AFAIK, this bug still exists.Colin Cross reported;
Under the following conditions,
__alloc_pages_slowpathcan loop forever:
gfp_mask&__GFP_WAITis true
gfp_mask&__GFP_FSis false
reclaim and compaction make no progress
order <=PAGE_ALLOC_COSTLY_ORDERThese conditions happen very often during suspend and resume, when
pm_restrict_gfp_mask()effectively converts allGFP_KERNELallocations into__GFP_WAIT.The oom killer is not run because
gfp_mask&__GFP_FSis false, butshould_alloc_retrywill always return true when order is less thanPAGE_ALLOC_COSTLY_ORDER.In his fix, he avoided retrying the allocation if reclaim made no progress and
__GFP_FSwas not set. The problem is that this would result inGFP_NOIOallocations failing that previously succeeded which would be very unfortunate.
Quoted 8 lines
The big difference between
GFP_NOIOand suspend convertingGFP_KERNELto behave likeGFP_NOIOis that normally flushers will be cleaning pages and kswapd reclaims pages allowingGFP_NOIOto succeed after a short delay. The same does not necessarily apply during suspend as the storage device may be suspended. Hence, this patch special cases the suspend case to fail the page allocation if reclaim cannot make progress. This might cause suspend to abort but that is better than a livelock.
Fair enough.
Quoted 6 lines
mm/page_alloc.c +0
-0
mm/page_alloc.c void pm_restrict_gfp_mask(void) · line 136
Quoted 10 lines
mm/page_alloc.c +7
-0@@ -127,6 +127,20 @@ void pm_restrict_gfp_mask(void) saved_gfp_mask = gfp_allowed_mask; gfp_allowed_mask &= ~GFP_IOFS ; } + +static bool pm_suspending (void ) +{ +if ((gfp_allowed_mask &GFP_IOFS ) ==GFP_IOFS ) +return false; +return true; +}
This doesn't seem a terribly reliable way of detecting that PM has disabled the storage devices (which is what we really want to know
here: kswapd got crippled).
I guess it's safe for now, because PM is the only caller who alters gfp_allowed_mask (I assume). But an explicit storage_is_unavaliable global which is set and cleared at exactly the correct time is clearer, more direct and future-safer, no?
Quoted 11 lines
mm/page_alloc.c +6
-0+#else + +static bool pm_suspending (void ) +{ +return false; +}#endif /* CONFIG_PM_SLEEP */ #ifdef CONFIG_HUGETLB_PAGE_SIZE_VARIABLE @@ -2214,6 +2228,14 @@ rebalance:
mm/page_alloc.c rebalance: · line 2238
Quoted 10 lines
mm/page_alloc.c +8
-0goto restart; } + +/* +* Suspend converts GFP_KERNEL to __GFP_WAIT which can +* prevent reclaim making forward progress without +* invoking OOM. Bail if we are suspending +*/ +if (pm_suspending ()) + goto nopage;
The comment doesn't tell the whole story: it's important that kswapd writeout was disabled?
mm/page_alloc.c renamed from mm/page_alloc.c~mm-avoid-livelock-on-__gfp_fs-allocations-fix +4 -3
@@ -2263,9 +2263,10 @@ rebalance: }/* -* Suspend converts GFP_KERNEL to __GFP_WAIT which can -* prevent reclaim making forward progress without -* invoking OOM. Bail if we are suspending +* Suspend converts GFP_KERNEL to __GFP_WAIT which can prevent +* reclaim making forward progress without invoking OOM. +* Suspend also disables storage devices so kswapd cannot save +* us. Bail if we are suspending. */ if (pm_suspending ()) goto nopage;
_
Quoted 12 lines · Andrew Morton
Quoted 12 lines
<SNIP>
mm/page_alloc.c +3
-0@@ -127,6 +127,20 @@ void pm_restrict_gfp_mask(void) saved_gfp_mask = gfp_allowed_mask; gfp_allowed_mask &= ~GFP_IOFS ; } + +static bool pm_suspending (void ) +{
Quoted 9 lines
mm/page_alloc.c void pm_restrict_gfp_mask(void) · line 136
Quoted 4 lines
mm/page_alloc.c +4
-0+if ((gfp_allowed_mask &GFP_IOFS ) ==GFP_IOFS ) +return false; +return true; +}This doesn't seem a terribly reliable way of detecting that PM has disabled the storage devices (which is what we really want to know
here: kswapd got crippled).
It only works because PM is the only caller that alters gfp_allowed_mask at runtime after early boot completes. We also check if suspend is in progress in mm/swapfile.c#try_to_free_swap() using the gfp_allowed_mask.
Quoted 2 lines
I guess it's safe for now, because PM is the only caller who alters
gfp_allowed_mask(I assume).
You assume correctly.
Quoted 4 lines
But an explicit
storage_is_unavaliable
global which is set and cleared at exactly the correct time is clearer, more direct and future-safer, no?
It feels overkill to allocate more global storage for it when gfp_allowed_mask is already there but I could rename pm_suspending() to pm_disabled_storage(), make try_to_free_swap() use the same helper but leave the implementation the same. This would clarify the situation.
Quoted 11 lines
Quoted 11 lines
mm/page_alloc.c +6
-0+#else + +static bool pm_suspending (void ) +{ +return false; +}#endif /* CONFIG_PM_SLEEP */ #ifdef CONFIG_HUGETLB_PAGE_SIZE_VARIABLE @@ -2214,6 +2228,14 @@ rebalance:
Quoted 14 lines
mm/page_alloc.c rebalance: · line 2238
Quoted 10 lines
mm/page_alloc.c +8
-0goto restart; } + +/* +* Suspend converts GFP_KERNEL to __GFP_WAIT which can +* prevent reclaim making forward progress without +* invoking OOM. Bail if we are suspending +*/ +if (pm_suspending ()) + goto nopage;The comment doesn't tell the whole story: it's important that kswapd writeout was disabled?
Writeout is disabled for flushers as well but your comment covers both and clarifies the situation. Thanks
Signature · 2 lines
Mel Gorman SUSE Labs
Quoted 6 lines
<SNIP>
It feels overkill to allocate more global storage for it whengfp_allowed_maskis already there but I could renamepm_suspending()topm_disabled_storage(), maketry_to_free_swap()use the same helper but leave the implementation the same. This would clarify the situation.
Something like this? It's on top of your comment update
==== CUT HERE ====
mm: Clarify usage of gfp_allowed_mask
This patch clarifies how gfp_allowed_mask is used and that during run-time it is PM-specific.
--- include/linux/gfp.h | 16 ++++++++++++++++ mm/page_alloc.c | 11 ++--------- mm/swapfile.c | 6 +++--- 3 files changed, 21 insertions(+), 12 deletions(-)
include/linux/gfp.h +16 -0
@@ -367,9 +367,25 @@ void drain_zone_pages(struct zone *zone, struct per_cpu_pages *pcp);void drain_all_pages (void );void drain_local_pages (void *dummy); +/* +* gfp_allowed_mask is set to GFP_BOOT_MASK during early boot to restrict what +* GFP flags are used before interrupts are enabled. Once interrupts are +* enabled, it is set to __GFP_BITS_MASK while the system is running. During +* hibernation, it is used by PM to avoid I/O during memory allocation while +* devices are suspended. +*/ extern gfp_t gfp_allowed_mask;extern void pm_restrict_gfp_mask (void );extern void pm_restore_gfp_mask (void ); +#ifdef CONFIG_PM_SLEEP +extern bool pm_suspended_storage (void ); +#else +static inline bool pm_suspended_storage (void ) +{ +return false; +} +#endif /* CONFIG_PM_SLEEP */ +#endif /* __LINUX_GFP_H */
mm/page_alloc.c +2 -9
@@ -128,19 +128,12 @@ void pm_restrict_gfp_mask(void) gfp_allowed_mask &= ~GFP_IOFS; } -static bool pm_suspending (void ) +bool pm_suspended_storage (void ) {if ((gfp_allowed_mask &GFP_IOFS ) ==GFP_IOFS )return false;return true; } - -#else - -static bool pm_suspending (void ) -{ -return false; -}#endif /* CONFIG_PM_SLEEP */ #ifdef CONFIG_HUGETLB_PAGE_SIZE_VARIABLE
@@ -2235,7 +2228,7 @@ rebalance:* Suspend also disables storage devices so kswapd cannot save * us. Bail if we are suspending. */ -if (pm_suspending ()) +if (pm_suspended_storage ()) goto nopage; }
mm/swapfile.c +3 -3
@@ -667,10 +667,10 @@ int try_to_free_swap(struct page *page)* original page might be freed under memory pressure, then * later read back in from swap, now with the wrong data. * -* Hibernation clears bits from gfp_allowed_mask to prevent -* memory reclaim from writing to disk, so check that here. +* Hibration suspends storage while it is writing the image +* to disk so check that here. */ -if (!(gfp_allowed_mask & __GFP_IO)) +if (pm_suspended_storage ())return 0 ;delete_from_swap_cache (page);
Quoted 74 lines · Mel Gorman
This patch seems to have gotten lost in the cracks and the discussion on alternatives that started here https://lkml.org/lkml/2011/10/25/24
petered out without any alternative patches being posted. Lacking a viable alternative patch, I'm reposting this patch because AFAIK, this bug still exists.Colin Cross reported;
Under the following conditions,
__alloc_pages_slowpathcan loop forever:
gfp_mask &__GFP_WAITis true
gfp_mask &__GFP_FSis false
reclaim and compaction make no progress
order <=PAGE_ALLOC_COSTLY_ORDERThese conditions happen very often during suspend and resume, when
pm_restrict_gfp_mask()effectively converts allGFP_KERNELallocations into__GFP_WAIT.The oom killer is not run because
gfp_mask&__GFP_FSis false, butshould_alloc_retrywill always return true when order is less thanPAGE_ALLOC_COSTLY_ORDER.In his fix, he avoided retrying the allocation if reclaim made no progress and
__GFP_FSwas not set. The problem is that this would result inGFP_NOIOallocations failing that previously succeeded which would be very unfortunate.The big difference between
GFP_NOIOand suspend convertingGFP_KERNELto behave likeGFP_NOIOis that normally flushers will be cleaning pages and kswapd reclaims pages allowingGFP_NOIOto succeed after a short delay. The same does not necessarily apply during suspend as the storage device may be suspended. Hence, this patch special cases the suspend case to fail the page allocation if reclaim cannot make progress. This might cause suspend to abort but that is better than a livelock.[[email protected]: Rework fix to be suspend specific]Reported-and-tested-by Colin Cross <[email protected]>Signed-off-by Mel Gorman <[email protected]>---
mm/page_alloc.c | 22 ++++++++++++++++++++++1 files changed, 22 insertions(+), 0 deletions(-)mm/page_alloc.c +15
-0@@ -127,6 +127,20 @@ void pm_restrict_gfp_mask(void) saved_gfp_mask = gfp_allowed_mask; gfp_allowed_mask &= ~GFP_IOFS; } + +static bool pm_suspending (void ) +{ + if ((gfp_allowed_mask & GFP_IOFS) == GFP_IOFS) + return false; + return true; +} + +#else + +static bool pm_suspending (void ) +{ + return false; +} #endif /* CONFIG_PM_SLEEP */ #ifdef CONFIG_HUGETLB_PAGE_SIZE_VARIABLE@@ -2214,6 +2228,14 @@ rebalance: goto restart; } +
mm/page_alloc.c rebalance: · line 2241
Quoted 10 lines
mm/page_alloc.c +7
-0+ /* + * Suspend converts GFP_KERNEL to __GFP_WAIT which can + * prevent reclaim making forward progress without + * invoking OOM. Bail if we are suspending + */ + if (pm_suspending()) + goto nopage; } /* Check if we should retry the allocation */
I don't have much time to look into this problem so I miss some things. But the feeling I have a mind when I faced this problem is why we should make another special case handling function.
Already we have such thing for hibernation - oom_killer_disabled in vm Could we use it instead of making new branch for very special case? Maybe It would be better to rename oom_killer_disabled with pm_is_going or something.
Signature · 9 lines
Kind regards, Minchan Kim -- To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM, see: http://www.linux-mm.org/ . Fight unfair telecom internet charges in Canada: sign http://stopthemeter.ca/ Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Quoted 83 lines · Minchan Kim
Quoted 82 lines · Mel Gorman
This patch seems to have gotten lost in the cracks and the discussion on alternatives that started here https://lkml.org/lkml/2011/10/25/24
petered out without any alternative patches being posted. Lacking a viable alternative patch, I'm reposting this patch because AFAIK, this bug still exists.Colin Cross reported;
Under the following conditions,
__alloc_pages_slowpathcan loop forever:
gfp_mask&__GFP_WAITis true
gfp_mask&__GFP_FSis false
reclaim and compaction make no progress
order <=PAGE_ALLOC_COSTLY_ORDERThese conditions happen very often during suspend and resume, when
pm_restrict_gfp_mask()effectively converts allGFP_KERNELallocations into__GFP_WAIT.The oom killer is not run because
gfp_mask&__GFP_FSis false, butshould_alloc_retrywill always return true when order is less thanPAGE_ALLOC_COSTLY_ORDER.In his fix, he avoided retrying the allocation if reclaim made no progress and
__GFP_FSwas not set. The problem is that this would result inGFP_NOIOallocations failing that previously succeeded which would be very unfortunate.The big difference between
GFP_NOIOand suspend convertingGFP_KERNELto behave likeGFP_NOIOis that normally flushers will be cleaning pages and kswapd reclaims pages allowingGFP_NOIOto succeed after a short delay. The same does not necessarily apply during suspend as the storage device may be suspended. Hence, this patch special cases the suspend case to fail the page allocation if reclaim cannot make progress. This might cause suspend to abort but that is better than a livelock.[[email protected]: Rework fix to be suspend specific]Reported-and-tested-by Colin Cross <[email protected]>Signed-off-by Mel Gorman <[email protected]>--- mm/page_alloc.c | 22 ++++++++++++++++++++++ 1 files changed, 22 insertions(+), 0 deletions(-)mm/page_alloc.c +22
-0@@ -127,6 +127,20 @@ void pm_restrict_gfp_mask(void) saved_gfp_mask = gfp_allowed_mask; gfp_allowed_mask &= ~GFP_IOFS; } + +static bool pm_suspending (void ) +{ +if ((gfp_allowed_mask &GFP_IOFS ) ==GFP_IOFS ) +return false; +return true; +} + +#else + +static bool pm_suspending (void ) +{ +return false; +}#endif /* CONFIG_PM_SLEEP */ #ifdef CONFIG_HUGETLB_PAGE_SIZE_VARIABLE @@ -2214,6 +2228,14 @@ rebalance: goto restart; } + +/* +* Suspend converts GFP_KERNEL to __GFP_WAIT which can +* prevent reclaim making forward progress without +* invoking OOM. Bail if we are suspending +*/ +if (pm_suspending ()) + goto nopage; }
Quoted 8 lines
mm/page_alloc.c rebalance: · line 2241
Quoted 2 lines
mm/page_alloc.c +0
-0/* Check if we should retry the allocation */ I don't have much time to look into this problem so I miss some things. But the feeling I have a mind when I faced this problem is why we should make another special case handling function.
Already we have such thing for hibernation -oom_killer_disabledin vm Could we use it instead of making new branch for very special case?
Fair question!
Suspend is a multi-stage process and the OOM killer is disabled at a different time to the GFP flags being restricted. This is another reason why renaming to pm_suspending to pm_suspended_storage is a good idea (pm_suspending is misleading at best).
I am vague on all the steps hibernation takes but initially processes are frozen and if they are successfully frozen then the OOM killer is disabled. At this point, storage is still active so the GFP allowed mask is the same. When preparing to write the image, kernel threads are suspended so there is no new IO being initiated and then the GFP mask is restricted to prevent any memory allocation trying to write pages to storage. It then writes the image to disk.
So what we have now is
if (!did_some_progress) {
if ((gfp_mask & __GFP_FS) && !(gfp_mask & __GFP_NORETRY)) {
if (oom_killer_disabled)
goto nopage;Lets say we changed that to
if (!did_some_progress) {
if (oom_killer_disabled)
goto nopage;
if ((gfp_mask & __GFP_FS) && !(gfp_mask & __GFP_NORETRY)) {The impact would be that during the time between processes been frozen and storage being suspended, GFP_NOIO allocations that used to call wait_iff_congested and retry while kswapd does its thing will return failure instead. These GFP_NOIO allocations that used to succeed will now fail in rare cases during suspend and I don't think we want that.
Is this what you meant or had you something else in mind?
Quoted 3 lines
Maybe It would be better to rename
oom_killer_disabledwithpm_is_goingor something.
I think renaming oom_killer_disabled to pm_oom_disabled would be reasonable but it does not necessarily get us away from needing a pm_suspended_storage() test.
Signature · 9 lines
Mel Gorman SUSE Labs -- To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM, see: http://www.linux-mm.org/ . Fight unfair telecom internet charges in Canada: sign http://stopthemeter.ca/ Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Quoted 122 lines · Mel Gorman
Quoted 83 lines · Minchan Kim
Quoted 82 lines · Mel Gorman
This patch seems to have gotten lost in the cracks and the discussion on alternatives that started here https://lkml.org/lkml/2011/10/25/24
petered out without any alternative patches being posted. Lacking a viable alternative patch, I'm reposting this patch because AFAIK, this bug still exists.Colin Cross reported;
Under the following conditions,
__alloc_pages_slowpathcan loop forever:
gfp_mask &__GFP_WAITis true
gfp_mask &__GFP_FSis false
reclaim and compaction make no progress
order <=PAGE_ALLOC_COSTLY_ORDERThese conditions happen very often during suspend and resume, when
pm_restrict_gfp_mask()effectively converts allGFP_KERNELallocations into__GFP_WAIT.The oom killer is not run because
gfp_mask&__GFP_FSis false, butshould_alloc_retrywill always return true when order is less thanPAGE_ALLOC_COSTLY_ORDER.In his fix, he avoided retrying the allocation if reclaim made no progress and
__GFP_FSwas not set. The problem is that this would result inGFP_NOIOallocations failing that previously succeeded which would be very unfortunate.The big difference between
GFP_NOIOand suspend convertingGFP_KERNELto behave likeGFP_NOIOis that normally flushers will be cleaning pages and kswapd reclaims pages allowingGFP_NOIOto succeed after a short delay. The same does not necessarily apply during suspend as the storage device may be suspended. Hence, this patch special cases the suspend case to fail the page allocation if reclaim cannot make progress. This might cause suspend to abort but that is better than a livelock.[[email protected]: Rework fix to be suspend specific]Reported-and-tested-by Colin Cross <[email protected]>Signed-off-by Mel Gorman <[email protected]>---
mm/page_alloc.c | 22 ++++++++++++++++++++++1 files changed, 22 insertions(+), 0 deletions(-)mm/page_alloc.c +22
-0@@ -127,6 +127,20 @@ void pm_restrict_gfp_mask(void) saved_gfp_mask = gfp_allowed_mask; gfp_allowed_mask &= ~GFP_IOFS; } + +static bool pm_suspending (void ) +{ + if ((gfp_allowed_mask & GFP_IOFS) == GFP_IOFS) + return false; + return true; +} + +#else + +static bool pm_suspending (void ) +{ + return false; +} #endif /* CONFIG_PM_SLEEP */ #ifdef CONFIG_HUGETLB_PAGE_SIZE_VARIABLE@@ -2214,6 +2228,14 @@ rebalance: goto restart; } + + /* + * Suspend converts GFP_KERNEL to __GFP_WAIT which can + * prevent reclaim making forward progress without + * invoking OOM. Bail if we are suspending + */ + if (pm_suspending()) + goto nopage; }Quoted 8 lines
mm/page_alloc.c rebalance: · line 2241
Quoted 2 lines
mm/page_alloc.c +0
-0/* Check if we should retry the allocation */I don't have much time to look into this problem so I miss some things. But the feeling I have a mind when I faced this problem is why we should make another special case handling function.
Already we have such thing for hibernation -oom_killer_disabledin vm Could we use it instead of making new branch for very special case?Fair question!
Suspend is a multi-stage process and the OOM killer is disabled at a different time to the GFP flags being restricted. This is another reason why renaming to
pm_suspendingtopm_suspended_storageis a good idea (pm_suspendingis misleading at best).I am vague on all the steps hibernation takes but initially processes are frozen and if they are successfully frozen then the OOM killer is disabled. At this point, storage is still active so the GFP allowed mask is the same. When preparing to write the image, kernel threads are suspended so there is no new IO being initiated and then the GFP mask is restricted to prevent any memory allocation trying to write pages to storage. It then writes the image to disk.
So what we have now is
if (!did_some_progress) { if ((gfp_mask & __GFP_FS) && !(gfp_mask & __GFP_NORETRY)) { if (oom_killer_disabled) goto nopage;Lets say we changed that to
if (!did_some_progress) { if (oom_killer_disabled) goto nopage; if ((gfp_mask & __GFP_FS) && !(gfp_mask & __GFP_NORETRY)) {
Quoted 8 lines
The impact would be that during the time between processes been frozen and storage being suspended,
GFP_NOIOallocations that used to callwait_iff_congestedand retry while kswapd does its thing will return failure instead. TheseGFP_NOIOallocations that used to succeed will now fail in rare cases during suspend and I don't think we want that.Is this what you meant or had you something else in mind?
You read my mind exactly!
I thought hibernation process is as follows,
freeze user processesoom_disablehibernate_preallocate_memory
freeze kernel processes(include kswapd)pm_restrict_gfp_maskswsusp_save
My guessing is hibernate_prealocate_memory should reserve all memory needed for hibernation for reclaimaing pages of kswapd because kswapd just would be stopped so during swsusp_save, page reclaim should not be occured.
But being see description of patch, my guess seems wrong.
Now the problem happens and it means page reclaim happens during swsusp_save. Colin or someone could confirm this?
If so, could we reserve more memory when we preallocate hibernation memory for avoiding page reclaim without kswapd?
Signature · 2 lines
Kind regards, Minchan Kim
Quoted 22 lines · Minchan Kim
Quoted 9 lines · Mel Gorman
On Wed, Nov 16, 2011 at 01:13:30AM +0900, Minchan Kim wrote:
The impact would be that during the time between processes been frozen and storage being suspended,GFP_NOIOallocations that used to callwait_iff_congestedand retry while kswapd does its thing will return failure instead. TheseGFP_NOIOallocations that used to succeed will now fail in rare cases during suspend and I don't think we want that.Is this what you meant or had you something else in mind?
You read my mind exactly!
I thought hibernation process is as follows,
freeze user processes
oom_disablehibernate_preallocate_memory
freeze kernel processes(include kswapd)pm_restrict_gfp_maskswsusp_save
Quoted 7 lines
My guessing is
hibernate_prealocate_memoryshould reserve all memory needed for hibernation for reclaimaing pages of kswapd because kswapd just would be stopped so duringswsusp_save, page reclaim should not be occured.But being see description of patch, my guess seems wrong.
Now the problem happens and it means page reclaim happens duringswsusp_save. Colin or someone could confirm this?
The problem I see is during suspend, not hibernation. The particular allocation that usually causes the problem is the pgd_alloc for page tables when re-enabling the 2nd cpu during resume, which is odd as those same page tables were freed during suspend. I guess an unfreezable kernel thread allocated that memory between the free and re-allocation.
Quoted 7 lines
If so, could we reserve more memory when we preallocate hibernation memory for avoiding page reclaim without kswapd?
--
Kind regards,
Minchan Kim
Quoted 31 lines · Colin Cross
Quoted 22 lines · Minchan Kim
Quoted 9 lines · Mel Gorman
On Wed, Nov 16, 2011 at 01:13:30AM +0900, Minchan Kim wrote:
The impact would be that during the time between processes been frozen and storage being suspended,GFP_NOIOallocations that used to callwait_iff_congestedand retry while kswapd does its thing will return failure instead. TheseGFP_NOIOallocations that used to succeed will now fail in rare cases during suspend and I don't think we want that.Is this what you meant or had you something else in mind?
You read my mind exactly!
I thought hibernation process is as follows,
freeze user processes
oom_disablehibernate_preallocate_memory
freeze kernel processes(include kswapd)pm_restrict_gfp_maskswsusp_saveQuoted 7 lines
My guessing is
hibernate_prealocate_memoryshould reserve all memory needed for hibernation for reclaimaing pages of kswapd because kswapd just would be stopped so duringswsusp_save, page reclaim should not be occured.But being see description of patch, my guess seems wrong.
Now the problem happens and it means page reclaim happens duringswsusp_save. Colin or someone could confirm this?
Quoted 6 lines
The problem I see is during suspend, not hibernation. The particular allocation that usually causes the problem is the
pgd_allocfor page tables when re-enabling the 2nd cpu during resume, which is odd as those same page tables were freed during suspend. I guess an unfreezable kernel thread allocated that memory between the free and re-allocation.
Then, How about this?
[barrios@barrios-laptop linux-2.6]$ git diff
kernel/power/suspend.c +2 -0
@@ -297,9 +297,11 @@ int enter_state(suspend_state_t state) goto Finish;pr_debug ("PM: Entering %s sleep\n" , pm_states[state]); +oom_killer_disable ();pm_restrict_gfp_mask (); error =suspend_devices_and_enter (state);pm_restore_gfp_mask (); +oom_killer_enable (); Finish:pr_debug ("PM: Finishing wakeup.\n" );
mm/page_alloc.c +2 -2
@@ -2177,9 +2177,9 @@ rebalance:* running out of options and have to consider going OOM */ if (!did_some_progress) { -if ((gfp_mask & __GFP_FS) && !(gfp_mask & __GFP_NORETRY)) { -if (oom_killer_disabled) +if (oom_killer_disabled) goto nopage; +if ((gfp_mask & __GFP_FS) && !(gfp_mask & __GFP_NORETRY)) { page = __alloc_pages_may_oom(gfp_mask, order, zonelist, high_zoneidx, nodemask, preferred_zone,
Signature · 2 lines
Kind regards, Minchan Kim
Quoted 26 lines · Minchan Kim
Then, How about this?
[barrios@barrios-laptop linux-2.6]$ git diff
kernel/power/suspend.c +2
-0@@ -297,9 +297,11 @@ int enter_state(suspend_state_t state) goto Finish;pr_debug ("PM: Entering %s sleep\n" , pm_states[state]); +oom_killer_disable ();pm_restrict_gfp_mask (); error =suspend_devices_and_enter (state);pm_restore_gfp_mask (); +oom_killer_enable (); Finish:pr_debug ("PM: Finishing wakeup.\n" );mm/page_alloc.c +0
-0@@ -2177,9 +2177,9 @@ rebalance:* running out of options and have to consider going OOM
mm/page_alloc.c rebalance: · line 2185
Quoted 10 lines
mm/page_alloc.c +2
-2*/ if (!did_some_progress) { -if ((gfp_mask & __GFP_FS) && !(gfp_mask & __GFP_NORETRY)) { -if (oom_killer_disabled) +if (oom_killer_disabled) goto nopage; +if ((gfp_mask & __GFP_FS) && !(gfp_mask & __GFP_NORETRY)) { page = __alloc_pages_may_oom(gfp_mask, order, zonelist, high_zoneidx, nodemask, preferred_zone,
I'd prefer something like this. The whole 'gfp_allowed_flags' thing was designed to make GFP_KERNEL work during boot time where it's obviously safe to do that. I really don't think that's going to work suspend cleanly.
Pekka
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Fight unfair telecom internet charges in Canada: sign http://stopthemeter.ca/
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Quoted 14 lines · Pekka Enberg
Quoted 14 lines
kernel/power/suspend.c +2
-0@@ -297,9 +297,11 @@ int enter_state(suspend_state_t state) goto Finish;pr_debug ("PM: Entering %s sleep\n" , pm_states[state]); +oom_killer_disable ();pm_restrict_gfp_mask (); error =suspend_devices_and_enter (state);pm_restore_gfp_mask (); +oom_killer_enable ();
Quoted 14 lines
Quoted 7 lines
kernel/power/suspend.c +0
-0Finish:pr_debug ("PM: Finishing wakeup.\n" );mm/page_alloc.c +0
-0mm/page_alloc.c rebalance: · line 2181
Quoted 7 lines
mm/page_alloc.c +1
-2@@ -2177,9 +2177,9 @@ rebalance:* running out of options and have to consider going OOM */ if (!did_some_progress) { -if ((gfp_mask & __GFP_FS) && !(gfp_mask & __GFP_NORETRY)) { -if (oom_killer_disabled) +if (oom_killer_disabled) goto nopage;
You're allowing __GFP_NOFAIL allocations to fail.
Quoted 10 lines
mm/page_alloc.c rebalance: · line 2185
Quoted 5 lines
mm/page_alloc.c +1
-0+if ((gfp_mask & __GFP_FS) && !(gfp_mask & __GFP_NORETRY)) { page = __alloc_pages_may_oom(gfp_mask, order, zonelist, high_zoneidx, nodemask, preferred_zone,I'd prefer something like this. The whole '
gfp_allowed_flags' thing was designed to makeGFP_KERNELwork during boot time where it's obviously safe to do that. I really don't think that's going to work suspend cleanly.
Adding Rafael to the cc.
This has been done since 2.6.34 and presumably has been working quite well. I don't have a specific objection to gfp_allowed_flags to be used outside of boot since it seems plausible that there are system-level contexts that would need different behavior in the page allocator and this does it effectively without major surgery or a slower fastpath. Suspend is using it just like boot does before irqs are enabled, so I don't have an objection to it.
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Fight unfair telecom internet charges in Canada: sign http://stopthemeter.ca/
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Quoted 44 lines · David Rientjes
Quoted 14 lines · Pekka Enberg
Quoted 14 lines
kernel/power/suspend.c +2
-0@@ -297,9 +297,11 @@ int enter_state(suspend_state_t state) goto Finish;pr_debug ("PM: Entering %s sleep\n" , pm_states[state]); +oom_killer_disable ();pm_restrict_gfp_mask (); error =suspend_devices_and_enter (state);pm_restore_gfp_mask (); +oom_killer_enable ();Quoted 14 lines
Quoted 7 lines
kernel/power/suspend.c +0
-0Finish:pr_debug ("PM: Finishing wakeup.\n" );mm/page_alloc.c +0
-0mm/page_alloc.c rebalance: · line 2181
Quoted 7 lines
mm/page_alloc.c +1
-2@@ -2177,9 +2177,9 @@ rebalance:* running out of options and have to consider going OOM */ if (!did_some_progress) { -if ((gfp_mask & __GFP_FS) && !(gfp_mask & __GFP_NORETRY)) { -if (oom_killer_disabled) +if (oom_killer_disabled) goto nopage;You're allowing
__GFP_NOFAILallocations to fail.Quoted 10 lines
mm/page_alloc.c rebalance: · line 2185
Quoted 5 lines
mm/page_alloc.c +1
-0+if ((gfp_mask & __GFP_FS) && !(gfp_mask & __GFP_NORETRY)) { page = __alloc_pages_may_oom(gfp_mask, order, zonelist, high_zoneidx, nodemask, preferred_zone,I'd prefer something like this. The whole '
gfp_allowed_flags' thing was designed to makeGFP_KERNELwork during boot time where it's obviously safe to do that. I really don't think that's going to work suspend cleanly.
Quoted 4 lines
Adding Rafael to the cc.
This has been done since 2.6.34 and presumably has been working quite well.
Yes, it has.
Quoted 6 lines
I don't have a specific objection to
gfp_allowed_flagsto be used outside of boot since it seems plausible that there are system-level contexts that would need different behavior in the page allocator and this does it effectively without major surgery or a slower fastpath. Suspend is using it just like boot does before irqs are enabled, so I don't have an objection to it.
Good. :-)
Thanks,
Rafael
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Fight unfair telecom internet charges in Canada: sign http://stopthemeter.ca/
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Quoted 46 lines · David Rientjes
Quoted 14 lines · Pekka Enberg
Quoted 14 lines
kernel/power/suspend.c +2
-0@@ -297,9 +297,11 @@ int enter_state(suspend_state_t state) goto Finish; pr_debug("PM: Entering %s sleep\n", pm_states[state]); + oom_killer_disable(); pm_restrict_gfp_mask(); error = suspend_devices_and_enter(state); pm_restore_gfp_mask(); + oom_killer_enable();Quoted 14 lines
Quoted 7 lines
Finish:
pr_debug("PM: Finishing wakeup.\n");mm/page_alloc.c +0
-0mm/page_alloc.c rebalance: · line 2181
Quoted 7 lines
mm/page_alloc.c +1
-2@@ -2177,9 +2177,9 @@ rebalance:* running out of options and have to consider going OOM */ if (!did_some_progress) { - if ((gfp_mask & __GFP_FS) && !(gfp_mask & __GFP_NORETRY)) { - if (oom_killer_disabled) + if (oom_killer_disabled) goto nopage;You're allowing
__GFP_NOFAILallocations to fail.Quoted 10 lines
mm/page_alloc.c rebalance: · line 2185
Quoted 5 lines
mm/page_alloc.c +1
-0+ if ((gfp_mask & __GFP_FS) && !(gfp_mask & __GFP_NORETRY)) { page = __alloc_pages_may_oom(gfp_mask, order, zonelist, high_zoneidx, nodemask, preferred_zone,I'd prefer something like this. The whole '
gfp_allowed_flags' thing was designed to makeGFP_KERNELwork during boot time where it's obviously safe to do that. I really don't think that's going to work suspend cleanly.Adding Rafael to the cc.
Quoted 8 lines
This has been done since 2.6.34 and presumably has been working quite well. I don't have a specific objection to
gfp_allowed_flagsto be used outside of boot since it seems plausible that there are system-level contexts that would need different behavior in the page allocator and this does it effectively without major surgery or a slower fastpath. Suspend is using it just like boot does before irqs are enabled, so I don't have an objection to it.
My point isn't using gfp_allowed_flags(maybe it's Pekka's concern) but why adding new special case handling code like pm_suspended_storage. I think we can handle the issue with oom_killer_disabled(but the naming is bad)
Signature · 9 lines
Kind regards, Minchan Kim -- To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM, see: http://www.linux-mm.org/ . Fight unfair telecom internet charges in Canada: sign http://stopthemeter.ca/ Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Quoted 53 lines · Minchan Kim
Quoted 44 lines
Quoted 14 lines
Quoted 14 lines
kernel/power/suspend.c +2
-0@@ -297,9 +297,11 @@ int enter_state(suspend_state_t state) A A A A A A A A goto Finish; A A A A pr_debug("PM: Entering %s sleep\n", pm_states[state]); + A A A oom_killer_disable(); A A A A pm_restrict_gfp_mask(); A A A A error = suspend_devices_and_enter(state); A A A A pm_restore_gfp_mask(); + A A A oom_killer_enable();Quoted 14 lines
Quoted 13 lines
A Finish:
A A A Apr_debug("PM: Finishing wakeup.\n");mm/page_alloc.c +0
-0@@ -2177,9 +2177,9 @@ rebalance:A A A A * running out of options and have to consider going OOM A A A A */ A A A A if (!did_some_progress) { - A A A A A A A if ((gfp_mask & __GFP_FS) && !(gfp_mask & __GFP_NORETRY)) { - A A A A A A A A A A A if (oom_killer_disabled) + A A A A A A A if (oom_killer_disabled)Quoted 1 line
A A A A A A A A A A A A A A A A goto nopage;
You're allowing
__GFP_NOFAILallocations to fail.Quoted 10 lines
Quoted 5 lines
+ A A A A A A A if ((
gfp_mask&__GFP_FS) && !(gfp_mask &__GFP_NORETRY)) {A A A A A A A A A A A A page = __alloc_pages_may_oom(gfp_mask, order, A A A A A A A A A A A A A A A A A A A A zonelist, high_zoneidx, A A A A A A A A A A A A A A A A A A A A nodemask, preferred_zone,I'd prefer something like this. The whole '
gfp_allowed_flags' thing was designed to makeGFP_KERNELwork during boot time where it's obviously safe to do that. I really don't think that's going to work suspend cleanly.Adding Rafael to the cc.
Quoted 8 lines
This has been done since 2.6.34 and presumably has been working quite well. A I don't have a specific objection to
gfp_allowed_flagsto be used outside of boot since it seems plausible that there are system-level contexts that would need different behavior in the page allocator and this does it effectively without major surgery or a slower fastpath. A Suspend is using it just like boot does before irqs are enabled, so I don't have an objection to it.
Quoted 4 lines
My point isn't using
gfp_allowed_flags(maybe it's Pekka's concern)but why adding new special case handling code likepm_suspended_storage. I think we can handle the issue withoom_killer_disabled(but the naming is bad)
Ignore the name of the function that Mel is introducing, it's only related to suspend because that's the only thing that (currently) alters gfp_allowed_mask after boot. If something else were to clear __GFP_FS and __GFP_IO in the future, we'd simply need to rename the function. I was going to ask for a comment specifically about that, but I think it's proximity to the function that allows gfp_allowed_mask to be altered is sufficient.
We'd really like to avoid the loop if __GFP_FS and __GFP_IO are not set. If oom_killer_disabled is set anytime that gfp_allowed_mask does not allow them for _all_ page allocations, then we could certainly replace the new pm_suspended_storage() check in should_alloc_retry() with a check on oom_killer_disabled.
I'll let you and Rafael work out whether that can be done or not, I just see pm_restrict_gfp_mask() being called in kernel/power/hibernate.c and kernel/power/suspend.c whereas oom_killer_disable() is only called in kernel/power/process.c. oom_killer_disable() would be unnecessary if __GFP_FS were already cleared, so it would require some changes in the suspend code.
I like Mel's patch because it's easily maintainable and only depends on the state of reclaim and the gfp flags being passed in, which is the direct cause of the infinite loop.
Quoted 30 lines · Mel Gorman
mm/page_alloc.c +15
-0@@ -127,6 +127,20 @@ void pm_restrict_gfp_mask(void) saved_gfp_mask = gfp_allowed_mask; gfp_allowed_mask &= ~GFP_IOFS; } + +static bool pm_suspending (void ) +{ +if ((gfp_allowed_mask &GFP_IOFS ) ==GFP_IOFS ) +return false; +return true; +} + +#else + +static bool pm_suspending (void ) +{ +return false; +}#endif /* CONFIG_PM_SLEEP */ #ifdef CONFIG_HUGETLB_PAGE_SIZE_VARIABLE @@ -2214,6 +2228,14 @@ rebalance: goto restart; } +
mm/page_alloc.c rebalance: · line 2241
Quoted 10 lines
mm/page_alloc.c +7
-0+/* +* Suspend converts GFP_KERNEL to __GFP_WAIT which can +* prevent reclaim making forward progress without +* invoking OOM. Bail if we are suspending +*/ +if (pm_suspending ()) + goto nopage; }/* Check if we should retry the allocation */
This allows all __GFP_NOFAIL allocations to fail while pm_restrict_gfp_mask() is in effect, so I disagree with this unless it is moved into the should_alloc_retry() logic. If you pass did_some_progress into that function and then moved the check for __GFP_NOFAIL right under the check for __GFP_NORETRY and checked for pm_suspending() there (and before the check for PAGE_ALLOC_COSTLY_ORDER) then it would allow the infinite loop for __GFP_NOFAIL which is required if __GFP_WAIT.
Quoted 15 lines · David Rientjes
Quoted 15 lines
<SNIP>
(quoted diff) +8
-0@@ -2214,6 +2228,14 @@ rebalance: goto restart; } + + /* + * Suspend converts GFP_KERNEL to __GFP_WAIT which can + * prevent reclaim making forward progress without + * invoking OOM. Bail if we are suspending + */ + if (pm_suspending()) + goto nopage; }
Quoted 9 lines
Review rebalance: · line 2241
Quoted 1 line
(quoted diff) +0
-0/* Check if we should retry the allocation */This allows all
__GFP_NOFAILallocations to fail whilepm_restrict_gfp_mask()is in effect, so I disagree with this unless it is moved into theshould_alloc_retry()logic. If you passdid_some_progressinto that function and then moved the check for__GFP_NOFAILright under the check for__GFP_NORETRYand checked forpm_suspending()there (and before the check forPAGE_ALLOC_COSTLY_ORDER) then it would allow the infinite loop for__GFP_NOFAILwhich is required if__GFP_WAIT.
Good point. I agree that it would be more consistent although there is still the risk of infinite looping with __GFP_NOFAIL if storage devices are disabled.
Colin reported elsewhere in this thread that "the particular allocation that usually causes the problem is the pgd_alloc for page tables when re-enabling the 2nd cpu during resume". On X86, those allocations are using the flags
GFP_KERNEL | __GFP_NOTRACK | __GFP_REPEAT | __GFP_ZERO
so they should not be trapped in an infinite loop due to __GFP_NOFAIL. On ARM, they use GFP_KERNEL so should also be ok.
That said, this patch is no longer functionally equivalent to what he tested so I had to remove the tested-by. Colin, can you retest with the following patch please? If it gets stuck, it's interesting in itself because it means we were previously failing __GFP_NOFAIL allocations during suspend on paths that probably don't handle allocation failure very well.
David, is this what you meant? This patch includes all the documentation-related updates that were discussed in this thread as well as updated the check in mm/swapfile.c for hibernation.
==== CUT HERE ====
mm: avoid livelock on !__GFP_FS allocations v2
Changelog since V1
o Move PM check to should_alloc_retry (David Rientjes)
o Add some additional documentation
Colin Cross reported;
Under the following conditions, __alloc_pages_slowpath can loop forever:
gfp_mask & __GFP_WAIT is true
gfp_mask & __GFP_FS is false
reclaim and compaction make no progress
order <= PAGE_ALLOC_COSTLY_ORDER
These conditions happen very often during suspend and resume, when pm_restrict_gfp_mask() effectively converts all GFP_KERNEL allocations into __GFP_WAIT.
The oom killer is not run because gfp_mask & __GFP_FS is false, but should_alloc_retry will always return true when order is less than PAGE_ALLOC_COSTLY_ORDER.
In his fix, he avoided retrying the allocation if reclaim made no progress and __GFP_FS was not set. The problem is that this would result in GFP_NOIO allocations failing that previously succeeded which would be very unfortunate.
The big difference between GFP_NOIO and suspend converting GFP_KERNEL to behave like GFP_NOIO is that normally flushers will be cleaning pages and kswapd reclaims pages allowing GFP_NOIO to succeed after a short delay. The same does not necessarily apply during suspend as the storage device may be suspended.
This patch special cases the suspend case to fail the page allocation if reclaim cannot make progress and adds some documentation on how gfp_allowed_mask is currently used. Failing allocations like this may cause suspend to abort but that is better than a livelock.
--- include/linux/gfp.h | 16 ++++++++++++++++ mm/page_alloc.c | 30 ++++++++++++++++++++++-------- mm/swapfile.c | 6 +++--- 3 files changed, 41 insertions(+), 11 deletions(-)
include/linux/gfp.h +16 -0
@@ -367,9 +367,25 @@ void drain_zone_pages(struct zone *zone, struct per_cpu_pages *pcp);void drain_all_pages (void );void drain_local_pages (void *dummy); +/* +* gfp_allowed_mask is set to GFP_BOOT_MASK during early boot to restrict what +* GFP flags are used before interrupts are enabled. Once interrupts are +* enabled, it is set to __GFP_BITS_MASK while the system is running. During +* hibernation, it is used by PM to avoid I/O during memory allocation while +* devices are suspended. +*/ extern gfp_t gfp_allowed_mask;extern void pm_restrict_gfp_mask (void );extern void pm_restore_gfp_mask (void ); +#ifdef CONFIG_PM_SLEEP +extern bool pm_suspended_storage (void ); +#else +static inline bool pm_suspended_storage (void ) +{ +return false; +} +#endif /* CONFIG_PM_SLEEP */ +#endif /* __LINUX_GFP_H */
mm/page_alloc.c +22 -8
@@ -127,6 +127,13 @@ void pm_restrict_gfp_mask(void) saved_gfp_mask = gfp_allowed_mask; gfp_allowed_mask &= ~GFP_IOFS; } + +bool pm_suspended_storage (void ) +{ +if ((gfp_allowed_mask &GFP_IOFS ) ==GFP_IOFS ) +return false; +return true; +}#endif /* CONFIG_PM_SLEEP */ #ifdef CONFIG_HUGETLB_PAGE_SIZE_VARIABLE
@@ -1795,12 +1802,25 @@ void warn_alloc_failed(gfp_t gfp_mask, int order, const char *fmt, ...)static inline int should_alloc_retry (gfp_t gfp_mask,unsigned int order, +unsigned long did_some_progress,unsigned long pages_reclaimed) {/* Do not loop if specifically requested */ if (gfp_mask & __GFP_NORETRY)return 0 ; +/* Always retry if specifically requested */ +if (gfp_mask & __GFP_NOFAIL) +return 1 ; + +/* +* Suspend converts GFP_KERNEL to __GFP_WAIT which can prevent reclaim +* making forward progress without invoking OOM. Suspend also disables +* storage devices so kswapd will not help. Bail if we are suspending. +*/ +if (!did_some_progress &&pm_suspended_storage ()) +return 0 ; +/* * In this implementation, order <= PAGE_ALLOC_COSTLY_ORDER * means __GFP_NOFAIL, but that may not be true in other
@@ -1819,13 +1839,6 @@ should_alloc_retry(gfp_t gfp_mask, unsigned int order,if (gfp_mask & __GFP_REPEAT && pages_reclaimed < (1 << order))return 1 ; -/* -* Don't let big-order allocations loop unless the caller -* explicitly requests that. -*/ -if (gfp_mask & __GFP_NOFAIL) -return 1 ; -return 0 ; }
@@ -2218,7 +2231,8 @@ rebalance:/* Check if we should retry the allocation */ pages_reclaimed += did_some_progress; -if (should_alloc_retry (gfp_mask, order, pages_reclaimed)) { +if (should_alloc_retry (gfp_mask, order, did_some_progress, + pages_reclaimed)) {/* Wait for some write requests to complete then retry */ wait_iff_congested (preferred_zone,BLK_RW_ASYNC ,HZ /50 ); goto rebalance;
mm/swapfile.c +3 -3
@@ -667,10 +667,10 @@ int try_to_free_swap(struct page *page)* original page might be freed under memory pressure, then * later read back in from swap, now with the wrong data. * -* Hibernation clears bits from gfp_allowed_mask to prevent -* memory reclaim from writing to disk, so check that here. +* Hibration suspends storage while it is writing the image +* to disk so check that here. */ -if (!(gfp_allowed_mask & __GFP_IO)) +if (pm_suspended_storage ())return 0 ;delete_from_swap_cache (page);
David Rientjes
Acked-byQuoted 4 lines · Mel Gorman
Good point. I agree that it would be more consistent although there is still the risk of infinite looping with
__GFP_NOFAILif storage devices are disabled.
Yeah, that's always been possible even regardless of the state of storage devices. If a task has access to memory reserves via TIF_MEMDIE, __alloc_pages_high_priority() will just loop indefinitely anyway for these allocations. While users of __GFP_NOFAIL accept that it won't return NULL as long as they have __GFP_WAIT (which they all do), then they should also accept the fact that it may never return at all.
Quoted 10 lines
Colin reported elsewhere in this thread that "the particular allocation that usually causes the problem is the
pgd_allocfor page tables when re-enabling the 2nd cpu during resume". On X86, those allocations are using the flags
GFP_KERNEL|__GFP_NOTRACK|__GFP_REPEAT|__GFP_ZEROso they should not be trapped in an infinite loop due to
__GFP_NOFAIL. On ARM, they useGFP_KERNELso should also be ok.
The __GFP_REPEAT is concerning because there's a high liklihood that !__GFP_FS as a result of suspend will never cause enough pages to be reclaimed so the necessary threshold will be reached to exit from its own self-induced infinite loop. So if we go forward with failing allocations attempted without __GFP_IO and __GFP_FS that are !__GFP_NOFAIL, then we should also add that __GFP_REPEAT is a no-op without __GFP_IO or __GFP_FS.
Quoted 43 lines
David, is this what you meant? This patch includes all the documentation-related updates that were discussed in this thread as well as updated the check in
mm/swapfile.cfor hibernation.==== CUT HERE ====
mm: avoid livelock on !__GFP_FS allocations v2Changelog since V1
o Move PM check toshould_alloc_retry(David Rientjes)
o Add some additional documentationColin Cross reported;
Under the following conditions,
__alloc_pages_slowpathcan loop forever:
gfp_mask&__GFP_WAITis true
gfp_mask&__GFP_FSis false
reclaim and compaction make no progress
order <=PAGE_ALLOC_COSTLY_ORDERThese conditions happen very often during suspend and resume, when
pm_restrict_gfp_mask()effectively converts allGFP_KERNELallocations into__GFP_WAIT.The oom killer is not run because
gfp_mask&__GFP_FSis false, butshould_alloc_retrywill always return true when order is less thanPAGE_ALLOC_COSTLY_ORDER.In his fix, he avoided retrying the allocation if reclaim made no progress and
__GFP_FSwas not set. The problem is that this would result inGFP_NOIOallocations failing that previously succeeded which would be very unfortunate.The big difference between
GFP_NOIOand suspend convertingGFP_KERNELto behave likeGFP_NOIOis that normally flushers will be cleaning pages and kswapd reclaims pages allowingGFP_NOIOto succeed after a short delay. The same does not necessarily apply during suspend as the storage device may be suspended.This patch special cases the suspend case to fail the page allocation if reclaim cannot make progress and adds some documentation on how
gfp_allowed_maskis currently used. Failing allocations like this may cause suspend to abort but that is better than a livelock.
Quoted 4 lines
[[email protected]: Rework fix to be suspend specific][[email protected]: Move suspended device check to should_alloc_retry]Reported-by Colin Cross <[email protected]>Signed-off-by Mel Gorman <[email protected]>
Thanks Mel!
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Fight unfair telecom internet charges in Canada: sign http://stopthemeter.ca/
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>