writeback: Don't wait for completion in writeback_inodes_sb_nr
PATCH 1/1 writeback: Don't wait for completion in writeback_inodes_sb_nr
Contrary to the comment block atop writeback_inodes_sb_nr(), we *were* calling
wait_for_completion(&done);
which should not be done, as this is not called for data integrity sync.
--- fs/fs-writeback.c | 3 --- 1 files changed, 0 insertions(+), 3 deletions(-)
fs/fs-writeback.c +0 -3
@@ -1186,17 +1186,14 @@ static void wait_sb_inodes(struct super_block *sb)*/ void writeback_inodes_sb_nr (struct super_block *sb,unsigned long nr) { -DECLARE_COMPLETION_ONSTACK (done);struct wb_writeback_work work = { .sb = sb, .sync_mode =WB_SYNC_NONE , - .done = &done, .nr_pages = nr, };WARN_ON (!rwsem_is_locked (&sb->s_umount));bdi_queue_work (sb->s_bdi, &work); -wait_for_completion (&done); }EXPORT_SYMBOL (writeback_inodes_sb_nr);
Signature · 1 line
1.7.3.1
Quoted 9 lines · Curt Wohlgemuth
Contrary to the comment block atop
writeback_inodes_sb_nr(), we *were* callingwait_for_completion(&done);which should not be done, as this is not called for data integrity sync.
Signed-off-by Curt Wohlgemuth <[email protected]>
The comment says it does not wait for IO to be -completed-.
The function as implemented waits for IO to be *submitted*.
This provides the callers with same blocking semantics (i.e. request queue full) as if the caller submitted the IO themselves. The code that uses this function rely on this blocking to delay the next set of operations they do until after IO has been started, so removing the completion will change their behaviour significantly.
Cheers,
Dave.
Signature · 2 lines
Dave Chinner [email protected]
Hi Dave:
Thanks for the response.
Quoted 13 lines · Dave Chinner
Quoted 9 lines · Curt Wohlgemuth
Contrary to the comment block atop
writeback_inodes_sb_nr(), we *were* calling
wait_for_completion(&done);which should not be done, as this is not called for data integrity sync.
Signed-off-by Curt Wohlgemuth <[email protected]>The comment says it does not wait for IO to be -completed-.
Quoted 7 lines
The function as implemented waits for IO to be *submitted*.
This provides the callers with same blocking semantics (i.e. request queue full) as if the caller submitted the IO themselves. The code that uses this function rely on this blocking to delay the next set of operations they do until after IO has been started, so removing the completion will change their behaviour significantly.
I don't quite understand this. It's true that all IO done as a result of calling wb_writeback() on this work item won't finish before the completion takes place, but sending all those pages in flight *will* take place. And that's a lot of time. To wait on this before we then call sync_inodes_sb(), and do it all over again, seems odd at best.
Pre-2.6.35 kernels would start non-integrity sync writeback and immediately return, which would seem like a reasonable "prefetch-y" thing to do, considering it's going to be immediately followed by a data integrity sync writeback operation.
The post 2.6.35 semantics are fine; but then I don't understand why we do both a __sync_filesystem(0) followed by a __sync_filesystem(1) (in the case of sync(2)). It doesn't seem to be any safer or more correct to me; why not just do the data integrity sync writeback and call it a day?
Thanks,
Curt
Quoted 8 lines
Cheers,
Dave.
--
Dave Chinner
[email protected]
--
To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Quoted 10 lines · Curt Wohlgemuth
I don't quite understand this. It's true that all IO done as a result of calling
wb_writeback()on this work item won't finish before the completion takes place, but sending all those pages in flight *will* take place. And that's a lot of time. To wait on this before we then callsync_inodes_sb(), and do it all over again, seems odd at best.Pre-2.6.35 kernels would start non-integrity sync writeback and immediately return, which would seem like a reasonable "prefetch-y" thing to do, considering it's going to be immediately followed by a data integrity sync writeback operation.
The only old kernel I have around is 2.6.16, so looking at that one for old semantics:
- it first does a wakeup_pdflush() which does a truely asynchronous writeback of everything in the system. This is replaced with a wakeup_flusher_threads(0) in 3.0, which has pretty similar sematics.
- after that things change a bit as we reordered sync quite a bit too
a) improve data integrity by properly ordering the steps of the sync and b) shared code between the global sync and per-fs sync as used by umount, sys_syncfs and a few other callers. But both do an semi-sync pass and a sync pass on per-sb writeback. For the old code it's done from the calling threads context, while the next code moved it to the flushers thread, but still waiting for it with the completion. No major change in semantics as far as I can see.
The idea of the async pass is to start writeback on as many as possible pages before actively having to wait on them. I'd agree with your assessment that the writeback_inodes_sb might not really help all that much - given that a lot of the waiting might not actually be for I/O completion but e.g. for the block level throtteling (or maybe cgroups in your case?).
For sys_sync I'm pretty sure we could simply remove the writeback_inodes_sb call and get just as good if not better performance, but we'd still need a solution for the other sync_filesystem callers, assuming the first write actually benefits them. Maybe you can run some sys_syncfs microbenchmarks to check it? Note that doing multiple passes generally doesn't actually help live-lock avoidance either, but wu has recently done some work in that area.
Another thing we've discussed a while ago is changing sync_inodes_sb to the writeback or at least the waiting back in the calling threads context to not block the flushers threads from getting real work done.
Jan Kara
reviewerQuoted 34 lines · Christoph Hellwig
Quoted 10 lines · Curt Wohlgemuth
I don't quite understand this. It's true that all IO done as a result of calling
wb_writeback()on this work item won't finish before the completion takes place, but sending all those pages in flight *will* take place. And that's a lot of time. To wait on this before we then callsync_inodes_sb(), and do it all over again, seems odd at best.Pre-2.6.35 kernels would start non-integrity sync writeback and immediately return, which would seem like a reasonable "prefetch-y" thing to do, considering it's going to be immediately followed by a data integrity sync writeback operation.
The only old kernel I have around is 2.6.16, so looking at that one for old semantics:
- it first does a
wakeup_pdflush()which does a truely asynchronous writeback of everything in the system. This is replaced with awakeup_flusher_threads(0)in 3.0, which has pretty similar sematics.
- after that things change a bit as we reordered sync quite a bit too
a) improve data integrity by properly ordering the steps of the sync and b) shared code between the global sync and per-fs sync as used by umount,sys_syncfsand a few other callers. But both do an semi-sync pass and a sync pass on per-sb writeback. For the old code it's done from the calling threads context, while the next code moved it to the flushers thread, but still waiting for it with the completion. No major change in semantics as far as I can see.The idea of the async pass is to start writeback on as many as possible pages before actively having to wait on them. I'd agree with your assessment that the
writeback_inodes_sbmight not really help all that much - given that a lot of the waiting might not actually be for I/O completion but e.g. for the block level throtteling (or maybe cgroups in your case?).
Quoted 2 lines
For
sys_syncI'm pretty sure we could simply remove thewriteback_inodes_sbcall and get just as good if not better performance,
Actually, it won't with current code. Because WB_SYNC_ALL writeback
currently has the peculiarity that it looks like:
for all inodes {
write all inode data
wait for inode data
}
while to achieve good performance we actually need something like
for all inodes
write all inode data
for all inodes
wait for inode data
It makes a difference in an order of magnitude when there are lots of smallish files - SLES had a bug like this so I know from user reports ;)
But with current inode list handling it will be hard to find all inodes that need to be waited for which I guess is the reason why it is done as it is done.
Quoted 9 lines
but we'd still need a solution for the other
sync_filesystemcallers, assuming the first write actually benefits them. Maybe you can run somesys_syncfsmicrobenchmarks to check it? Note that doing multiple passes generally doesn't actually help live-lock avoidance either, but wu has recently done some work in that area.Another thing we've discussed a while ago is changing
sync_inodes_sbto the writeback or at least the waiting back in the calling threads context to not block the flushers threads from getting real work done.
You mean that sync(1) would actually write the data itself? It would
certainly make some things simpler but it has its problems as well - for example sync racing with flusher thread writing back inodes can create rather bad IO pattern...
Honza
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Quoted 15 lines · Jan Kara
Quoted 2 lines
For
sys_syncI'm pretty sure we could simply remove thewriteback_inodes_sbcall and get just as good if not better performance,Actually, it won't with current code. Because
WB_SYNC_ALLwriteback
currently has the peculiarity that it looks like:
for all inodes {
write all inode data
wait for inode data
}
while to achieve good performance we actually need something like
for all inodes
write all inode data
for all inodes
wait for inode data
It makes a difference in an order of magnitude when there are lots of smallish files - SLES had a bug like this so I know from user reports ;)
I don't think that's true. The WB_SYNC_ALL writeback is done using sync_inodes_sb, which operates as:
for all dirty inodes in bdi:
if inode belongs to sb
write all inode data
for all inodes in sb:
wait for inode data
we still do that in a big for each sb loop, though.
Quoted 4 lines
You mean that sync(1) would actually write the data itself? It would
certainly make some things simpler but it has its problems as well - for example sync racing with flusher thread writing back inodes can create rather bad IO pattern...
Only the second pass. The idea is that we first try to use the flusher threads for good I/O patterns, but if we can't get that to work only block the caller and not everyone. But that's just an idea so far, it would need serious benchmark. And despite what I claimed before we actually do the wait in the caller context already anyway, which already gives you the easy part of the above effect.
Jan Kara
reviewerQuoted 20 lines · Christoph Hellwig
Quoted 15 lines · Jan Kara
Quoted 2 lines
For
sys_syncI'm pretty sure we could simply remove thewriteback_inodes_sbcall and get just as good if not better performance,Actually, it won't with current code. Because
WB_SYNC_ALLwriteback
currently has the peculiarity that it looks like:
for all inodes {
write all inode data
wait for inode data
}
while to achieve good performance we actually need something like
for all inodes
write all inode data
for all inodes
wait for inode data
It makes a difference in an order of magnitude when there are lots of smallish files - SLES had a bug like this so I know from user reports ;)I don't think that's true. The
WB_SYNC_ALLwriteback is done usingsync_inodes_sb, which operates as:
Quoted 8 lines
for all dirty inodes in bdi:
if inode belongs to sb
write all inode datafor all inodes in sb:
wait for inode datawe still do that in a big for each sb loop, though.
True but writeback_single_inode() has in it:
if (wbc->sync_mode == WB_SYNC_ALL) {
int err = filemap_fdatawait(mapping);
if (ret == 0)
ret = err;
} So we end up waiting much earlier. Probably we should remove this wait
but that will need some audit I guess.
Quoted 11 lines
Quoted 4 lines
You mean that sync(1) would actually write the data itself? It would
certainly make some things simpler but it has its problems as well - for example sync racing with flusher thread writing back inodes can create rather bad IO pattern...Only the second pass. The idea is that we first try to use the flusher threads for good I/O patterns, but if we can't get that to work only block the caller and not everyone. But that's just an idea so far, it would need serious benchmark. And despite what I claimed before we actually do the wait in the caller context already anyway, which already gives you the easy part of the above effect.
Modulo the writeback_single_inode() wait. But if that is dealt with I
agree.
Honza
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Quoted 8 lines · Jan Kara
True but
writeback_single_inode()has in it:if (wbc->sync_mode == WB_SYNC_ALL) { int err = filemap_fdatawait(mapping); if (ret == 0) ret = err; }So we end up waiting much earlier. Probably we should remove this wait
but that will need some audit I guess.
Uhh, indeed. We'll need this wait for things like sync_inode, though.
Jan Kara
reviewerQuoted 11 lines · Christoph Hellwig
Quoted 8 lines · Jan Kara
True but
writeback_single_inode()has in it:if (wbc->sync_mode == WB_SYNC_ALL) { int err = filemap_fdatawait(mapping); if (ret == 0) ret = err; }So we end up waiting much earlier. Probably we should remove this wait
but that will need some audit I guess.Uhh, indeed. We'll need this wait for things like
sync_inode, though.
Yes. Actually, specifically for filesystems like XFS which update inode
after IO completion we would need more passes to be efficient and correct:
for all inodes
fdatawrite
for all inodes
fdatawait
for all inodes
write_inode
for all inodes
wait for inode IO
But maybe this could be handled by having new WB_SYNC_ mode indicating
that writeback_single_inode() should not bother waiting (thus we'd really end up waiting in sync_inodes_sb()) and then XFS and other filesystems that need it would writeout inodes in their sync_fs() implementation (possibly using a generic helper)?
Honza
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Quoted 11 lines · Jan Kara
Yes. Actually, specifically for filesystems like XFS which update inode
after IO completion we would need more passes to be efficient and correct:
for all inodes
fdatawrite
for all inodes
fdatawait
for all inodes
write_inode
for all inodes
wait for inode IO
Quoted 5 lines
But maybe this could be handled by having new
WB_SYNC_mode indicating
thatwriteback_single_inode()should not bother waiting (thus we'd really end up waiting insync_inodes_sb()) and then XFS and other filesystems that need it would writeout inodes in theirsync_fs()implementation (possibly using a generic helper)?
We do very little in write_inode these days. Basically just log the inode size and timestamp sized into the in-memory log. The actual writeout of the log happens in ->sync_fs already.
Hi Jan:
Quoted 29 lines · Jan Kara
Quoted 20 lines · Christoph Hellwig
Quoted 15 lines · Jan Kara
Quoted 2 lines
For
sys_syncI'm pretty sure we could simply remove thewriteback_inodes_sbcall and get just as good if not better performance,Actually, it won't with current code. Because
WB_SYNC_ALLwriteback currently has the peculiarity that it looks like:
for all inodes {
write all inode data
wait for inode data
}
while to achieve good performance we actually need something like for all inodes
write all inode data
for all inodes
wait for inode data
It makes a difference in an order of magnitude when there are lots of smallish files - SLES had a bug like this so I know from user reports ;)I don't think that's true. The
WB_SYNC_ALLwriteback is done usingsync_inodes_sb, which operates as:Quoted 8 lines
for all dirty inodes in bdi:
if inode belongs to sb
write all inode datafor all inodes in sb:
wait for inode datawe still do that in a big for each sb loop, though.
Quoted 8 lines
True but
writeback_single_inode()has in it:
if (wbc->sync_mode==WB_SYNC_ALL) {
int err =filemap_fdatawait(mapping);
if (ret == 0)
ret = err;
}
So we end up waiting much earlier. Probably we should remove this wait but that will need some audit I guess.
So today for WB_SYNC_ALL from sync_inodes_sb(), we do:
- queue a work item; this will
- write all dirty inodes in a sb
- write one inode's pages
- wait on all inode's pages
- wait for the work item
- wait on all inodes in the sb (wait_sb_inodes)
I guess the point of wait_sb_inodes() is to wait on all inodes that were written in a previous writeback pass.
One other issue I have with sync as it's structured is that we don't do a WB_SYNC_ALL pass on any inode that's only associated with a block device, and not on a mounted filesystem. Blockdev mounts are pseudo-mounts, and are explicitly skipped in __sync_filesystem(). So if you've written directly to a block device and do a sync, the only pass over the pages for this inode are via the wakeup_flusher_threads() -- which operates on a BDI, regardless of the superblock, and uses WB_SYNC_NONE.
All the sync_filesystem() calls are per-sb, not per-BDI, and they'll exclude pseudo-superblocks.
I've seen cases in our modified kernels here at Google in which lilo/shutdown failed because of a lack of WB_SYNC_ALL writeback for /dev/sda (though I haven't been able to come up with a consistent test case, nor reproduce this on an upstream kernel).
Thanks,
Curt
Quoted 20 lines
Quoted 6 lines
Quoted 5 lines
You mean that sync(1) would actually write the data itself? It would certainly make some things simpler but it has its problems as well - for example sync racing with flusher thread writing back inodes can create rather bad IO pattern...
Quoted 6 lines
Only the second pass. The idea is that we first try to use the flusher threads for good I/O patterns, but if we can't get that to work only block the caller and not everyone. But that's just an idea so far, it would need serious benchmark. And despite what I claimed before we actually do the wait in the caller context already anyway, which already gives you the easy part of the above effect.
Modulo the
writeback_single_inode()wait. But if that is dealt with I agree.Honza --
Jan Kara <[email protected]>
SUSE Labs, CR
Quoted 11 lines · Curt Wohlgemuth
One other issue I have with sync as it's structured is that we don't do a
WB_SYNC_ALLpass on any inode that's only associated with a block device, and not on a mounted filesystem. Blockdev mounts are pseudo-mounts, and are explicitly skipped in__sync_filesystem(). So if you've written directly to a block device and do a sync, the only pass over the pages for this inode are via thewakeup_flusher_threads()-- which operates on a BDI, regardless of the superblock, and usesWB_SYNC_NONE.All the
sync_filesystem()calls are per-sb, not per-BDI, and they'll exclude pseudo-superblocks.
Interesting. I think that's actually correct by the traditional defintion of sync, but not really useful. I also doubt it's intentional.
Quoted 4 lines
I've seen cases in our modified kernels here at Google in which lilo/shutdown failed because of a lack of
WB_SYNC_ALLwriteback for/dev/sda(though I haven't been able to come up with a consistent test case, nor reproduce this on an upstream kernel).
lilo really should do an fsync of the partition it installed itself into. That's won't only fix this issue, but also has a lot less impact on the rest of the system.
Jan Kara
reviewerHi Curt,
Quoted 47 lines · Curt Wohlgemuth
Quoted 29 lines · Jan Kara
Quoted 20 lines · Christoph Hellwig
Quoted 15 lines · Jan Kara
Quoted 2 lines
For
sys_syncI'm pretty sure we could simply remove thewriteback_inodes_sbcall and get just as good if not better performance,Actually, it won't with current code. Because
WB_SYNC_ALLwriteback currently has the peculiarity that it looks like:
for all inodes {
write all inode data
wait for inode data
}
while to achieve good performance we actually need something like for all inodes
write all inode data
for all inodes
wait for inode data
It makes a difference in an order of magnitude when there are lots of smallish files - SLES had a bug like this so I know from user reports ;)I don't think that's true. The
WB_SYNC_ALLwriteback is done usingsync_inodes_sb, which operates as:Quoted 8 lines
for all dirty inodes in bdi:
if inode belongs to sb
write all inode datafor all inodes in sb:
wait for inode datawe still do that in a big for each sb loop, though.
Quoted 8 lines
True but
writeback_single_inode()has in it:
if (wbc->sync_mode==WB_SYNC_ALL) {
int err =filemap_fdatawait(mapping);
if (ret == 0)
ret = err;
}
So we end up waiting much earlier. Probably we should remove this wait but that will need some audit I guess.So today for
WB_SYNC_ALLfromsync_inodes_sb(), we do:
- queue a work item; this will
- write all dirty inodes in a sb
- write one inode's pages
- wait on all inode's pages
- wait for the work item
- wait on all inodes in the sb (wait_sb_inodes)
Quoted 2 lines
I guess the point of
wait_sb_inodes()is to wait on all inodes that were written in a previous writeback pass.
Yes. As we discussed with Christoph, waiting for each inode could be
avoided in principle but we have to be careful not to break data consistency guarantees in some other path calling writeback_single_inode()...
Quoted 9 lines
One other issue I have with sync as it's structured is that we don't do a
WB_SYNC_ALLpass on any inode that's only associated with a block device, and not on a mounted filesystem. Blockdev mounts are pseudo-mounts, and are explicitly skipped in__sync_filesystem(). So if you've written directly to a block device and do a sync, the only pass over the pages for this inode are via thewakeup_flusher_threads()-- which operates on a BDI, regardless of the superblock, and usesWB_SYNC_NONE.
Quoted 7 lines
All the
sync_filesystem()calls are per-sb, not per-BDI, and they'll exclude pseudo-superblocks.I've seen cases in our modified kernels here at Google in which lilo/shutdown failed because of a lack of
WB_SYNC_ALLwriteback for/dev/sda(though I haven't been able to come up with a consistent test case, nor reproduce this on an upstream kernel).
Ho hum, I wonder what would be the cleanest way to fix this. I guess we
could make an exception for 'bdev' superblock in the code but it's ugly.
Honza
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Hi Jan:
Quoted 69 lines · Jan Kara
Hi Curt,
Quoted 47 lines · Curt Wohlgemuth
Quoted 29 lines · Jan Kara
Quoted 20 lines · Christoph Hellwig
Quoted 15 lines · Jan Kara
Quoted 2 lines
For
sys_syncI'm pretty sure we could simply remove thewriteback_inodes_sbcall and get just as good if not better performance,Actually, it won't with current code. Because
WB_SYNC_ALLwriteback currently has the peculiarity that it looks like:
for all inodes {
write all inode data
wait for inode data
}
while to achieve good performance we actually need something like for all inodes
write all inode data
for all inodes
wait for inode data
It makes a difference in an order of magnitude when there are lots of smallish files - SLES had a bug like this so I know from user reports ;)I don't think that's true. The
WB_SYNC_ALLwriteback is done usingsync_inodes_sb, which operates as:Quoted 8 lines
for all dirty inodes in bdi:
if inode belongs to sb
write all inode datafor all inodes in sb:
wait for inode datawe still do that in a big for each sb loop, though.
Quoted 8 lines
True but
writeback_single_inode()has in it:
if (wbc->sync_mode==WB_SYNC_ALL) {
int err =filemap_fdatawait(mapping);
if (ret == 0)
ret = err;
}
So we end up waiting much earlier. Probably we should remove this wait but that will need some audit I guess.So today for
WB_SYNC_ALLfromsync_inodes_sb(), we do:
- queue a work item; this will
- write all dirty inodes in a sb
- write one inode's pages
- wait on all inode's pages
- wait for the work item
- wait on all inodes in the sb (wait_sb_inodes)Quoted 2 lines
I guess the point of
wait_sb_inodes()is to wait on all inodes that were written in a previous writeback pass.Yes. As we discussed with Christoph, waiting for each inode could be avoided in principle but we have to be careful not to break data consistency guarantees in some other path calling
writeback_single_inode()...Quoted 12 lines
One other issue I have with sync as it's structured is that we don't do a
WB_SYNC_ALLpass on any inode that's only associated with a block device, and not on a mounted filesystem. Blockdev mounts are pseudo-mounts, and are explicitly skipped in__sync_filesystem(). So if you've written directly to a block device and do a sync, the only pass over the pages for this inode are via thewakeup_flusher_threads()-- which operates on a BDI, regardless of the superblock, and usesWB_SYNC_NONE.All the
sync_filesystem()calls are per-sb, not per-BDI, and they'll exclude pseudo-superblocks.
Quoted 6 lines
Quoted 4 lines
I've seen cases in our modified kernels here at Google in which lilo/shutdown failed because of a lack of
WB_SYNC_ALLwriteback for/dev/sda(though I haven't been able to come up with a consistent test case, nor reproduce this on an upstream kernel).Ho hum, I wonder what would be the cleanest way to fix this. I guess we could make an exception for 'bdev' superblock in the code but it's ugly.
Yes, it's ugly.
You could declare that sync(2) is just doing it's job, that it's not designed to write dirty pages from devices that don't have filesystems mounted on them; that's what Christoph seems to be saying, and it's what the man pages for sync(2) that I've seen say as well. But everybody I've talked to about this is surprised, so you could declare it a bug as well :-) .
It seems to me that sys_sync *could* just dispense with iterating over the superblocks, and just iterate over the BDIs, just as wakeup_flusher_threads() does. I.e., just do two passes over all BDIs -- one with WB_SYNC_NONE, and one with WB_SYNC_ALL. Well, not quite. It would still have to go to each SB and call the quota_sync and sync_fs callbacks, but those should be cheap.
And yes, this would make sys_sync and sys_syncfs more different than they are today.
Thanks,
Curt
Quoted 5 lines
Honza --
Jan Kara <[email protected]>
SUSE Labs, CR
Jan Kara
reviewerQuoted 28 lines · Curt Wohlgemuth
Quoted 12 lines
Quoted 12 lines
One other issue I have with sync as it's structured is that we don't do a
WB_SYNC_ALLpass on any inode that's only associated with a block device, and not on a mounted filesystem. Blockdev mounts are pseudo-mounts, and are explicitly skipped in__sync_filesystem(). So if you've written directly to a block device and do a sync, the only pass over the pages for this inode are via thewakeup_flusher_threads()-- which operates on a BDI, regardless of the superblock, and usesWB_SYNC_NONE.All the
sync_filesystem()calls are per-sb, not per-BDI, and they'll exclude pseudo-superblocks.Quoted 6 lines
Quoted 4 lines
I've seen cases in our modified kernels here at Google in which lilo/shutdown failed because of a lack of
WB_SYNC_ALLwriteback for/dev/sda(though I haven't been able to come up with a consistent test case, nor reproduce this on an upstream kernel).Ho hum, I wonder what would be the cleanest way to fix this. I guess we could make an exception for 'bdev' superblock in the code but it's ugly.
Yes, it's ugly.
You could declare that sync(2) is just doing it's job, that it's not designed to write dirty pages from devices that don't have filesystems mounted on them; that's what Christoph seems to be saying, and it's what the man pages for sync(2) that I've seen say as well. But everybody I've talked to about this is surprised, so you could declare it a bug as well :-) .
Quoted 6 lines
It seems to me that
sys_sync*could* just dispense with iterating over the superblocks, and just iterate over the BDIs, just aswakeup_flusher_threads()does. I.e., just do two passes over all BDIs -- one withWB_SYNC_NONE, and one withWB_SYNC_ALL. Well, not quite. It would still have to go to each SB and call thequota_syncandsync_fscallbacks, but those should be cheap.
Well, they're not exactly cheap (journaling filesystems have to force
transaction commits and wait for them) but that does not really matter. The real problem is that to wait for IO completion you need some list of inodes you want to wait for and you can currently get such list only from superblock.
Quoted 2 lines
And yes, this would make
sys_syncandsys_syncfsmore different than they are today.
Honza
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Quoted 35 lines · Jan Kara
Quoted 28 lines · Curt Wohlgemuth
Quoted 12 lines
Quoted 12 lines
One other issue I have with sync as it's structured is that we don't do a
WB_SYNC_ALLpass on any inode that's only associated with a block device, and not on a mounted filesystem. Blockdev mounts are pseudo-mounts, and are explicitly skipped in__sync_filesystem(). So if you've written directly to a block device and do a sync, the only pass over the pages for this inode are via thewakeup_flusher_threads()-- which operates on a BDI, regardless of the superblock, and usesWB_SYNC_NONE.All the
sync_filesystem()calls are per-sb, not per-BDI, and they'll exclude pseudo-superblocks.Quoted 6 lines
Quoted 4 lines
I've seen cases in our modified kernels here at Google in which lilo/shutdown failed because of a lack of
WB_SYNC_ALLwriteback for/dev/sda(though I haven't been able to come up with a consistent test case, nor reproduce this on an upstream kernel).Ho hum, I wonder what would be the cleanest way to fix this. I guess we could make an exception for 'bdev' superblock in the code but it's ugly.
Yes, it's ugly.
You could declare that sync(2) is just doing it's job, that it's not designed to write dirty pages from devices that don't have filesystems mounted on them; that's what Christoph seems to be saying, and it's what the man pages for sync(2) that I've seen say as well. But everybody I've talked to about this is surprised, so you could declare it a bug as well :-) .
Quoted 6 lines
It seems to me that
sys_sync*could* just dispense with iterating over the superblocks, and just iterate over the BDIs, just aswakeup_flusher_threads()does. I.e., just do two passes over all BDIs -- one withWB_SYNC_NONE, and one withWB_SYNC_ALL. Well, not quite. It would still have to go to each SB and call thequota_syncandsync_fscallbacks, but those should be cheap.
Quoted 5 lines
Well, they're not exactly cheap (journaling filesystems have to force transaction commits and wait for them) but that does not really matter. The real problem is that to wait for IO completion you need some list of inodes you want to wait for and you can currently get such list only from superblock.
Ah, of course, I realized that some days ago but forgot it -- something that seems to happen more than I'd like to admit to myself.
Thanks,
Curt
Quoted 9 lines
Quoted 3 lines
And yes, this would make
sys_syncandsys_syncfsmore different than they are today.Honza --
Jan Kara <[email protected]>
SUSE Labs, CR
--
To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Quoted 5 lines · Jan Kara
Well, they're not exactly cheap (journaling filesystems have to force
transaction commits and wait for them) but that does not really matter. The real problem is that to wait for IO completion you need some list of inodes you want to wait for and you can currently get such list only from superblock.
All block device inodes sit on blockdev_superblock, we got rid of inodes without a superblock long time ago.
Jan Kara
reviewerQuoted 9 lines · Christoph Hellwig
Quoted 5 lines · Jan Kara
Well, they're not exactly cheap (journaling filesystems have to force
transaction commits and wait for them) but that does not really matter. The real problem is that to wait for IO completion you need some list of inodes you want to wait for and you can currently get such list only from superblock.All block device inodes sit on
blockdev_superblock, we got rid of inodes without a superblock long time ago.
Sure, we can easily iterate also blockdev_superblock. What I meant is
that blockdev_superblock will need a special handling since we otherwise ignore pseudo superblocks...
Honza
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Quoted 5 lines · Jan Kara
Quoted 2 lines
All block device inodes sit on
blockdev_superblock, we got rid of inodes without a superblock long time ago.Sure, we can easily iterate also
blockdev_superblock. What I meant is
thatblockdev_superblockwill need a special handling since we otherwise ignore pseudo superblocks...
Pseudo superblocks aren't ignored. They are added to super_blocks like all others, and iterate_supers doesn't skip over them. The problem is that blockdev_superblock doesn't have a proper s_bdi set, and thus gets skipped over by __sync_filesystem.
Jan Kara
reviewerQuoted 11 lines · Christoph Hellwig
Quoted 5 lines · Jan Kara
Quoted 2 lines
All block device inodes sit on
blockdev_superblock, we got rid of inodes without a superblock long time ago.Sure, we can easily iterate also
blockdev_superblock. What I meant is
thatblockdev_superblockwill need a special handling since we otherwise ignore pseudo superblocks...Pseudo superblocks aren't ignored. They are added to
super_blockslike all others, anditerate_supersdoesn't skip over them. The problem is thatblockdev_superblockdoesn't have a propers_bdiset, and thus gets skipped over by__sync_filesystem.
Yes. But even if it was not skipped writeback_inodes_sb() doesn't have
one flusher thread to kick to actually do the writeout (since each inode on blockdev_superblock belongs to a different bdi). So it's perfectly fine we skip blockdev_superblock.
If we want to fix the problem something like attached patch should do.
Comments?
Honza
PS: While testing the patch, I've noticed that block device can have any dirty data only if it is still open (__blkdev_put() writes all dirty pages) so that somehow limits how much people can be burned by sync not writing out block devices...
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Quoted 1 line
From
2834bd2727c93055bb7373d8849492044f70c530Mon Sep 17 00:00:00 2001
From: Jan Kara <[email protected]>
Date: Tue, 12 Jul 2011 22:01:51 +0200
Subject: [PATCH] vfs: Make sync(1) writeout also block device inodes
In case block device does not have filesystem mounted on it, sync(1) will just ignore it and doesn't writeout dirty pages because it iterates over filesystems with s_bdi != noop_backing_dev_info and thus it avoids blockdev_superblock. Since it's unexpected that sync doesn't writeout dirty data for block devices be nice to users and change the behavior to do so.
This requires a change to how syncing is done. We now first traverse all superblocks with s_bdi != noop_backing_dev_info, writeout their inodes and call sync_fs and when this is done, we traverse all block devices and sync them.
--- fs/sync.c | 70 +++++++++++++++++++++++++++++++++++++++++++++++------------- 1 files changed, 55 insertions(+), 15 deletions(-)
fs/sync.c +55 -15
@@ -23,20 +23,13 @@/* * Do the filesystem syncing work. For simple filesystems -* writeback_inodes_sb(sb) just dirties buffers with inodes so we have to -* submit IO for these buffers via __sync_blockdev(). This also speeds up the -* wait == 1 case since in that case write_inode() functions do +* writeback_inodes_sb(sb) just dirties buffers with inodes so the caller has +* to additionally submit IO for these buffers via __sync_blockdev(). This also +* speeds up the wait == 1 case since in that case write_inode() functions do * sync_dirty_buffer() and thus effectively write one block at a time. */ -static int __sync_filesystem (struct super_block *sb,int wait) +static void __sync_filesystem (struct super_block *sb,int wait) { -/* -* This should be safe, as we require bdi backing to actually -* write out data in the first place -*/ -if (sb->s_bdi == &noop_backing_dev_info) -return 0 ; -if (sb->s_qcop && sb->s_qcop->quota_sync) sb->s_qcop->quota_sync (sb,-1 , wait);
@@ -47,7 +40,6 @@ static int __sync_filesystem(struct super_block *sb, int wait)if (sb->s_op->sync_fs) sb->s_op->sync_fs (sb, wait); -return __sync_blockdev (sb->s_bdev, wait); }/*
@@ -71,16 +63,26 @@ int sync_filesystem(struct super_block *sb)if (sb->s_flags &MS_RDONLY )return 0 ; - ret =__sync_filesystem (sb,0 ); +/* +* This should be safe, as we require bdi backing to actually +* write out data in the first place. +*/ +if (sb->s_bdi == &noop_backing_dev_info) +return 0 ; + +__sync_filesystem (sb,0 ); + ret =__sync_blockdev (sb->s_bdev,0 );if (ret <0 )return ret; -return __sync_filesystem (sb,1 ); +__sync_filesystem (sb,1 ); +return __sync_blockdev (sb->s_bdev,1 ); }EXPORT_SYMBOL_GPL (sync_filesystem);static void sync_one_sb (struct super_block *sb,void *arg) { -if (!(sb->s_flags &MS_RDONLY )) +/* Avoid read-only filesystems and filesystems without backing device */ +if (!(sb->s_flags &MS_RDONLY ) && sb->s_bdi != &noop_backing_dev_info)__sync_filesystem (sb, *(int *)arg); }/*
@@ -92,6 +94,42 @@ static void sync_filesystems(int wait)iterate_supers (sync_one_sb, &wait); } +static void sync_all_bdevs (int wait) +{ +struct inode *inode, *old_inode =NULL ; + +spin_lock (&inode_sb_list_lock); +list_for_each_entry (inode, &blockdev_superblock->s_inodes, i_sb_list) { +struct address_space *mapping = inode->i_mapping; + +spin_lock (&inode->i_lock); +if (inode->i_state & (I_FREEING |I_WILL_FREE |I_NEW ) || + mapping->nrpages ==0 ) { +spin_unlock (&inode->i_lock); +continue ; + } +__iget (inode); +spin_unlock (&inode->i_lock); +spin_unlock (&inode_sb_list_lock); +/* +* We hold a reference to 'inode' so it couldn't have been +* removed from s_inodes list while we dropped the +* inode_sb_list_lock. We cannot iput the inode now as we can +* be holding the last reference and we cannot iput it under +* inode_sb_list_lock. So we keep the reference and iput it +* later. +*/ +iput (old_inode); + old_inode = inode; + +__sync_blockdev (I_BDEV (inode), wait); + +spin_lock (&inode_sb_list_lock); + } +spin_unlock (&inode_sb_list_lock); +iput (old_inode); +} +/* * sync everything. Start out by waking pdflush, because that writes back * all queues in parallel.
@@ -101,6 +139,8 @@ SYSCALL_DEFINE0(sync)wakeup_flusher_threads (0 );sync_filesystems (0 );sync_filesystems (1 ); +sync_all_bdevs (0 ); +sync_all_bdevs (1 );if (unlikely (laptop_mode))laptop_sync_completion ();return 0 ;
Signature · 1 line
1.7.1
Hi Jan:
Quoted 12 lines · Jan Kara
Quoted 11 lines · Christoph Hellwig
Quoted 5 lines · Jan Kara
Quoted 2 lines
All block device inodes sit on
blockdev_superblock, we got rid of inodes without a superblock long time ago.Sure, we can easily iterate also
blockdev_superblock. What I meant is thatblockdev_superblockwill need a special handling since we otherwise ignore pseudo superblocks...Pseudo superblocks aren't ignored. They are added to
super_blockslike all others, anditerate_supersdoesn't skip over them. The problem is thatblockdev_superblockdoesn't have a propers_bdiset, and thus gets skipped over by__sync_filesystem.
Quoted 7 lines
Yes. But even if it was not skipped
writeback_inodes_sb()doesn't have one flusher thread to kick to actually do the writeout (since each inode onblockdev_superblockbelongs to a different bdi). So it's perfectly fine we skipblockdev_superblock.If we want to fix the problem something like attached patch should do. Comments?
Your patch looks good to me, in that it does hit all the bdevs with both WB_SYNC_NONE and SYNC_ALL. However, I still say that the call to wakeup_flusher_threads() in sys_sync() is superfluous, at least as long as writeback_inodes_sb() waits for completion of the work item that it enqueues.
Thanks,
Curt
Quoted 11 lines
Honza
PS: While testing the patch, I've noticed that block device can have any dirty data only if it is still open (
__blkdev_put()writes all dirty pages) so that somehow limits how much people can be burned by sync not writing out block devices...
--
Jan Kara <[email protected]>
SUSE Labs, CR
Jan Kara
reviewerHi Curt,
Quoted 18 lines · Curt Wohlgemuth
Quoted 17 lines · Jan Kara
Quoted 11 lines · Christoph Hellwig
Quoted 5 lines · Jan Kara
Quoted 2 lines
All block device inodes sit on
blockdev_superblock, we got rid of inodes without a superblock long time ago.Sure, we can easily iterate also
blockdev_superblock. What I meant is thatblockdev_superblockwill need a special handling since we otherwise ignore pseudo superblocks...Pseudo superblocks aren't ignored. They are added to
super_blockslike all others, anditerate_supersdoesn't skip over them. The problem is thatblockdev_superblockdoesn't have a propers_bdiset, and thus gets skipped over by__sync_filesystem.Yes. But even if it was not skipped
writeback_inodes_sb()doesn't have one flusher thread to kick to actually do the writeout (since each inode onblockdev_superblockbelongs to a different bdi). So it's perfectly fine we skipblockdev_superblock.
Quoted 8 lines
Quoted 2 lines
If we want to fix the problem something like attached patch should do. Comments?
Your patch looks good to me, in that it does hit all the bdevs with both
WB_SYNC_NONEandSYNC_ALL. However, I still say that the call towakeup_flusher_threads()insys_sync()is superfluous, at least as long aswriteback_inodes_sb()waits for completion of the work item that it enqueues.
Actually, it's the other way around writeback_inodes_sb() is superfluous
because of wakeup_flusher_threads(). So something like attached patch could improve sync times (especially in presence of other IO). So far I have only checked that sync times look reasonable with it but didn't really compare them with original kernel...
Honza
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Quoted 1 line
From
7d17da2ceed603a3cb013e06a2158e56029043a8Mon Sep 17 00:00:00 2001
From: Jan Kara <[email protected]>
Date: Thu, 14 Jul 2011 23:42:19 +0200
Subject: [PATCH] vfs: Avoid unnecessary WB_SYNC_NONE writeback during sync(1)
wakeup_flusher_thread(0) will queue work doing complete writeback for each flusher thread. Thus there is not much point in submitting another work doing full inode WB_SYNC_NONE writeback by sync_filesystems(). So change sync to do:
wakeup_flusher_threads(0);
for each filesystem
async quota_sync()
synchronous inode writeback
async sync_fs()
submit dirty buffers from all block devices
for each filesystem
synchronous quota_sync()
synchronous sync_fs()
synchronous writeout of all block devices
Note that we do synchronous inode writeback before calling sync_fs().
Otherwise sync_fs() would be called in the middle of inode writeback done by flusher thread and thus couldn't do very meaningful work.
--- fs/sync.c | 38 ++++++++++++++++++++++++++++++++------ 1 files changed, 32 insertions(+), 6 deletions(-)
fs/sync.c +32 -6
@@ -21,6 +21,11 @@#define VALID_FLAGS (SYNC_FILE_RANGE_WAIT_BEFORE|SYNC_FILE_RANGE_WRITE| \ SYNC_FILE_RANGE_WAIT_AFTER) +/* Wait flags for __sync_filesystem */ +#define WAIT_QUOTA 0x01 +#define WAIT_INODES 0x02 +#define WAIT_FS 0x04 +/* * Do the filesystem syncing work. For simple filesystems * writeback_inodes_sb(sb) just dirties buffers with inodes so the caller has
@@ -31,15 +36,15 @@static void __sync_filesystem (struct super_block *sb,int wait) {if (sb->s_qcop && sb->s_qcop->quota_sync) - sb->s_qcop->quota_sync (sb,-1 , wait); + sb->s_qcop->quota_sync (sb,-1 , !!(wait &WAIT_QUOTA )); -if (wait) +if (wait &WAIT_INODES )sync_inodes_sb (sb);else writeback_inodes_sb (sb);if (sb->s_op->sync_fs) - sb->s_op->sync_fs (sb, wait); + sb->s_op->sync_fs (sb, !!(wait &WAIT_FS )); }/*
@@ -74,7 +79,7 @@ int sync_filesystem(struct super_block *sb) ret =__sync_blockdev (sb->s_bdev,0 );if (ret <0 )return ret; -__sync_filesystem (sb,1 ); +__sync_filesystem (sb,WAIT_QUOTA |WAIT_INODES |WAIT_FS );return __sync_blockdev (sb->s_bdev,1 ); }EXPORT_SYMBOL_GPL (sync_filesystem);
@@ -130,16 +135,37 @@ static void sync_all_bdevs(int wait)iput (old_inode); } +static void sync_one_fs_metadata (struct super_block *sb,void *arg) +{ +/* Avoid read-only filesystems and filesystems without backing device */ +if (sb->s_flags &MS_RDONLY ) +return ; +if (sb->s_bdi == &noop_backing_dev_info) +return ; +if (sb->s_qcop && sb->s_qcop->quota_sync) + sb->s_qcop->quota_sync (sb,-1 ,1 ); +if (sb->s_op->sync_fs) + sb->s_op->sync_fs (sb,1 ); +} +/* * sync everything. Start out by waking pdflush, because that writes back * all queues in parallel. */ SYSCALL_DEFINE0 (sync) { +/* Start flushing on all devices */ wakeup_flusher_threads (0 ); -sync_filesystems (0 ); -sync_filesystems (1 ); +/* +* Above call queued work doing complete writeout on each filesystem. +* So now we only have to queue work which guarantees data integrity +* - not much should be left for it to write. The WB_SYNC_ALL inode +* writeback also guarantees that sync_fs() is called after inodes +* are written out and thus it can do meaningful work. +*/ +sync_filesystems (WAIT_INODES );sync_all_bdevs (0 ); +iterate_supers (sync_one_fs_metadata,NULL );sync_all_bdevs (1 );if (unlikely (laptop_mode))laptop_sync_completion ();
Signature · 1 line
1.7.1
Quoted 5 lines · Jan Kara
Actually, it's the other way around
writeback_inodes_sb()is superfluous
because ofwakeup_flusher_threads(). So something like attached patch could improve sync times (especially in presence of other IO). So far I have only checked that sync times look reasonable with it but didn't really compare them with original kernel...
This changes the order in which ->quota_sync is called relatively to other operations, see my other mail about it. Also the code gets really confusing at this point, I think you're better of stopping to try to shared code between syncfs, umount & co and sys_sync with these bits.
You're also skipping the ->sync_fs and quotasync calls the first round. I know for XFS sync_fs without wait is already mostly a no-op, but we'll need to benchmark and document this change, and apply it to the non-sync caller as well.
Jan Kara
reviewerQuoted 11 lines · Christoph Hellwig
Quoted 5 lines · Jan Kara
Actually, it's the other way around
writeback_inodes_sb()is superfluous
because ofwakeup_flusher_threads(). So something like attached patch could improve sync times (especially in presence of other IO). So far I have only checked that sync times look reasonable with it but didn't really compare them with original kernel...This changes the order in which ->quota_sync is called relatively to other operations, see my other mail about it. Also the code gets really confusing at this point, I think you're better of stopping to try to shared code between syncfs, umount & co and
sys_syncwith these bits.
Good point. I'll stop sharing the code for these two cases.
Quoted 1 line
You're also skipping the ->sync_fs and quotasync calls the first round.
Well, kind of. Since writeback is running asynchronously, we have no way
to call ->sync_fs(sb, 0) just after async inode writeback is done (and it doesn't really make sense to call it before that moment). So we call synchronous inode writeback first and only then ->sync_fs().
Quoted 3 lines
I know for XFS
sync_fswithout wait is already mostly a no-op, but we'll need to benchmark and document this change, and apply it to the non-sync caller as well.
I already have some numbers:
I've checked pure sync overhead by
sync; time for (( i = 0; i < 100; i++ )); do sync; done
the results of 5 runs are:
avg 1.130400s, stddev 0.027369 (unpatched kernel) avg 1.073200s, stddev 0.040848 (patched kernel)
so as expected slightly better for patched kernel.
And test with 20000 4k files in 100 directories on xfs:
avg 155.995600s, stddev 1.879084 (unpatched kernel) avg 155.942200s, stddev 1.843881 (patched kernel)
so no real difference.
The same test with ext3:
avg 154.597200s, stddev 1.556965 (unpatched kernel) avg 153.109800s, stddev 1.339094 (patched kernel)
again almost no difference.
Where would you like to document the change?
Honza
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Quoted 5 lines · Jan Kara
Quoted 1 line
You're also skipping the ->sync_fs and quotasync calls the first round.
Well, kind of. Since writeback is running asynchronously, we have no way
to call ->sync_fs(sb, 0) just after async inode writeback is done (and it doesn't really make sense to call it before that moment). So we call synchronous inode writeback first and only then ->sync_fs().
I don't nessecarily say it's a bad thing, but when you do subtile behaviour changes like this it at least needs to be clearly documented in the changelog, or even better split into a patch on it's own.
Quoted 1 line
Where would you like to document the change?
commit log.
Quoted 5 lines · Curt Wohlgemuth
Your patch looks good to me, in that it does hit all the bdevs with both
WB_SYNC_NONEandSYNC_ALL. However, I still say that the call towakeup_flusher_threads()insys_sync()is superfluous, at least as long aswriteback_inodes_sb()waits for completion of the work item that it enqueues.
Commit 3beab0b42413e83a7907db7176b54c840fc75a81
"sys_sync(): fix 16% performance regression in ffsb create_4k test"
suggests we need the wakeup_flusher_threads. We might be able to get rid of the writeback_inodes_sb call if we're lucky.
Quoted 14 lines · Jan Kara
fs/sync.c +1
-8-static int __sync_filesystem (struct super_block *sb,int wait) +static void __sync_filesystem (struct super_block *sb,int wait) { -/* -* This should be safe, as we require bdi backing to actually -* write out data in the first place -*/ -if (sb->s_bdi == &noop_backing_dev_info) -return 0 ; -if (sb->s_qcop && sb->s_qcop->quota_sync) sb->s_qcop->quota_sync (sb,-1 , wait);
fs/sync.c static int __sync_filesystem(struct super_block *sb, int wait) · line 50
Quoted 4 lines
fs/sync.c +0
-1@@ -47,7 +40,6 @@ static int __sync_filesystem(struct super_block *sb, int wait)if (sb->s_op->sync_fs) sb->s_op->sync_fs (sb, wait); -return __sync_blockdev (sb->s_bdev, wait);
Removing this call breaks sys_syncfs and similar semantics on filesystem just pushing metadata into buffers in ->write_inode or ->sync_fs and then expecting the caller to write them out. This list of filesystem includes ext2 and in general most filesystems without journaling or similar technics.
I'm perfectly fine with pushing the sync_blockdev call into the filesystem for these, but we'll need a way to handle them.
fs/sync.c int sync_filesystem(struct super_block *sb) · line 71
Quoted 6 lines
fs/sync.c +6
-0+/* +* This should be safe, as we require bdi backing to actually +* write out data in the first place. +*/ +if (sb->s_bdi == &noop_backing_dev_info) +return 0 ;
There's really no reason to do that check early. There's nothing no reason to not have a filesystem that doesn't use the writeback code, but still has a ->sync_fs method. IMHO this check should move into sync_inodes_sb/writeback_inodes_sb.
Quoted 24 lines
fs/sync.c +24
-0+static void sync_all_bdevs (int wait) +{ +struct inode *inode, *old_inode =NULL ; + +spin_lock (&inode_sb_list_lock); +list_for_each_entry (inode, &blockdev_superblock->s_inodes, i_sb_list) { +struct address_space *mapping = inode->i_mapping; + +spin_lock (&inode->i_lock); +if (inode->i_state & (I_FREEING |I_WILL_FREE |I_NEW ) || + mapping->nrpages ==0 ) { +spin_unlock (&inode->i_lock); +continue ; + } +__iget (inode); +spin_unlock (&inode->i_lock); +spin_unlock (&inode_sb_list_lock); +/* +* We hold a reference to 'inode' so it couldn't have been +* removed from s_inodes list while we dropped the +* inode_sb_list_lock. We cannot iput the inode now as we can +* be holding the last reference and we cannot iput it under +* inode_sb_list_lock. So we keep the reference and iput it +* later.
fs/sync.c static void sync_filesystems(int wait) · line 130
Quoted 10 lines
fs/sync.c +10
-0+*/ +iput (old_inode); + old_inode = inode; + +__sync_blockdev (I_BDEV (inode), wait); + +spin_lock (&inode_sb_list_lock); + } +spin_unlock (&inode_sb_list_lock); +iput (old_inode);
At which point we could fold this code into a blkdev_sync_fs method for now. Long term we'll need to support multiple BDIs per SB anyway, at which point the code can go away again.
Jan Kara
reviewerQuoted 16 lines · Christoph Hellwig
Quoted 15 lines · Jan Kara
fs/sync.c +1
-8-static int __sync_filesystem (struct super_block *sb,int wait) +static void __sync_filesystem (struct super_block *sb,int wait) { -/* -* This should be safe, as we require bdi backing to actually -* write out data in the first place -*/ -if (sb->s_bdi == &noop_backing_dev_info) -return 0 ; -if (sb->s_qcop && sb->s_qcop->quota_sync) sb->s_qcop->quota_sync (sb,-1 , wait);@@ -47,7 +40,6 @@ static int __sync_filesystem(struct super_block *sb, int wait)
Quoted 9 lines
fs/sync.c static int __sync_filesystem(struct super_block *sb, int wait) · line 50
Quoted 3 lines
fs/sync.c +0
-1if (sb->s_op->sync_fs) sb->s_op->sync_fs (sb, wait); -return __sync_blockdev (sb->s_bdev, wait);Removing this call breaks
sys_syncfsand similar semantics on filesystem just pushing metadata into buffers in ->write_inode or ->sync_fs and then expecting the caller to write them out. This list of filesystem includes ext2 and in general most filesystems without journaling or similar technics.
Note that for sys_syncfs() the __sync_blockdev() has just moved from__sync_filesystem() to sync_filesystem(). So nothing should change.
Quoted 2 lines
I'm perfectly fine with pushing the
sync_blockdevcall into the filesystem for these, but we'll need a way to handle them.
Yes, calling sync_blockdev() from sync_fs() might actually make sence
(e.g. ext3 and ext4 don't need it) but I didn't want to go that far in this patch.
Quoted 11 lines
fs/sync.c int sync_filesystem(struct super_block *sb) · line 71
Quoted 6 lines
fs/sync.c +6
-0+/* +* This should be safe, as we require bdi backing to actually +* write out data in the first place. +*/ +if (sb->s_bdi == &noop_backing_dev_info) +return 0 ;There's really no reason to do that check early. There's nothing no reason to not have a filesystem that doesn't use the writeback code, but still has a ->sync_fs method. IMHO this check should move into sync_inodes_sb/writeback_inodes_sb.
OK, will change.
Quoted 30 lines
Quoted 30 lines
fs/sync.c +30
-0+static void sync_all_bdevs (int wait) +{ +struct inode *inode, *old_inode =NULL ; + +spin_lock (&inode_sb_list_lock); +list_for_each_entry (inode, &blockdev_superblock->s_inodes, i_sb_list) { +struct address_space *mapping = inode->i_mapping; + +spin_lock (&inode->i_lock); +if (inode->i_state & (I_FREEING |I_WILL_FREE |I_NEW ) || + mapping->nrpages ==0 ) { +spin_unlock (&inode->i_lock); +continue ; + } +__iget (inode); +spin_unlock (&inode->i_lock); +spin_unlock (&inode_sb_list_lock); +/* +* We hold a reference to 'inode' so it couldn't have been +* removed from s_inodes list while we dropped the +* inode_sb_list_lock. We cannot iput the inode now as we can +* be holding the last reference and we cannot iput it under +* inode_sb_list_lock. So we keep the reference and iput it +* later. +*/ +iput (old_inode); + old_inode = inode; + +__sync_blockdev (I_BDEV (inode), wait); +
Quoted 8 lines
fs/sync.c static void sync_filesystems(int wait) · line 122
Quoted 4 lines
fs/sync.c +4
-0+spin_lock (&inode_sb_list_lock); + } +spin_unlock (&inode_sb_list_lock); +iput (old_inode);At which point we could fold this code into a
blkdev_sync_fsmethod for now. Long term we'll need to support multiple BDIs per SB anyway, at which point the code can go away again.
Ah, I had to think a bit before I understood what you mean :). It's kind
of elegant but also slightly subtle (it's not immediately obvious how blockdevs are synced during sync when you look at the code). Umm, and you don't have any guarantee in which order superblocks are on the sb list so you could sync block devices before some filesystems are finished. So I don't think using blkdev_sync_fs() is a good idea after all.
Honza
Signature · 2 lines
Jan Kara <[email protected]> SUSE Labs, CR
Quoted 7 lines · Jan Kara
Quoted 5 lines
Removing this call breaks
sys_syncfsand similar semantics on filesystem just pushing metadata into buffers in ->write_inode or ->sync_fs and then expecting the caller to write them out. This list of filesystem includes ext2 and in general most filesystems without journaling or similar technics.Note that for
sys_syncfs()the__sync_blockdev()has just moved from__sync_filesystem()tosync_filesystem(). So nothing should change.
Sorry, I missed that.
Quoted 5 lines
Quoted 2 lines
I'm perfectly fine with pushing the
sync_blockdevcall into the filesystem for these, but we'll need a way to handle them.Yes, calling
sync_blockdev()fromsync_fs()might actually make sence
(e.g. ext3 and ext4 don't need it) but I didn't want to go that far in this patch.
The more fine-grained we can split the patches, the better. It'll help to explain what we're doing to thise trying to figure out a few years down the road.
Quoted 9 lines
Quoted 3 lines
At which point we could fold this code into a
blkdev_sync_fsmethod for now. Long term we'll need to support multiple BDIs per SB anyway, at which point the code can go away again.Ah, I had to think a bit before I understood what you mean :). It's kind
of elegant but also slightly subtle (it's not immediately obvious how blockdevs are synced during sync when you look at the code). Umm, and you don't have any guarantee in which order superblocks are on the sb list so you could sync block devices before some filesystems are finished. So I don't think usingblkdev_sync_fs()is a good idea after all.
This required calling sync_blkdev from ->sync_fs of those filesystems that actually need it as a pre-requisite of course.
Hi Christoph:
Quoted 15 lines · Christoph Hellwig
Quoted 10 lines · Curt Wohlgemuth
I don't quite understand this. It's true that all IO done as a result of calling
wb_writeback()on this work item won't finish before the completion takes place, but sending all those pages in flight *will* take place. And that's a lot of time. To wait on this before we then callsync_inodes_sb(), and do it all over again, seems odd at best.Pre-2.6.35 kernels would start non-integrity sync writeback and immediately return, which would seem like a reasonable "prefetch-y" thing to do, considering it's going to be immediately followed by a data integrity sync writeback operation.
The only old kernel I have around is 2.6.16, so looking at that one for old semantics:
Quoted 11 lines
- it first does a
wakeup_pdflush()which does a truely asynchronous writeback of everything in the system. This is replaced with awakeup_flusher_threads(0)in 3.0, which has pretty similar sematics.
- after that things change a bit as we reordered sync quite a bit too
a) improve data integrity by properly ordering the steps of the sync and b) shared code between the global sync and per-fs sync as used by umount,sys_syncfsand a few other callers. But both do an semi-sync pass and a sync pass on per-sb writeback. For the old code it's done from the calling threads context, while the next code moved it to the flushers thread, but still waiting for it with the completion. No major change in semantics as far as I can see.
You're right about this. (I'm looking at 2.6.26 as it happens, but it's got the same behavior.)
The semantics did change between 2.6.34 and 2.6.35 though. When the work item queue was introduced in 2.6.32, the semantics changed from what you describe above to what's present through 2.6.34:writeback_inodes_sb() would enqueue a work item, and return. Your commit 83ba7b07 ("writeback: simplify the write back thread queue") added the wait_for_completion() call, putting the semantics back to where they were pre-2.6.32.
And in fact, this change has only minor effect on writeback, at least -- since the WB_SYNC_ALL work item won't even get started until the WB_SYNC_NONE one is finished.
Though one additional change between the old way (pre-2.6.32) and
today: with the old kernel, the pdflush thread would operate concurrently with the first (and second?) sync path through writeback.
Today of course, they're *all* serialized. So really a call tosys_sync() will enqueue 3 work items -- one from wakeup_flusher_threads(), one from writeback_inodes_sb(), and one from sync_inodes_sb().
Quoted 6 lines
The idea of the async pass is to start writeback on as many as possible pages before actively having to wait on them. I'd agree with your assessment that the
writeback_inodes_sbmight not really help all that much - given that a lot of the waiting might not actually be for I/O completion but e.g. for the block level throtteling (or maybe cgroups in your case?).
What I'm seeing with an admittedly nasty workload -- two FIO scripts writing as fast as possible to 1000 files each, with a sync 10 seconds into the workload -- is that the "async pass" will write out a (nearly) completely separate set of pages from the "sync pass." Because the work items are serialized, there are so many new dirty pages since the first work item starts, and so the sync pass has just about as many pages to write out as the first. So the "prefetch-y" async pass is basically wasted time.
Quoted 8 lines
For
sys_syncI'm pretty sure we could simply remove thewriteback_inodes_sbcall and get just as good if not better performance, but we'd still need a solution for the othersync_filesystemcallers, assuming the first write actually benefits them. Maybe you can run somesys_syncfsmicrobenchmarks to check it? Note that doing multiple passes generally doesn't actually help live-lock avoidance either, but wu has recently done some work in that area.
I've been running tests using sys_sync, and seeing sync take far longer than I'd expect, but haven't tried sys_syncfs yet. I'll add that to my list.
Still, both do two completely serialized passes: one "async pass" followed by the "sync pass" -- during which other work items can get queued, lots more pages get dirtied, etc.
Quoted 4 lines
Another thing we've discussed a while ago is changing
sync_inodes_sbto the writeback or at least the waiting back in the calling threads context to not block the flushers threads from getting real work done.
This would make sense to insure that sync finishes reasonably fast, but as Jan points out, would cause that much more interference with the write pattern...
Thanks,
Curt
--
To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Quoted 7 lines · Curt Wohlgemuth
The semantics did change between 2.6.34 and 2.6.35 though. When the work item queue was introduced in 2.6.32, the semantics changed from what you describe above to what's present through 2.6.34:
writeback_inodes_sb()would enqueue a work item, and return. Your commit83ba7b07("writeback: simplify the write back thread queue") added thewait_for_completion()call, putting the semantics back to where they were pre-2.6.32.
Yes. The kernels inbetween had that nasty writeback vs umount races that we could trigger quite often.
Quoted 7 lines
Though one additional change between the old way (pre-2.6.32) and
today: with the old kernel, the pdflush thread would operate concurrently with the first (and second?) sync path through writeback.
Today of course, they're *all* serialized. So really a call tosys_sync()will enqueue 3 work items -- one fromwakeup_flusher_threads(), one fromwriteback_inodes_sb(), and one fromsync_inodes_sb().
Yes. And having WB_SYNC_NONE items from both wakeup_flusher_threads vs writeback_inodes_sb is plain stupid. The initial conversion to the new sync_filesystem scheme had removed the wakeup_flusher_threads call, but that caused a huge regression in some benchmark.
As mentioned before Wu was working on some code to introduce tagging so that the WB_SYNC_ALL call won't start writing pages dirtied after the sync call, which should help with your issue. Although to completely solve it we really need to get down to just two passes.
Quoted 11 lines · Christoph Hellwig
Quoted 7 lines · Curt Wohlgemuth
The semantics did change between 2.6.34 and 2.6.35 though. When the work item queue was introduced in 2.6.32, the semantics changed from what you describe above to what's present through 2.6.34:
writeback_inodes_sb()would enqueue a work item, and return. Your commit83ba7b07("writeback: simplify the write back thread queue") added thewait_for_completion()call, putting the semantics back to where they were pre-2.6.32.Yes. The kernels inbetween had that nasty writeback vs umount races that we could trigger quite often.
My apologies for not being aware of these races (I tried to search mailing lists) -- how did they manifest themselves? I don't see anything about it in your commit message of 83ba7b07. I do see that we are not taking s_umount for sys_sync any longer (only in sync_filesystem(), e.g., for sys_syncfs)...
Quoted 13 lines
Quoted 7 lines
Though one additional change between the old way (pre-2.6.32) and
today: with the old kernel, the pdflush thread would operate concurrently with the first (and second?) sync path through writeback. Today of course, they're *all* serialized. So really a call tosys_sync()will enqueue 3 work items -- one fromwakeup_flusher_threads(), one fromwriteback_inodes_sb(), and one fromsync_inodes_sb().Yes. And having
WB_SYNC_NONEitems from bothwakeup_flusher_threadsvswriteback_inodes_sbis plain stupid. The initial conversion to the newsync_filesystemscheme had removed thewakeup_flusher_threadscall, but that caused a huge regression in some benchmark.
Quoted 4 lines
As mentioned before Wu was working on some code to introduce tagging so that the
WB_SYNC_ALLcall won't start writing pages dirtied after the sync call, which should help with your issue. Although to completely solve it we really need to get down to just two passes.
I can definitely see how tagging with the start of sync would be helpful; also how going from three to two passes seems like a no-brainer.
But what's the real point of doing even two passes -- one SYNC_NONE followed by one SYNC_ALL? Is it try to get through as many inodes as possible before we potentially lock up by waiting on an inode on an unavailable device?
Thanks,
Curt
Quoted 8 lines · Curt Wohlgemuth
I can definitely see how tagging with the start of sync would be helpful; also how going from three to two passes seems like a no-brainer.
But what's the real point of doing even two passes -- one
SYNC_NONEfollowed by oneSYNC_ALL? Is it try to get through as many inodes as possible before we potentially lock up by waiting on an inode on an unavailable device?
Yes. Or at least that's the idea, I'd love to see an actual prove for it.
Quoted 9 lines · Dave Chinner
The comment says it does not wait for IO to be -completed-.
The function as implemented waits for IO to be *submitted*.
This provides the callers with same blocking semantics (i.e. request queue full) as if the caller submitted the IO themselves. The code that uses this function rely on this blocking to delay the next set of operations they do until after IO has been started, so removing the completion will change their behaviour significantly.
The real importance is for locking. If we don't wait for completion we may still do writebacks in the flushers thread while we're already unlocked s_umount. That will give us back the nasty writeback vs umount races that this scheme fixed. The commit logs that added it should explain it in more detail (at least I usually write detailed changelogs)