]> git.proxmox.com Git - mirror_zfs.git/commitdiff
ZTS: Add dirty dnode stress test
authorTony Hutter <hutter2@llnl.gov>
Mon, 11 Dec 2023 17:59:59 +0000 (09:59 -0800)
committerGitHub <noreply@github.com>
Mon, 11 Dec 2023 17:59:59 +0000 (09:59 -0800)
Add a test for the dirty dnode SEEK_HOLE/SEEK_DATA bug described in
https://github.com/openzfs/zfs/issues/15526

The bug was fixed in https://github.com/openzfs/zfs/pull/15571 and
was backported to 2.2.2 and 2.1.14.  This test case is just to
make sure it does not come back.

seekflood.c originally written by Rob Norris.

Reviewed-by: Graham Perrin <grahamperrin@freebsd.org>
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed-by: Rob Norris <robn@despairlabs.com>
Signed-off-by: Tony Hutter <hutter2@llnl.gov>
Closes #15608

tests/runfiles/common.run
tests/zfs-tests/Makefile.am
tests/zfs-tests/tests/Makefile.am
tests/zfs-tests/tests/functional/cp_files/.gitignore [new file with mode: 0644]
tests/zfs-tests/tests/functional/cp_files/cp_stress.ksh [new file with mode: 0755]
tests/zfs-tests/tests/functional/cp_files/seekflood.c [new file with mode: 0644]

index dd44b63aaf3d9e6bd9a726641da39fa47c575e81..8c1f0ad0b14e97d06682b62b5d55a98c1cb00b20 100644 (file)
@@ -598,7 +598,7 @@ tests = ['compress_001_pos', 'compress_002_pos', 'compress_003_pos',
 tags = ['functional', 'compression']
 
 [tests/functional/cp_files]
-tests = ['cp_files_001_pos']
+tests = ['cp_files_001_pos', 'cp_stress']
 tags = ['functional', 'cp_files']
 
 [tests/functional/crtime]
index f8166352489e59e5732e3fb442167f7405159ca2..3dd1a6452728e4fc41c0a41557334fd9e36bcc39 100644 (file)
@@ -13,6 +13,9 @@ scripts_zfs_tests_functional_hkdf_PROGRAMS = %D%/tests/functional/hkdf/hkdf_test
 %C%_tests_functional_hkdf_hkdf_test_LDADD = \
        libzpool.la
 
+scripts_zfs_tests_functional_cp_filesdir = $(datadir)/$(PACKAGE)/zfs-tests/tests/functional/cp_files
+scripts_zfs_tests_functional_cp_files_PROGRAMS = %D%/tests/functional/cp_files/seekflood
+
 if BUILD_LINUX
 scripts_zfs_tests_functional_tmpfiledir = $(datadir)/$(PACKAGE)/zfs-tests/tests/functional/tmpfile
 scripts_zfs_tests_functional_tmpfile_PROGRAMS = \
index a2ffd583faa088eb6925e3739cd11e1d3e44cec3..3c9f0938242478fb979bbd907b7d40447b131627 100644 (file)
@@ -1369,6 +1369,7 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \
        functional/compression/setup.ksh \
        functional/cp_files/cleanup.ksh \
        functional/cp_files/cp_files_001_pos.ksh \
+       functional/cp_files/cp_stress.ksh \
        functional/cp_files/setup.ksh \
        functional/crtime/cleanup.ksh \
        functional/crtime/crtime_001_pos.ksh \
diff --git a/tests/zfs-tests/tests/functional/cp_files/.gitignore b/tests/zfs-tests/tests/functional/cp_files/.gitignore
new file mode 100644 (file)
index 0000000..d15225a
--- /dev/null
@@ -0,0 +1 @@
+seekflood
diff --git a/tests/zfs-tests/tests/functional/cp_files/cp_stress.ksh b/tests/zfs-tests/tests/functional/cp_files/cp_stress.ksh
new file mode 100755 (executable)
index 0000000..43bb8ab
--- /dev/null
@@ -0,0 +1,73 @@
+#! /bin/ksh -p
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or https://opensource.org/licenses/CDDL-1.0.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+
+#
+# Copyright (c) 2023 by Lawrence Livermore National Security, LLC.
+#
+
+. $STF_SUITE/include/libtest.shlib
+
+#
+# DESCRIPTION:
+#
+# https://github.com/openzfs/zfs/issues/15526 identified a dirty dnode
+# SEEK_HOLE/SEEK_DATA bug.  https://github.com/openzfs/zfs/pull/15571
+# fixed the bug, and was backported to 2.1.14 and 2.2.2.
+#
+# This test is to ensure that the bug, as understood, will not recur.
+#
+# STRATEGY:
+#
+# 1. Run the 'seekflood' binary, for creation of files with timing
+#    characteristics that can trigger #15526.
+# 2. A single run is not always a trigger, so run repeatedly.
+
+verify_runnable "global"
+
+function cleanup
+{
+       rm -rf /$TESTDIR/cp_stress
+}
+
+log_assert "Run the 'seekflood' binary repeatedly to try to trigger #15526"
+
+log_onexit cleanup
+
+log_must mkdir /$TESTPOOL/cp_stress
+
+MYPWD="$PWD"
+cd /$TESTPOOL/cp_stress
+CPUS=$(get_num_cpus)
+
+if is_freebsd ; then
+       # 'seekflood' takes longer on FreeBSD and can timeout the test
+       RUNS=3
+else
+       RUNS=10
+fi
+
+for i in $(seq 1 $RUNS) ; do
+       # Each run takes around 12 seconds.
+       log_must $STF_SUITE/tests/functional/cp_files/seekflood 2000 $CPUS
+done
+cd "$MYPWD"
+
+log_pass "No corruption detected"
diff --git a/tests/zfs-tests/tests/functional/cp_files/seekflood.c b/tests/zfs-tests/tests/functional/cp_files/seekflood.c
new file mode 100644 (file)
index 0000000..02c2c8e
--- /dev/null
@@ -0,0 +1,180 @@
+/*
+ * SPDX-License-Identifier: MIT
+ *
+ * Copyright (c) 2023, Rob Norris <robn@despairlabs.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ */
+
+#ifndef        _GNU_SOURCE
+#define        _GNU_SOURCE
+#endif
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#define        DATASIZE        (4096)
+char data[DATASIZE];
+
+static int
+_open_file(int n, int wr)
+{
+       char buf[256];
+       int fd;
+
+       snprintf(buf, sizeof (buf), "testdata_%d_%d", getpid(), n);
+
+       if ((fd = open(buf, wr ? (O_WRONLY | O_CREAT) : O_RDONLY,
+           wr ? (S_IRUSR | S_IWUSR) : 0)) < 0) {
+               fprintf(stderr, "Error: open '%s' (%s): %s\n",
+                   buf, wr ? "write" : "read", strerror(errno));
+               exit(1);
+       }
+
+       return (fd);
+}
+
+static void
+_write_file(int n, int fd)
+{
+       /* write a big ball of stuff */
+       ssize_t nwr = write(fd, data, DATASIZE);
+       if (nwr < 0) {
+               fprintf(stderr, "Error: write '%d_%d': %s\n",
+                   getpid(), n, strerror(errno));
+               exit(1);
+       } else if (nwr < DATASIZE) {
+               fprintf(stderr, "Error: write '%d_%d': short write\n", getpid(),
+                   n);
+               exit(1);
+       }
+}
+
+static int
+_seek_file(int n, int fd)
+{
+       struct stat st;
+       if (fstat(fd, &st) < 0) {
+               fprintf(stderr, "Error: fstat '%d_%d': %s\n", getpid(), n,
+                   strerror(errno));
+               exit(1);
+       }
+
+       /*
+        * A zero-sized file correctly has no data, so seeking the file is
+        * pointless.
+        */
+       if (st.st_size == 0)
+               return (0);
+
+       /* size is real, and we only write, so SEEK_DATA must find something */
+       if (lseek(fd, 0, SEEK_DATA) < 0) {
+               if (errno == ENXIO)
+                       return (1);
+               fprintf(stderr, "Error: lseek '%d_%d': %s\n",
+                   getpid(), n, strerror(errno));
+               exit(2);
+       }
+
+       return (0);
+}
+
+int
+main(int argc, char **argv)
+{
+       int nfiles = 0;
+       int nthreads = 0;
+
+       if (argc < 3 || (nfiles = atoi(argv[1])) == 0 ||
+           (nthreads = atoi(argv[2])) == 0) {
+               printf("usage: seekflood <nfiles> <threads>\n");
+               exit(1);
+       }
+
+       memset(data, 0x5a, DATASIZE);
+
+       /* fork off some flood threads */
+       for (int i = 0; i < nthreads; i++) {
+               if (!fork()) {
+                       /* thread main */
+
+                       /* create zero file */
+                       int fd = _open_file(0, 1);
+                       _write_file(0, fd);
+                       close(fd);
+
+                       int count = 0;
+
+                       int h = 0, i, j, rfd, wfd;
+                       for (i = 0; i < nfiles; i += 2, h++) {
+                               j = i+1;
+
+                               /* seek h, write i */
+                               rfd = _open_file(h, 0);
+                               wfd = _open_file(i, 1);
+                               count += _seek_file(h, rfd);
+                               _write_file(i, wfd);
+                               close(rfd);
+                               close(wfd);
+
+                               /* seek i, write j */
+                               rfd = _open_file(i, 0);
+                               wfd = _open_file(j, 1);
+                               count += _seek_file(i, rfd);
+                               _write_file(j, wfd);
+                               close(rfd);
+                               close(wfd);
+                       }
+
+                       /* return count of failed seeks to parent */
+                       exit(count < 256 ? count : 255);
+               }
+       }
+
+       /* wait for threads, take their seek fail counts from exit code */
+       int count = 0, crashed = 0;
+       for (int i = 0; i < nthreads; i++) {
+               int wstatus;
+               wait(&wstatus);
+               if (WIFEXITED(wstatus))
+                       count += WEXITSTATUS(wstatus);
+               else
+                       crashed++;
+       }
+
+       if (crashed) {
+               fprintf(stderr, "Error: child crashed; test failed\n");
+               exit(1);
+       }
+
+       if (count) {
+               fprintf(stderr, "Error: %d seek failures; test failed\n",
+                   count);
+               exit(1);
+       }
+
+       exit(0);
+}