[XFS] Fix a race in xfs_submit_ioend() where we can be completing I/O for
authorDavid Chinner <dgc@sgi.com>
Wed, 18 Jan 2006 02:38:12 +0000 (13:38 +1100)
committerNathan Scott <nathans@sgi.com>
Wed, 18 Jan 2006 02:38:12 +0000 (13:38 +1100)
a page while we are still submitting other buffers on the same page for
I/O.

SGI-PV: 948197
SGI-Modid: xfs-linux-melb:xfs-kern:25004a

Signed-off-by: David Chinner <dgc@sgi.com>
Signed-off-by: Nathan Scott <nathans@sgi.com>
fs/xfs/linux-2.6/xfs_aops.c

index d1db8c1..1206267 100644 (file)
@@ -336,24 +336,47 @@ static inline int bio_add_buffer(struct bio *bio, struct buffer_head *bh)
 }
 
 /*
- * Submit all of the bios for all of the ioends we have saved up,
- * covering the initial writepage page and also any probed pages.
+ * Submit all of the bios for all of the ioends we have saved up, covering the
+ * initial writepage page and also any probed pages.
+ *
+ * Because we may have multiple ioends spanning a page, we need to start
+ * writeback on all the buffers before we submit them for I/O. If we mark the
+ * buffers as we got, then we can end up with a page that only has buffers
+ * marked async write and I/O complete on can occur before we mark the other
+ * buffers async write.
+ *
+ * The end result of this is that we trip a bug in end_page_writeback() because
+ * we call it twice for the one page as the code in end_buffer_async_write()
+ * assumes that all buffers on the page are started at the same time.
+ *
+ * The fix is two passes across the ioend list - one to start writeback on the
+ * bufferheads, and then the second one submit them for I/O.
  */
 STATIC void
 xfs_submit_ioend(
        xfs_ioend_t             *ioend)
 {
+       xfs_ioend_t             *head = ioend;
        xfs_ioend_t             *next;
        struct buffer_head      *bh;
        struct bio              *bio;
        sector_t                lastblock = 0;
 
+       /* Pass 1 - start writeback */
+       do {
+               next = ioend->io_list;
+               for (bh = ioend->io_buffer_head; bh; bh = bh->b_private) {
+                       xfs_start_buffer_writeback(bh);
+               }
+       } while ((ioend = next) != NULL);
+
+       /* Pass 2 - submit I/O */
+       ioend = head;
        do {
                next = ioend->io_list;
                bio = NULL;
 
                for (bh = ioend->io_buffer_head; bh; bh = bh->b_private) {
-                       xfs_start_buffer_writeback(bh);
 
                        if (!bio) {
  retry: