Skip to content

Commit

Permalink
migrate_pages: avoid blocking for IO in MIGRATE_SYNC_LIGHT
Browse files Browse the repository at this point in the history
The MIGRATE_SYNC_LIGHT mode is intended to block for things that will
finish quickly but not for things that will take a long time.  Exactly how
long is too long is not well defined, but waits of tens of milliseconds is
likely non-ideal.

When putting a Chromebook under memory pressure (opening over 90 tabs on a
4GB machine) it was fairly easy to see delays waiting for some locks in
the kcompactd code path of > 100 ms.  While the laptop wasn't amazingly
usable in this state, it was still limping along and this state isn't
something artificial.  Sometimes we simply end up with a lot of memory
pressure.

Putting the same Chromebook under memory pressure while it was running
Android apps (though not stressing them) showed a much worse result (NOTE:
this was on a older kernel but the codepaths here are similar).  Android
apps on ChromeOS currently run from a 128K-block, zlib-compressed,
loopback-mounted squashfs disk.  If we get a page fault from something
backed by the squashfs filesystem we could end up holding a folio lock
while reading enough from disk to decompress 128K (and then decompressing
it using the somewhat slow zlib algorithms).  That reading goes through
the ext4 subsystem (because it's a loopback mount) before eventually
ending up in the block subsystem.  This extra jaunt adds extra overhead. 
Without much work I could see cases where we ended up blocked on a folio
lock for over a second.  With more extreme memory pressure I could see up
to 25 seconds.

We considered adding a timeout in the case of MIGRATE_SYNC_LIGHT for the
two locks that were seen to be slow [1] and that generated much
discussion.  After discussion, it was decided that we should avoid waiting
for the two locks during MIGRATE_SYNC_LIGHT if they were being held for
IO.  We'll continue with the unbounded wait for the more full SYNC modes.

With this change, I couldn't see any slow waits on these locks with my
previous testcases.

NOTE: The reason I stated digging into this originally isn't because some
benchmark had gone awry, but because we've received in-the-field crash
reports where we have a hung task waiting on the page lock (which is the
equivalent code path on old kernels).  While the root cause of those
crashes is likely unrelated and won't be fixed by this patch, analyzing
those crash reports did point out these very long waits seemed like
something good to fix.  With this patch we should no longer hang waiting
on these locks, but presumably the system will still be in a bad shape and
hang somewhere else.

[1] https://lore.kernel.org/r/20230421151135.v2.1.I2b71e11264c5c214bc59744b9e13e4c353bc5714@changeid

Link: https://lkml.kernel.org/r/20230428135414.v3.1.Ia86ccac02a303154a0b8bc60567e7a95d34c96d3@changeid
Signed-off-by: Douglas Anderson <dianders@chromium.org>
Suggested-by: Matthew Wilcox <willy@infradead.org>
Reviewed-by: Matthew Wilcox (Oracle) <willy@infradead.org>
Acked-by: Mel Gorman <mgorman@techsingularity.net>
Cc: Hillf Danton <hdanton@sina.com>
Cc: Gao Xiang <hsiangkao@linux.alibaba.com>
Cc: Alexander Viro <viro@zeniv.linux.org.uk>
Cc: Christian Brauner <brauner@kernel.org>
Cc: Gao Xiang <hsiangkao@linux.alibaba.com>
Cc: Huang Ying <ying.huang@intel.com>
Cc: Vlastimil Babka <vbabka@suse.cz>
Cc: Yu Zhao <yuzhao@google.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
  • Loading branch information
dianders authored and akpm00 committed Jun 9, 2023
1 parent f785a8f commit 4bb6dc7
Showing 1 changed file with 26 additions and 23 deletions.
49 changes: 26 additions & 23 deletions mm/migrate.c
Original file line number Diff line number Diff line change
Expand Up @@ -692,37 +692,32 @@ static bool buffer_migrate_lock_buffers(struct buffer_head *head,
enum migrate_mode mode)
{
struct buffer_head *bh = head;
struct buffer_head *failed_bh;

/* Simple case, sync compaction */
if (mode != MIGRATE_ASYNC) {
do {
lock_buffer(bh);
bh = bh->b_this_page;

} while (bh != head);

return true;
}

/* async case, we cannot block on lock_buffer so use trylock_buffer */
do {
if (!trylock_buffer(bh)) {
/*
* We failed to lock the buffer and cannot stall in
* async migration. Release the taken locks
*/
struct buffer_head *failed_bh = bh;
bh = head;
while (bh != failed_bh) {
unlock_buffer(bh);
bh = bh->b_this_page;
}
return false;
if (mode == MIGRATE_ASYNC)
goto unlock;
if (mode == MIGRATE_SYNC_LIGHT && !buffer_uptodate(bh))
goto unlock;
lock_buffer(bh);
}

bh = bh->b_this_page;
} while (bh != head);

return true;

unlock:
/* We failed to lock the buffer and cannot stall. */
failed_bh = bh;
bh = head;
while (bh != failed_bh) {
unlock_buffer(bh);
bh = bh->b_this_page;
}

return false;
}

static int __buffer_migrate_folio(struct address_space *mapping,
Expand Down Expand Up @@ -1156,6 +1151,14 @@ static int migrate_folio_unmap(new_page_t get_new_page, free_page_t put_new_page
if (current->flags & PF_MEMALLOC)
goto out;

/*
* In "light" mode, we can wait for transient locks (eg
* inserting a page into the page table), but it's not
* worth waiting for I/O.
*/
if (mode == MIGRATE_SYNC_LIGHT && !folio_test_uptodate(src))
goto out;

folio_lock(src);
}
locked = true;
Expand Down

0 comments on commit 4bb6dc7

Please sign in to comment.