generic: test race between appending AIO DIO and fallocate

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>
This commit is contained in:
Darrick J. Wong
2019-11-12 18:44:16 -08:00
committed by Eryu Guan
parent 5e6b248c66
commit d38fc1527d
5 changed files with 260 additions and 0 deletions
+1
View File
@@ -155,6 +155,7 @@
/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
@@ -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;
}
+44
View File
@@ -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
+2
View File
@@ -0,0 +1,2 @@
QA output created by 586
Silence is golden.
+1
View File
@@ -588,3 +588,4 @@
583 auto quick encrypt
584 auto quick encrypt
585 auto rename
586 auto quick rw prealloc