generic: test race between appending AIO DIO and fallocate
authorDarrick J. Wong <darrick.wong@oracle.com>
Wed, 13 Nov 2019 02:44:16 +0000 (18:44 -0800)
committerEryu Guan <guaneryu@gmail.com>
Sun, 1 Dec 2019 15:13:14 +0000 (23:13 +0800)
Dave Chinner reports[1] that an appending AIO DIO write to the second
block of a zero-length file and an fallocate request to the first block
of the same file can race to set isize, with the user-visible end result
that the file size is set incorrectly to one block long.  Write a small
test to reproduce the results.

[1] https://lore.kernel.org/linux-xfs/20191029100342.GA41131@bfoster/T/

Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
Reviewed-by: Eryu Guan <guaneryu@gmail.com>
Signed-off-by: Eryu Guan <guaneryu@gmail.com>
.gitignore
src/aio-dio-regress/aio-dio-append-write-fallocate-race.c [new file with mode: 0644]
tests/generic/586 [new file with mode: 0755]
tests/generic/586.out [new file with mode: 0644]
tests/generic/group

index d83df7d..b0f96ca 100644 (file)
 /src/writemod
 /src/writev_on_pagefault
 /src/xfsctl
+/src/aio-dio-regress/aio-dio-append-write-fallocate-race
 /src/aio-dio-regress/aio-dio-append-write-read-race
 /src/aio-dio-regress/aio-dio-cow-race
 /src/aio-dio-regress/aio-dio-cycle-write
diff --git a/src/aio-dio-regress/aio-dio-append-write-fallocate-race.c b/src/aio-dio-regress/aio-dio-append-write-fallocate-race.c
new file mode 100644 (file)
index 0000000..091b047
--- /dev/null
@@ -0,0 +1,212 @@
+// SPDX-License-Identifier: GPL-2.0-or-newer
+/*
+ * Copyright (c) 2019 Oracle.
+ * All Rights Reserved.
+ *
+ * Race appending aio dio and fallocate to make sure we get the correct file
+ * size afterwards.
+ */
+#include <stdio.h>
+#include <pthread.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <libaio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <limits.h>
+
+static int fd;
+static int blocksize;
+
+static void *
+falloc_thread(
+       void            *p)
+{
+       int             ret;
+
+       ret = fallocate(fd, 0, 0, blocksize);
+       if (ret)
+               perror("falloc");
+
+       return NULL;
+}
+
+static int
+test(
+       const char      *fname,
+       unsigned int    iteration,
+       unsigned int    *passed)
+{
+       struct stat     sbuf;
+       pthread_t       thread;
+       io_context_t    ioctx = 0;
+       struct iocb     iocb;
+       struct iocb     *iocbp = &iocb;
+       struct io_event event;
+       char            *buf;
+       bool            wait_thread = false;
+       int             ret;
+
+       /* Truncate file, allocate resources for doing IO. */
+       fd = open(fname, O_DIRECT | O_RDWR | O_TRUNC | O_CREAT, 0644);
+       if (fd < 0) {
+               perror(fname);
+               return -1;
+       }
+
+       ret = fstat(fd, &sbuf);
+       if (ret) {
+               perror(fname);
+               goto out;
+       }
+       blocksize = sbuf.st_blksize;
+
+       ret = posix_memalign((void **)&buf, blocksize, blocksize);
+       if (ret) {
+               errno = ret;
+               perror("buffer");
+               goto out;
+       }
+       memset(buf, 'X', blocksize);
+       memset(&event, 0, sizeof(event));
+
+       ret = io_queue_init(1, &ioctx);
+       if (ret) {
+               errno = -ret;
+               perror("io_queue_init");
+               goto out_buf;
+       }
+
+       /*
+        * Set ourselves up to race fallocate(0..blocksize) with aio dio
+        * pwrite(blocksize..blocksize * 2).  This /should/ give us a file
+        * with length (2 * blocksize).
+        */
+       io_prep_pwrite(&iocb, fd, buf, blocksize, blocksize);
+
+       ret = pthread_create(&thread, NULL, falloc_thread, NULL);
+       if (ret) {
+               errno = ret;
+               perror("pthread");
+               goto out_io;
+       }
+       wait_thread = true;
+
+       ret = io_submit(ioctx, 1, &iocbp);
+       if (ret != 1) {
+               errno = -ret;
+               perror("io_submit");
+               goto out_join;
+       }
+
+       ret = io_getevents(ioctx, 1, 1, &event, NULL);
+       if (ret != 1) {
+               errno = -ret;
+               perror("io_getevents");
+               goto out_join;
+       }
+
+       if (event.res < 0) {
+               errno = -event.res;
+               perror("io_event.res");
+               goto out_join;
+       }
+
+       if (event.res2 < 0) {
+               errno = -event.res2;
+               perror("io_event.res2");
+               goto out_join;
+       }
+
+       wait_thread = false;
+       ret = pthread_join(thread, NULL);
+       if (ret) {
+               errno = ret;
+               perror("join");
+               goto out_io;
+       }
+
+       /* Make sure we actually got a file of size (2 * blocksize). */
+       ret = fstat(fd, &sbuf);
+       if (ret) {
+               perror(fname);
+               goto out_buf;
+       }
+
+       if (sbuf.st_size != 2 * blocksize) {
+               fprintf(stderr, "[%u]: sbuf.st_size=%llu, expected %llu.\n",
+                               iteration,
+                               (unsigned long long)sbuf.st_size,
+                               (unsigned long long)2 * blocksize);
+       } else {
+               printf("[%u]: passed.\n", iteration);
+               (*passed)++;
+       }
+
+out_join:
+       if (wait_thread) {
+               ret = pthread_join(thread, NULL);
+               if (ret) {
+                       errno = ret;
+                       perror("join");
+                       goto out_io;
+               }
+       }
+out_io:
+       ret = io_queue_release(ioctx);
+       if (ret) {
+               errno = -ret;
+               perror("io_queue_release");
+       }
+
+out_buf:
+       free(buf);
+out:
+       ret = close(fd);
+       fd = -1;
+       if (ret) {
+               perror("close");
+               return -1;
+       }
+
+       return 0;
+}
+
+int main(int argc, char *argv[])
+{
+       int             ret;
+       long            l;
+       unsigned int    i;
+       unsigned int    passed = 0;
+
+       if (argc != 3) {
+               printf("Usage: %s filename iterations\n", argv[0]);
+               return 1;
+       }
+
+       errno = 0;
+       l = strtol(argv[2], NULL, 0);
+       if (errno) {
+               perror(argv[2]);
+               return 1;
+       }
+       if (l < 1 || l > UINT_MAX) {
+               fprintf(stderr, "%ld: must be between 1 and %u.\n",
+                               l, UINT_MAX);
+               return 1;
+       }
+
+       for (i = 0; i < l; i++) {
+               ret = test(argv[1], i, &passed);
+               if (ret)
+                       return 1;
+       }
+
+       printf("pass rate: %u/%u (%.2f%%)\n", passed, i, 100.0 * passed / i);
+
+       return 0;
+}
diff --git a/tests/generic/586 b/tests/generic/586
new file mode 100755 (executable)
index 0000000..62cd579
--- /dev/null
@@ -0,0 +1,44 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (c) 2019, Oracle and/or its affiliates.  All Rights Reserved.
+#
+# FS QA Test No. 586
+#
+# Race an appending aio dio write to the second block of a file while
+# simultaneously fallocating to the first block.  Make sure that we end up
+# with a two-block file.
+
+seq=`basename $0`
+seqres=$RESULT_DIR/$seq
+echo "QA output created by $seq"
+
+here=`pwd`
+tmp=/tmp/$$
+status=1    # failure is the default!
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+_cleanup()
+{
+       cd /
+       rm -f $tmp.* $testfile
+}
+
+# get standard environment, filters and checks
+. ./common/rc
+
+# real QA test starts here
+_supported_os Linux
+_supported_fs generic
+_require_aiodio "aio-dio-append-write-fallocate-race"
+_require_test
+_require_xfs_io_command "falloc"
+
+rm -f $seqres.full
+
+testfile=$TEST_DIR/test-$seq
+$AIO_TEST $testfile 100 >> $seqres.full
+
+echo Silence is golden.
+# success, all done
+status=0
+exit
diff --git a/tests/generic/586.out b/tests/generic/586.out
new file mode 100644 (file)
index 0000000..a610e1b
--- /dev/null
@@ -0,0 +1,2 @@
+QA output created by 586
+Silence is golden.
index e5d0c1d..3684880 100644 (file)
 583 auto quick encrypt
 584 auto quick encrypt
 585 auto rename
+586 auto quick rw prealloc