Skip to contentSkip to conversation

mm: avoid livelock on !__GFP_FS allocations

PATCH In review Mel Gorman 9 participants 19 messages Nov 14, 2011 – Nov 16, 2011

Subsystems (6)
lore
Share & tools
lore

Mel Gorman

author
#0 lore raw

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.

[[email protected]: Rework fix to be suspend specific]
Reported-and-tested-by Colin Cross <[email protected]>
---
 mm/page_alloc.c |   22 ++++++++++++++++++++++
 1 files changed, 22 insertions(+), 0 deletions(-)
mm/page_alloc.c +22 -0
index 9dd443d..5402897 100644
@@ -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 */
#1 lore raw
Quoted 4 lines · Mel Gorman

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.

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>

author
#2 lore raw
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_FS was not set. The problem is that this would result in GFP_NOIO allocations 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.

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 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.

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_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.

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_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.

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
maintainer
#3 lore raw
In reply to Mel Gorman
Quoted 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_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.

Quoted 8 lines

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.

Fair enough.

Quoted 6 lines
mm/page_alloc.c +0 -0
index 9dd443d..5402897 100644

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 -0
 			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;

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;

_

author
#4 lore raw
Quoted 12 lines · Andrew Morton
Quoted 12 lines

<SNIP>

mm/page_alloc.c +3 -0
index 9dd443d..5402897 100644
@@ -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 -0
 			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;

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
author
#5 lore raw
Quoted 6 lines

<SNIP>
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.

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
index 3a76faf..033f55f 100644
@@ -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
index 2ee4040..4ace1e0 100644
@@ -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
index b1cd120..9520592 100644
@@ -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);
#6 lore raw
In reply to Mel Gorman
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_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.

[[email protected]: Rework fix to be suspend specific]
Reported-and-tested-by Colin Cross <[email protected]>

---
 mm/page_alloc.c |   22 ++++++++++++++++++++++

 1 files changed, 22 insertions(+), 0 deletions(-)
mm/page_alloc.c +15 -0
index 9dd443d..5402897 100644
@@ -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>
author
#7 lore raw
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_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.

[[email protected]: Rework fix to be suspend specific]
Reported-and-tested-by Colin Cross <[email protected]>
---
 mm/page_alloc.c |   22 ++++++++++++++++++++++
 1 files changed, 22 insertions(+), 0 deletions(-)
mm/page_alloc.c +22 -0
index 9dd443d..5402897 100644
@@ -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_disabled in 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_disabled with pm_is_going or 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>
#8 lore raw
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_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.

[[email protected]: Rework fix to be suspend specific]
Reported-and-tested-by Colin Cross <[email protected]>

---
 mm/page_alloc.c |   22 ++++++++++++++++++++++

 1 files changed, 22 insertions(+), 0 deletions(-)
mm/page_alloc.c +22 -0
index 9dd443d..5402897 100644
@@ -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_disabled in 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)) {
Quoted 8 lines

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?

You read my mind exactly!

I thought hibernation process is as follows,

freeze user processes
oom_disable
hibernate_preallocate_memory
freeze kernel processes(include kswapd)
pm_restrict_gfp_mask
swsusp_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
#9 lore raw
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_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?

You read my mind exactly!

I thought hibernation process is as follows,

freeze user processes
oom_disable
hibernate_preallocate_memory
freeze kernel processes(include kswapd)
pm_restrict_gfp_mask
swsusp_save

Quoted 7 lines

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?

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

#10 lore raw
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_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?

You read my mind exactly!

I thought hibernation process is as follows,

freeze user processes
oom_disable
hibernate_preallocate_memory
freeze kernel processes(include kswapd)
pm_restrict_gfp_mask
swsusp_save

Quoted 7 lines

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?

Quoted 6 lines

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.

Then, How about this?

[barrios@barrios-laptop linux-2.6]$ git diff

kernel/power/suspend.c +2 -0
index fdd4263..01aa9b5 100644
@@ -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
index 6e8ecb6..d8c31b7 100644
@@ -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
#11 lore raw
Quoted 26 lines · Minchan Kim

Then, How about this?

[barrios@barrios-laptop linux-2.6]$ git diff

kernel/power/suspend.c +2 -0
index fdd4263..01aa9b5 100644
@@ -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
index 6e8ecb6..d8c31b7 100644
@@ -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>

#12 lore raw
Quoted 14 lines · Pekka Enberg
Quoted 14 lines
kernel/power/suspend.c +2 -0
index fdd4263..01aa9b5 100644
@@ -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 -0
 Finish:
       pr_debug("PM: Finishing wakeup.\n");
mm/page_alloc.c +0 -0
index 6e8ecb6..d8c31b7 100644

mm/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 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.

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>

#13 lore raw
Quoted 44 lines · David Rientjes
Quoted 14 lines · Pekka Enberg
Quoted 14 lines
kernel/power/suspend.c +2 -0
index fdd4263..01aa9b5 100644
@@ -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 -0
 Finish:
       pr_debug("PM: Finishing wakeup.\n");
mm/page_alloc.c +0 -0
index 6e8ecb6..d8c31b7 100644

mm/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 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.

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_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.

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>

#14 lore raw
In reply to David Rientjes
Quoted 46 lines · David Rientjes
Quoted 14 lines · Pekka Enberg
Quoted 14 lines
kernel/power/suspend.c +2 -0
index fdd4263..01aa9b5 100644
@@ -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 -0
index 6e8ecb6..d8c31b7 100644

mm/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 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.

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_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.

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>
#15 lore raw
Quoted 53 lines · Minchan Kim
Quoted 44 lines
Quoted 14 lines
Quoted 14 lines
kernel/power/suspend.c +2 -0
index fdd4263..01aa9b5 100644
@@ -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 A pr_debug("PM: Finishing wakeup.\n");

mm/page_alloc.c +0 -0
index 6e8ecb6..d8c31b7 100644
@@ -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_NOFAIL allocations 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 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.

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_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. 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 like pm_suspended_storage. I think we can handle the issue with oom_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.

#16 lore raw
In reply to Mel Gorman
Quoted 30 lines · Mel Gorman
mm/page_alloc.c +15 -0
index 9dd443d..5402897 100644
@@ -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.

author
#17 lore raw
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_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.

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.

[[email protected]: Rework fix to be suspend specific]
[[email protected]: Move suspended device check to should_alloc_retry]
---
 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
index 3a76faf..033f55f 100644
@@ -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
index 9dd443d..a72cbf9 100644
@@ -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
index b1cd120..9520592 100644
@@ -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);
#18 lore raw
Quoted 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_NOFAIL if 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_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.

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.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.

Quoted 4 lines
[[email protected]: Rework fix to be suspend specific]
[[email protected]: Move suspended device check to should_alloc_retry]

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>