writeback livelock fixes v2
Subsystems (4)
cover writeback livelock fixes v2
Andrew,
Here are the writeback livelock fixes (patch 3, 4) from Jan Kara.
changes from v1:
- collect the various changelog and comment changes from email discussions
[PATCH 1/5] writeback: integrated background writeback work
[PATCH 2/5] writeback: trace wakeup event for background writeback [PATCH 3/5] writeback: stop background/kupdate works from livelocking other works [PATCH 4/5] writeback: avoid livelocking WB_SYNC_ALL writeback [PATCH 5/5] writeback: check skipped pages on WB_SYNC_ALL
fs/fs-writeback.c | 105 +++++++++++++++++++++++------ include/trace/events/writeback.h | 1 2 files changed, 87 insertions(+), 19 deletions(-)
Thanks,
Fengguang
PATCH 1/5 writeback: integrated background writeback work
From: Jan Kara <[email protected]>
Check whether background writeback is needed after finishing each work.
When bdi flusher thread finishes doing some work check whether any kind of background writeback needs to be done (either because dirty_background_ratio is exceeded or because we need to start flushing old inodes). If so, just do background write back.
This way, bdi_start_background_writeback() just needs to wake up the flusher thread. It will do background writeback as soon as there is no other work.
This is a preparatory patch for the next patch which stops background writeback as soon as there is other work to do.
--- fs/fs-writeback.c | 61 +++++++++++++++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 15 deletions(-)
fs/fs-writeback.c +46 -15
@@ -84,13 +84,9 @@ static inline struct inode *wb_inode(str return list_entry(head, struct inode, i_wb_list); } -static void bdi_queue_work (struct backing_dev_info *bdi, -struct wb_writeback_work *work) +/* Wakeup flusher thread or forker thread to fork it. Requires bdi->wb_lock. */ +static void bdi_wakeup_flusher (struct backing_dev_info *bdi) { -trace_writeback_queue (bdi, work); - -spin_lock_bh (&bdi->wb_lock); -list_add_tail (&work->list, &bdi->work_list);if (bdi->wb.task) {wake_up_process (bdi->wb.task); }else {
@@ -98,15 +94,26 @@ static void bdi_queue_work(struct backin* The bdi thread isn't there, wake up the forker thread which * will create and run it. */ -trace_writeback_nothread (bdi, work);wake_up_process (default_backing_dev_info.wb.task); } +} + +static void bdi_queue_work (struct backing_dev_info *bdi, +struct wb_writeback_work *work) +{ +trace_writeback_queue (bdi, work); + +spin_lock_bh (&bdi->wb_lock); +list_add_tail (&work->list, &bdi->work_list); +if (!bdi->wb.task) +trace_writeback_nothread (bdi, work); +bdi_wakeup_flusher (bdi);spin_unlock_bh (&bdi->wb_lock); }static void __bdi_start_writeback (struct backing_dev_info *bdi,long nr_pages, -bool range_cyclic,bool for_background) +bool range_cyclic) {struct wb_writeback_work *work;
@@ -126,7 +133,6 @@ __bdi_start_writeback(struct backing_dev work->sync_mode =WB_SYNC_NONE ; work->nr_pages = nr_pages; work->range_cyclic = range_cyclic; - work->for_background = for_background;bdi_queue_work (bdi, work); }
@@ -144,7 +150,7 @@ __bdi_start_writeback(struct backing_dev*/ void bdi_start_writeback (struct backing_dev_info *bdi,long nr_pages) { -__bdi_start_writeback (bdi, nr_pages, true, false); +__bdi_start_writeback (bdi, nr_pages, true); }/**
@@ -152,13 +158,20 @@ void bdi_start_writeback(struct backing_* @bdi: the backing device to write from * * Description: -* This does WB_SYNC_NONE background writeback. The IO is only -* started when this function returns, we make no guarentees on -* completion. Caller need not hold sb s_umount semaphore. +* This makes sure WB_SYNC_NONE background writeback happens. When +* this function returns, it is only guaranteed that for given BDI +* some IO is happening if we are over background dirty threshold. +* Caller need not hold sb s_umount semaphore. */ void bdi_start_background_writeback (struct backing_dev_info *bdi) { -__bdi_start_writeback (bdi,LONG_MAX , true, true); +/* +* We just wake up the flusher thread. It will perform background +* writeback as soon as there is no other work to do. +*/ +spin_lock_bh (&bdi->wb_lock); +bdi_wakeup_flusher (bdi); +spin_unlock_bh (&bdi->wb_lock); }/*
@@ -707,6 +720,23 @@ get_next_work_item(struct backing_dev_inreturn work; } +static long wb_check_background_flush (struct bdi_writeback *wb) +{ +if (over_bground_thresh ()) { + +struct wb_writeback_work work = { + .nr_pages =LONG_MAX , + .sync_mode =WB_SYNC_NONE , + .for_background =1 , + .range_cyclic =1 , + }; + +return wb_writeback (wb, &work); + } + +return 0 ; +} +static long wb_check_old_data_flush (struct bdi_writeback *wb) {unsigned long expired;
@@ -782,6 +812,7 @@ long wb_do_writeback(struct bdi_writebac* Check for periodic writeback, kupdated() style */ wrote +=wb_check_old_data_flush (wb); + wrote +=wb_check_background_flush (wb);clear_bit (BDI_writeback_running, &wb->bdi->state);return wrote;
@@ -868,7 +899,7 @@ void wakeup_flusher_threads(long nr_pagelist_for_each_entry_rcu (bdi, &bdi_list, bdi_list) {if (!bdi_has_dirty_io (bdi))continue ; -__bdi_start_writeback (bdi, nr_pages, false, false); +__bdi_start_writeback (bdi, nr_pages, false); }rcu_read_unlock (); }
--
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 policy in Canada: sign http://dissolvethecrtc.ca/
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
PATCH 2/5 writeback: trace wakeup event for background writeback
In reply to Wu FengguangThis tracks when balance_dirty_pages() tries to wakeup the flusher thread for background writeback (if it was not started already).
---
fs/fs-writeback.c | 1 + include/trace/events/writeback.h | 1 + 2 files changed, 2 insertions(+)
include/trace/events/writeback.h +1 -0
@@ -81,6 +81,7 @@ DEFINE_EVENT(writeback_class, name, \ TP_ARGS(bdi))DEFINE_WRITEBACK_EVENT (writeback_nowork); +DEFINE_WRITEBACK_EVENT (writeback_wake_background);DEFINE_WRITEBACK_EVENT (writeback_wake_thread);DEFINE_WRITEBACK_EVENT (writeback_wake_forker_thread);DEFINE_WRITEBACK_EVENT (writeback_bdi_register);
fs/fs-writeback.c +1 -0
@@ -169,6 +169,7 @@ void bdi_start_background_writeback(stru* We just wake up the flusher thread. It will perform background * writeback as soon as there is no other work to do. */ +trace_writeback_wake_background (bdi);spin_lock_bh (&bdi->wb_lock);bdi_wakeup_flusher (bdi);spin_unlock_bh (&bdi->wb_lock);
--
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 policy in Canada: sign http://dissolvethecrtc.ca/
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
PATCH 3/5 writeback: stop background/kupdate works from livelocking other works
In reply to Wu FengguangFrom: Jan Kara <[email protected]>
Background writeback is easily livelockable in a loop in wb_writeback() by a process continuously re-dirtying pages (or continuously appending to a file). This is in fact intended as the target of background writeback is to write dirty pages it can find as long as we are over dirty_background_threshold.
But the above behavior gets inconvenient at times because no other work queued in the flusher thread's queue gets processed. In particular, since e.g. sync(1) relies on flusher thread to do all the IO for it, sync(1) can hang forever waiting for flusher thread to do the work.
Generally, when a flusher thread has some work queued, someone submitted the work to achieve a goal more specific than what background writeback does. Moreover by working on the specific work, we also reduce amount of dirty pages which is exactly the target of background writeout. So it makes sense to give specific work a priority over a generic page cleaning.
Thus we interrupt background writeback if there is some other work to do. We return to the background writeback after completing all the queued work.
This may delay the writeback of expired inodes for a while, however the expired inodes will eventually be flushed to disk as long as the other works won't livelock.
--- fs/fs-writeback.c | 10 ++++++++++ 1 file changed, 10 insertions(+)
fs/fs-writeback.c +10 -0
@@ -651,6 +651,16 @@ static long wb_writeback(struct bdi_writbreak ;/* +* Background writeout and kupdate-style writeback may +* run forever. Stop them if there is other work to do +* so that e.g. sync can proceed. They'll be restarted +* after the other works are all done. +*/ +if ((work->for_background || work->for_kupdate) && + !list_empty (&wb->bdi->work_list)) +break ; + +/* * For background writeout, stop when we are below the * background dirty threshold */
--
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 policy in Canada: sign http://dissolvethecrtc.ca/
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Re: [PATCH 3/5] writeback: stop background/kupdate works from livelocking other works
Jan, the below comment is also updated, please double check.
Quoted 6 lines
fs/fs-writeback.c +3
-0/* +* Background writeout and kupdate-style writeback may +* run forever. Stop them if there is other work to do +* so that e.g. sync can proceed. They'll be restarted
fs/fs-writeback.c static long wb_writeback(struct bdi_writ · line 666
Quoted 10 lines
fs/fs-writeback.c +7
-0+* after the other works are all done. +*/ +if ((work->for_background || work->for_kupdate) && + !list_empty (&wb->bdi->work_list)) +break ; + +/* * For background writeout, stop when we are below the * background dirty threshold */
--
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 policy in Canada: sign http://dissolvethecrtc.ca/
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
Jan Kara
maintainerRe: [PATCH 3/5] writeback: stop background/kupdate works from livelocking other works
Quoted 1 line · Wu Fengguang
Jan, the below comment is also updated, please double check.
Thanks! The comment looks OK.
Quoted 6 lines
Quoted 6 lines
fs/fs-writeback.c +5
-0/* +* Background writeout and kupdate-style writeback may +* run forever. Stop them if there is other work to do +* so that e.g. sync can proceed. They'll be restarted +* after the other works are all done. +*/
Quoted 8 lines
fs/fs-writeback.c static long wb_writeback(struct bdi_writ · line 666
Quoted 8 lines
fs/fs-writeback.c +5
-0+if ((work->for_background || work->for_kupdate) && + !list_empty (&wb->bdi->work_list)) +break ; + +/* * For background writeout, stop when we are below the * background dirty threshold */
Honza
Signature · 9 lines
Jan Kara <[email protected]> SUSE Labs, CR -- 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 policy in Canada: sign http://dissolvethecrtc.ca/ Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
PATCH 4/5 writeback: avoid livelocking WB_SYNC_ALL writeback
In reply to Wu FengguangFrom: Jan Kara <[email protected]>
When wb_writeback() is called in WB_SYNC_ALL mode, work->nr_to_write is usually set to LONG_MAX. The logic in wb_writeback() then calls __writeback_inodes_sb() with nr_to_write == MAX_WRITEBACK_PAGES and we easily end up with non-positive nr_to_write after the function returns, if the inode has more than MAX_WRITEBACK_PAGES dirty pages at the moment.
When nr_to_write is <= 0 wb_writeback() decides we need another round of writeback but this is wrong in some cases! For example when a single large file is continuously dirtied, we would never finish syncing it because each pass would be able to write MAX_WRITEBACK_PAGES and inode dirty timestamp never gets updated (as inode is never completely clean). Thus __writeback_inodes_sb() would write the redirtied inode again and again.
Fix the issue by setting nr_to_write to LONG_MAX in WB_SYNC_ALL mode. We do not need nr_to_write in WB_SYNC_ALL mode anyway since write_cache_pages() does livelock avoidance using page tagging in WB_SYNC_ALL mode.
This makes wb_writeback() call __writeback_inodes_sb() only once on WB_SYNC_ALL. The latter function won't livelock because it works on
- a finite set of files by doing queue_io() once at the beginning
- a finite set of pages by PAGECACHE_TAG_TOWRITE page tagging
After this patch, program from http://lkml.org/lkml/2010/10/24/154 is no longer able to stall sync forever.
--- fs/fs-writeback.c | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-)
Fengguang, I've been testing with those writeback fixes you reposted
a few days ago and I've been able to still reproduce livelocks with Jan Engelhard's test case. Using writeback tracing I've tracked the problem to the above and with this patch, sync finishes OK (well, it still takes about 15 minutes but that's about expected time given the throughput I see to the disk - the test case randomly dirties pages in a huge file). So could you please add this patch to the previous two send them to Jens for inclusion?
fs/fs-writeback.c +23 -4
@@ -630,6 +630,7 @@ static long wb_writeback(struct bdi_writ };unsigned long oldest_jif;long wrote =0 ; +long write_chunk;struct inode *inode;if (wbc.for_kupdate) {
@@ -642,6 +643,24 @@ static long wb_writeback(struct bdi_writ wbc.range_end =LLONG_MAX ; } +/* +* WB_SYNC_ALL mode does livelock avoidance by syncing dirty +* inodes/pages in one big loop. Setting wbc.nr_to_write=LONG_MAX +* here avoids calling into writeback_inodes_wb() more than once. +* +* The intended call sequence for WB_SYNC_ALL writeback is: +* +* wb_writeback() +* __writeback_inodes_sb() <== called only once +* write_cache_pages() <== called once for each inode +* (quickly) tag currently dirty pages +* (maybe slowly) sync all tagged pages +*/ +if (wbc.sync_mode ==WB_SYNC_NONE ) + write_chunk =MAX_WRITEBACK_PAGES ; +else + write_chunk =LONG_MAX ; + wbc.wb_start = jiffies;/* livelock avoidance */ for (;;) {/*
@@ -668,7 +687,7 @@ static long wb_writeback(struct bdi_writbreak ; wbc.more_io =0 ; - wbc.nr_to_write =MAX_WRITEBACK_PAGES ; + wbc.nr_to_write = write_chunk; wbc.pages_skipped =0 ;trace_wbc_writeback_start (&wbc, wb->bdi);
@@ -678,8 +697,8 @@ static long wb_writeback(struct bdi_writwriteback_inodes_wb (wb, &wbc);trace_wbc_writeback_written (&wbc, wb->bdi); - work->nr_pages -=MAX_WRITEBACK_PAGES - wbc.nr_to_write; - wrote +=MAX_WRITEBACK_PAGES - wbc.nr_to_write; + work->nr_pages -= write_chunk - wbc.nr_to_write; + wrote += write_chunk - wbc.nr_to_write;/* * If we consumed everything, see if we have more
@@ -694,7 +713,7 @@ static long wb_writeback(struct bdi_writ/* * Did we write something? Try for more */ -if (wbc.nr_to_write <MAX_WRITEBACK_PAGES ) +if (wbc.nr_to_write < write_chunk)continue ;/* * Nothing written. Wait for some inode to
--
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 policy in Canada: sign http://dissolvethecrtc.ca/
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
PATCH 5/5 writeback: check skipped pages on WB_SYNC_ALL
In reply to Wu FengguangIn WB_SYNC_ALL mode, filesystems are not expected to skip dirty pages on temporal lock contentions or non fatal errors, otherwise sync() will return without actually syncing the skipped pages. Add a check to catch possible redirty_page_for_writepage() callers that violate this expectation.
I'd recommend to keep this check in -mm tree for some time and fixup the possible warnings before pushing it to upstream.
If some FS triggers this warning and it's non-trivial to fix the FS, we'll have to work out a sync retry scheme for skipped pages.
--- fs/fs-writeback.c | 6 ++++++ 1 file changed, 6 insertions(+)
fs/fs-writeback.c +6 -0
@@ -527,6 +527,12 @@ static int writeback_sb_inodes(struct su* buffers. Skip this inode for now. */ redirty_tail (inode); +/* +* There's no logic to retry skipped pages for sync(), +* filesystems are assumed not to skip dirty pages on +* temporal lock contentions or non fatal errors. +*/ +WARN_ON_ONCE (wbc->sync_mode ==WB_SYNC_ALL ); }spin_unlock (&inode_lock);iput (inode);
--
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 policy in Canada: sign http://dissolvethecrtc.ca/
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>