Add more tests for multi fs block atomic writes
v5 cover Add more tests for multi fs block atomic writes
Changes in v5: (Thanks to John & Darrick for reviews)
- commor/rc: Add a _require_fio_version helper
- fsx: Switch atomic writes off if direct IO (-Z) not passed
- fio tests: better commit messages to explain what we are testing
- ext4/06{1..2}: Refactor code, also test only a few combinations of bs clustersize rather than every single
Changes in v4: (Thanks to Darrick, John and Zorro for the reviews) [4]
- g/1226,1227: Modify fio threads to not issue overlapping atomic writes
- g/1228: Use xfs_io -c "shutdown" instead of _scratch_shutdown to avoid bash overhead
- g/1229: Remove FSX_AVOID handling for bigalloc from common/rc. It is part of the specific test now
- ext4/063: add more clearer extent diagram
- ext4/064: Drop the test for now as im taking sometime to understand the behavior better.
- Removed test numbers from commit message
- For tests with significant changes I've removed the RVBs
[4] https://lore.kernel.org/fstests/[email protected]/T/#mef34a8c13cbee466bfc162db637d6e1cf0a8b06d
Changes in v3 [3]:
- (2/13) use dumpe2fs to figure out if FS is bigalloc
- (9/13) generic/1230: Detect device speeds for more accurate testing. ALso speeds up the test
- fio tests - switch to write followed by verify approach to avoid false failures due to fio verify reads splitting and racing with atomic writes. Discussion thread:
https://lore.kernel.org/fstests/[email protected]/
[3] https://lore.kernel.org/fstests/[email protected]/
Changes in v2 [1]:
- (1/13) new patch with _min and _max helpers
- (2/13) remove setup_fs_options and add fsx specifc helper
- (4/13) skip atomic write instead of falling back to normal write (fsx)
- (4/13) make atomic write default on instead of default off (fsx)
- (5,6/13) refactor and cleanup fio tests
- (7/13) refactored common code
- (8/13) dont ignore mmap writes for fsx with atomic writes
- (9/13) use od instead of xxd. handle cleanup of bg threads in _cleanup()
- (10-13/13) minor refactors
- change all tests use _fail for better consistency
- use higher tests numbers for easier merging
[1] https://lore.kernel.org/fstests/[email protected]/
* Original cover [2] *
These are the tests we were using to verify that filesystems are not tearing multi fs block atomic writes. Infact some of the tests like generic/772 (now: g/1230) actually helped us catch and fix issues in ext4's early implementations of multi fs block atomic writes and hence we feel these tests are useful to have in xfstests.
We have tested these with scsi debug as well as a real nvme device supporting multi fs block atomic writes.
Thoughts and suggestions are welcome!
[2] rfc: https://lore.kernel.org/fstests/[email protected]/
Ojaswin Mujoo (10):
common/rc: Add _min() and _max() helpers
common/rc: Add _require_fio_version helper
common/rc: Add a helper to run fsx on a given file ltp/fsx.c: Add atomic writes support to fsx
generic: Add atomic write test using fio crc check verifier
generic: Add atomic write test using fio verify on file mixed mappings
generic: Add atomic write multi-fsblock O_[D]SYNC tests
generic: Stress fsx with atomic writes enabled
generic: Add sudden shutdown tests for multi block atomic writes
ext4: Atomic write test for extent split across leaf nodes
Ritesh Harjani (IBM) (2):
ext4: test atomic write and ioend codepaths with bigalloc
ext4: Test atomic writes allocation and write codepaths with bigalloc
common/rc | 77 +++++++- ltp/fsx.c | 115 +++++++++++- tests/ext4/061 | 155 ++++++++++++++++ tests/ext4/061.out | 2 + tests/ext4/062 | 203 +++++++++++++++++++++ tests/ext4/062.out | 2 + tests/ext4/063 | 129 +++++++++++++ tests/ext4/063.out | 2 + tests/generic/1226 | 108 +++++++++++ tests/generic/1226.out | 2 + tests/generic/1227 | 132 ++++++++++++++ tests/generic/1227.out | 2 + tests/generic/1228 | 137 ++++++++++++++ tests/generic/1228.out | 2 + tests/generic/1229 | 68 +++++++ tests/generic/1229.out | 2 + tests/generic/1230 | 397 +++++++++++++++++++++++++++++++++++++++++ tests/generic/1230.out | 2 + 18 files changed, 1529 insertions(+), 8 deletions(-) create mode 100755 tests/ext4/061 create mode 100644 tests/ext4/061.out create mode 100755 tests/ext4/062 create mode 100644 tests/ext4/062.out create mode 100755 tests/ext4/063 create mode 100644 tests/ext4/063.out create mode 100755 tests/generic/1226 create mode 100644 tests/generic/1226.out create mode 100755 tests/generic/1227 create mode 100644 tests/generic/1227.out create mode 100755 tests/generic/1228 create mode 100644 tests/generic/1228.out create mode 100755 tests/generic/1229 create mode 100644 tests/generic/1229.out create mode 100755 tests/generic/1230 create mode 100644 tests/generic/1230.out
Signature · 1 line
2.49.0
PATCH v5 1/12 common/rc: Add _min() and _max() helpers
Many programs open code these functionalities so add it as a generic helper in common/rc
--- common/rc | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+)
common/rc +22 -0
@@ -5975,6 +5975,28 @@ _require_inplace_writes() fi } +_min() { + local ret + + for arg in "$@"; do + if [ -z "$ret" ] || (( $arg < $ret )); then + ret="$arg" + fi + done + echo $ret +} + +_max() { + local ret + + for arg in "$@"; do + if [ -z "$ret" ] || (( $arg > $ret )); then + ret="$arg" + fi + done + echo $ret +} + ################################################################################ # make sure this script returns success /bin/true
Signature · 1 line
2.49.0
PATCH v5 4/12 ltp/fsx.c: Add atomic writes support to fsx
In reply to Ojaswin MujooImplement atomic write support to help fuzz atomic writes with fsx.
--- ltp/fsx.c | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 110 insertions(+), 5 deletions(-)
ltp/fsx.c +110 -5
@@ -40,6 +40,7 @@#include <liburing.h> #endif #include <sys/syscall.h> +#include "statx.h" #ifndef MAP_FILE # define MAP_FILE 0
@@ -49,6 +50,10 @@#define RWF_DONTCACHE 0x80#endif +#ifndef RWF_ATOMIC +#define RWF_ATOMIC 0x40 +#endif +#define NUMPRINTCOLUMNS 32/* # columns of data to print on each line */ /* Operation flags (bitmask) */
@@ -110,6 +115,7 @@ enum {OP_READ_DONTCACHE ,OP_WRITE ,OP_WRITE_DONTCACHE , +OP_WRITE_ATOMIC ,OP_MAPREAD ,OP_MAPWRITE , OP_MAX_LITE,
@@ -200,6 +206,11 @@ int uring = 0;int mark_nr =0 ;int dontcache_io =1 ;int hugepages =0 ;/* -h flag */ +int do_atomic_writes =1 ;/* -a flag disables */ + +/* User for atomic writes */ +int awu_min =0 ; +int awu_max =0 ;/* Stores info needed to periodically collapse hugepages */ struct hugepages_collapse_info {
@@ -288,6 +299,7 @@ static const char *op_names[] = { [OP_READ_DONTCACHE] = "read_dontcache", [OP_WRITE] = "write", [OP_WRITE_DONTCACHE] = "write_dontcache", + [OP_WRITE_ATOMIC] = "write_atomic", [OP_MAPREAD] = "mapread", [OP_MAPWRITE] = "mapwrite", [OP_TRUNCATE] = "truncate",
@@ -422,6 +434,7 @@ logdump(void)prt ("\t***RRRR***" );break ;case OP_WRITE_DONTCACHE : +case OP_WRITE_ATOMIC :case OP_WRITE : prt("WRITE 0x%x thru 0x%x\t(0x%x bytes)", lp->args[0], lp->args[0] + lp->args[1] - 1,
@@ -1073,6 +1086,25 @@ update_file_size(unsigned offset, unsigned size) file_size = offset + size; } +static int is_power_of_2 (unsigned n) { +return ((n & (n -1 )) ==0 ); +} + +/* +* Round down n to nearest power of 2. +* If n is already a power of 2, return n; +*/ +static int rounddown_pow_of_2 (int n) { +int i =0 ; + +if (is_power_of_2 (n)) +return n; + +for (; (1 << i) < n; i++); + +return 1 << (i -1 ); +} +void dowrite (unsigned offset,unsigned size,int flags) {
@@ -1081,6 +1113,27 @@ dowrite(unsigned offset, unsigned size, int flags) offset -= offset % writebdy;if (o_direct) size -= size % writebdy; +if (flags &RWF_ATOMIC ) { +/* atomic write len must be inbetween awu_min and awu_max */ +if (size < awu_min) + size = awu_min; +if (size > awu_max) + size = awu_max; + +/* atomic writes need power-of-2 sizes */ + size =rounddown_pow_of_2 (size); + +/* atomic writes need naturally aligned offsets */ + offset -= offset % size; + +/* Skip the write if we are crossing max filesize */ +if ((offset + size) > maxfilelen) { +if (!quiet && testcalls > simulatedopcount) +prt ("skipping atomic write past maxfilelen\n" ); +log4 (OP_WRITE_ATOMIC , offset, size,FL_SKIPPED ); +return ; + } + }if (size ==0 ) {if (!quiet && testcalls > simulatedopcount && !o_direct)prt ("skipping zero size write\n" );
@@ -1088,7 +1141,10 @@ dowrite(unsigned offset, unsigned size, int flags)return ; } -log4 (OP_WRITE , offset, size,FL_NONE ); +if (flags &RWF_ATOMIC ) +log4 (OP_WRITE_ATOMIC , offset, size,FL_NONE ); +else +log4 (OP_WRITE , offset, size,FL_NONE );gendata (original_buf, good_buf, offset, size);if (offset + size > file_size) {
@@ -1108,8 +1164,9 @@ dowrite(unsigned offset, unsigned size, int flags) (monitorstart == -1 || (offset + size > monitorstart && (monitorend == -1 || offset <= monitorend)))))) -prt ("%lld write\t0x%x thru\t0x%x\t(0x%x bytes)\tdontcache=%d\n" , testcalls, - offset, offset + size -1 , size, (flags &RWF_DONTCACHE ) !=0 ); +prt ("%lld write\t0x%x thru\t0x%x\t(0x%x bytes)\tdontcache=%d atomic_wr=%d\n" , testcalls, + offset, offset + size -1 , size, (flags &RWF_DONTCACHE ) !=0 , + (flags &RWF_ATOMIC ) !=0 ); iret =fsxwrite (fd, good_buf + offset, size, offset, flags);if (iret != size) {if (iret ==-1 )
@@ -1785,6 +1842,36 @@ do_dedupe_range(unsigned offset, unsigned length, unsigned dest) }#endif +int test_atomic_writes (void ) { +int ret; +struct statx stx; + +if (o_direct !=O_DIRECT ) { +fprintf (stderr,"main: atomic writes need O_DIRECT (-Z), " +"disabling!\n" ); +return 0 ; + } + + ret =xfstests_statx (AT_FDCWD , fname,0 ,STATX_WRITE_ATOMIC , &stx); +if (ret <0 ) { +fprintf (stderr,"main: Statx failed with %d." +" Failed to determine atomic write limits, " +" disabling!\n" , ret); +return 0 ; + } + +if (stx.stx_attributes &STATX_ATTR_WRITE_ATOMIC && + stx.stx_atomic_write_unit_min >0 ) { + awu_min = stx.stx_atomic_write_unit_min; + awu_max = stx.stx_atomic_write_unit_max; +return 1 ; + } + +fprintf (stderr,"main: IO Stack does not support " +"atomic writes, disabling!\n" ); +return 0 ; +} +#ifdef HAVE_COPY_FILE_RANGE int test_copy_range (void )
@@ -2356,6 +2443,12 @@ have_op: goto out; }break ; +case OP_WRITE_ATOMIC : +if (!do_atomic_writes) { +log4 (OP_WRITE_ATOMIC , offset, size,FL_SKIPPED ); + goto out; + } +break ; }switch (op) {
@@ -2385,6 +2478,11 @@ have_op:dowrite (offset, size,0 );break ; +case OP_WRITE_ATOMIC : +TRIM_OFF_LEN (offset, size, maxfilelen); +dowrite (offset, size,RWF_ATOMIC ); +break ; +case OP_MAPREAD :TRIM_OFF_LEN (offset, size, file_size);domapread (offset, size);
@@ -2511,13 +2609,14 @@ voidusage (void) { fprintf(stdout, "usage: %s", - "fsx [-dfhknqxyzBEFHIJKLORWXZ0]\n\ + "fsx [-adfhknqxyzBEFHIJKLORWXZ0]\n\ [-b opnum] [-c Prob] [-g filldata] [-i logdev] [-j logid]\n\ [-l flen] [-m start:end] [-o oplen] [-p progressinterval]\n\ [-r readbdy] [-s style] [-t truncbdy] [-w writebdy]\n\ [-A|-U] [-D startingop] [-N numops] [-P dirpath] [-S seed]\n\ [--replay-ops=opsfile] [--record-ops[=opsfile]] [--duration=seconds]\n\ ... fname\n\ + -a: disable atomic writes\n\ -b opnum: beginning operation number (default 1)\n\ -c P: 1 in P chance of file close+open at each op (default infinity)\n\ -d: debug output for all operations\n\
@@ -3059,9 +3158,13 @@ main(int argc, char **argv)setvbuf (stdout, (char *)0 , _IOLBF,0 );/* line buffered stdout */ while ((ch =getopt_long (argc, argv, -"0b:c:de:fg:hi:j:kl:m:no:p:qr:s:t:uw:xyABD:EFJKHzCILN:OP:RS:UWXZ" , +"0ab:c:de:fg:hi:j:kl:m:no:p:qr:s:t:uw:xyABD:EFJKHzCILN:OP:RS:UWXZ" , longopts,NULL )) !=EOF )switch (ch) { +case 'a' : +prt ("main(): Atomic writes disabled\n" ); + do_atomic_writes =0 ; +break ;case 'b' : simulatedopcount =getnum (optarg, &endp);if (!quiet)
@@ -3475,6 +3578,8 @@ main(int argc, char **argv) exchange_range_calls =test_exchange_range ();if (dontcache_io) dontcache_io =test_dontcache_io (); +if (do_atomic_writes) + do_atomic_writes =test_atomic_writes ();while (keep_running ())if (!test ())
Signature · 1 line
2.49.0
PATCH v5 2/12 common/rc: Add _require_fio_version helper
In reply to Ojaswin MujooThe main motivation of adding this function on top of _require_fio is that there has been a case in fio where atomic= option was added but later it was changed to noop since kernel didn't yet have support for atomic writes. It was then again utilized to do atomic writes in a later version, once kernel got the support. Due to this there is a point in fio where _require_fio w/ atomic=1 will succeed even though it would not be doing atomic writes.
Hence, add an explicit helper to ensure tests to require specific versions of fio to work past such issues.
--- common/rc | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+)
common/rc +32 -0
@@ -5997,6 +5997,38 @@ _max() { echo $ret } +# Check the required fio version. Examples: +# _require_fio_version 3.38 (matches 3.38 only) +# _require_fio_version 3.38+ (matches 3.38 and above) +# _require_fio_version 3.38- (matches 3.38 and below) +_require_fio_version() { + local req_ver="$1" + local fio_ver + + _require_fio + _require_math + + fio_ver=$(fio -v | cut -d"-" -f2) + + case "$req_ver" in + *+) + req_ver=${req_ver%+} + test $(_math "$fio_ver >= $req_ver") -eq 1 || \ + _notrun "need fio >= $req_ver (found $fio_ver)" + ;; + *-) + req_ver=${req_ver%-} + test $(_math "$fio_ver <= $req_ver") -eq 1 || \ + _notrun "need fio <= $req_ver (found $fio_ver)" + ;; + *) + req_ver=${req_ver%-} + test $(_math "$fio_ver == $req_ver") -eq 1 || \ + _notrun "need fio = $req_ver (found $fio_ver)" + ;; + esac +} + ################################################################################ # make sure this script returns success /bin/true
Signature · 1 line
2.49.0
PATCH v5 3/12 common/rc: Add a helper to run fsx on a given file
In reply to Ojaswin MujooCurrently run_fsx is hardcoded to run on a file in $TEST_DIR. Add a helper _run_fsx_on_file so that we can run fsx on any given file including in $SCRATCH_MNT. Also, refactor _run_fsx to use this helper.
No functional change is intended in this patch.
--- common/rc | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-)
common/rc +20 -3
@@ -5200,13 +5200,24 @@ _require_hugepage_fsx() _notrun "fsx binary does not support MADV_COLLAPSE" } -_run_fsx() +_run_fsx_on_file() { + local testfile=$1 + shift + + if ! [ -f $testfile ] + then + echo "_run_fsx_on_file: $testfile doesn't exist. Creating" >> $seqres.full + touch $testfile + fi + echo "fsx $*" local args=`echo $@ | sed -e "s/ BSIZE / $bsize /g" -e "s/ PSIZE / $psize /g"` - set -- $FSX_PROG $args $FSX_AVOID $TEST_DIR/junk + + set -- $FSX_PROG $args $FSX_AVOID $testfile + echo "$@" >>$seqres.full - rm -f $TEST_DIR/junk + rm -f $testfile "$@" 2>&1 | tee -a $seqres.full >$tmp.fsx local res=${PIPESTATUS[0]} if [ $res -ne 0 ]; then
@@ -5218,6 +5229,12 @@ _run_fsx() return 0 } +_run_fsx() +{ + _run_fsx_on_file $TEST_DIR/junk $@ + return $? +} + # Run fsx with -h(ugepage buffers). If we can't set up a hugepage then skip # the test, but if any other error occurs then exit the test. _run_hugepage_fsx() {
Signature · 1 line
2.49.0
PATCH v5 5/12 generic: Add atomic write test using fio crc check verifier
In reply to Ojaswin MujooThis adds atomic write test using fio based on it's crc check verifier. fio adds a crc header for each data block, which is verified later to ensure there is no data corruption or torn write.
This test essentially does a lot of parallel RWF_ATOMIC IO on a preallocated file to stress the write and end-io unwritten conversion code paths. The idea is to increase code coverage to ensure RWF_ATOMIC hasn't introduced any issues.
Avoid doing overlapping parallel atomic writes because it might give unexpected results. Use offset_increment=, size= fio options to achieve this behavior.
--- tests/generic/1226 | 108 +++++++++++++++++++++++++++++++++++++++++ tests/generic/1226.out | 2 + 2 files changed, 110 insertions(+) create mode 100755 tests/generic/1226 create mode 100644 tests/generic/1226.out
tests/generic/1226 new file +108 -0
@@ -0,0 +1,108 @@ +#! /bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2025 IBM Corporation. All Rights Reserved. +# +# FS QA Test 1226 +# +# Validate FS atomic write using fio crc check verifier. +# +. ./common/preamble +. ./common/atomicwrites + +_begin_fstest auto aio rw atomicwrites + +_require_scratch_write_atomic +_require_odirect +_require_aio +_require_fio_version "3.38+" + +_scratch_mkfs >> $seqres.full 2>&1 +_scratch_mount +_require_xfs_io_command "falloc" + +touch "$SCRATCH_MNT/f1" +awu_min_write=$(_get_atomic_write_unit_min "$SCRATCH_MNT/f1") +awu_max_write=$(_get_atomic_write_unit_max "$SCRATCH_MNT/f1") + +blocksize=$(_max "$awu_min_write" "$((awu_max_write/2))") +threads=$(_min "$(($(nproc) * 2 * LOAD_FACTOR))" "100") +filesize=$((blocksize * threads * 100)) +depth=$threads +io_size=$((filesize / threads)) +io_inc=$io_size +testfile=$SCRATCH_MNT/test-file + +fio_config=$tmp.fio +fio_out=$tmp.fio.out + +fio_aw_config=$tmp.aw.fio +fio_verify_config=$tmp.verify.fio + +function create_fio_configs() +{ + create_fio_aw_config + create_fio_verify_config +} + +function create_fio_verify_config() +{ +cat >$fio_verify_config <<EOF + [verify-job] + direct=1 + ioengine=libaio + rw=read + bs=$blocksize + filename=$testfile + size=$filesize + iodepth=$depth + group_reporting=1 + + verify_only=1 + verify=crc32c + verify_fatal=1 + verify_state_save=0 + verify_write_sequence=0 +EOF +} + +function create_fio_aw_config() +{ +cat >$fio_aw_config <<EOF + [atomicwrite-job] + direct=1 + ioengine=libaio + rw=randwrite + bs=$blocksize + filename=$testfile + size=$io_inc + offset_increment=$io_inc + iodepth=$depth + numjobs=$threads + group_reporting=1 + atomic=1 + + verify_state_save=0 + verify=crc32c + do_verify=0 +EOF +} + +create_fio_configs +_require_fio $fio_aw_config + +cat $fio_aw_config >> $seqres.full +cat $fio_verify_config >> $seqres.full + +$XFS_IO_PROG -fc "falloc 0 $filesize" $testfile >> $seqres.full + +$FIO_PROG $fio_aw_config >> $seqres.full +ret1=$? +$FIO_PROG $fio_verify_config >> $seqres.full +ret2=$? + +[[ $ret1 -eq 0 && $ret2 -eq 0 ]] || _fail "fio with atomic write failed" + +# success, all done +echo Silence is golden +status=0 +exit
tests/generic/1226.out new file +2 -0
@@ -0,0 +1,2 @@ +QA output created by 1226 +Silence is golden
Signature · 1 line
2.49.0
PATCH v5 6/12 generic: Add atomic write test using fio verify on file mixed mappings
In reply to Ojaswin MujooThis test uses fio to first create a file with mixed mappings. Then it does atomic writes using aio dio with parallel jobs to the same file with mixed mappings. Finally, we perform a fio verify step to ensure there is no data corruption or torn write.
The aim is to stress the FS block allocation and extent handling logic to ensure it handles mixed mappings with RWF_ATOMIC correctly without tearing or losing data.
Avoid doing overlapping parallel atomic writes because it might give unexpected results. Use offset_increment=, size= fio options to achieve this behavior.
--- tests/generic/1227 | 132 +++++++++++++++++++++++++++++++++++++++++ tests/generic/1227.out | 2 + 2 files changed, 134 insertions(+) create mode 100755 tests/generic/1227 create mode 100644 tests/generic/1227.out
tests/generic/1227 new file +132 -0
@@ -0,0 +1,132 @@ +#! /bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2025 IBM Corporation. All Rights Reserved. +# +# FS QA Test 1227 +# +# Validate FS atomic write using fio crc check verifier on mixed mappings +# of a file. +# +. ./common/preamble +. ./common/atomicwrites + +_begin_fstest auto aio rw atomicwrites + +_require_scratch_write_atomic_multi_fsblock +_require_odirect +_require_aio +_require_fio_version "3.38+" +_require_xfs_io_command "truncate" + +_scratch_mkfs >> $seqres.full 2>&1 +_scratch_mount + +touch "$SCRATCH_MNT/f1" +awu_min_write=$(_get_atomic_write_unit_min "$SCRATCH_MNT/f1") +awu_max_write=$(_get_atomic_write_unit_max "$SCRATCH_MNT/f1") + +aw_bsize=$(_max "$awu_min_write" "$((awu_max_write/4))") +fsbsize=$(_get_block_size $SCRATCH_MNT) + +threads=$(_min "$(($(nproc) * 2 * LOAD_FACTOR))" "100") +filesize=$((aw_bsize * threads * 100)) +depth=$threads +aw_io_size=$((filesize / threads)) +aw_io_inc=$aw_io_size +testfile=$SCRATCH_MNT/test-file + +fio_prep_config=$tmp.prep.fio +fio_aw_config=$tmp.aw.fio +fio_verify_config=$tmp.verify.fio +fio_out=$tmp.fio.out + +cat >$fio_prep_config <<EOF +# prep file to have mixed mappings +[global] +ioengine=libaio +filename=$testfile +size=$filesize +bs=$fsbsize +direct=1 +iodepth=$depth +group_reporting=1 + +# Create written extents +[prep_written_blocks] +ioengine=libaio +rw=randwrite +io_size=$((filesize/3)) +random_generator=lfsr + +# Create unwritten extents +[prep_unwritten_blocks] +ioengine=falloc +rw=randwrite +io_size=$((filesize/3)) +random_generator=lfsr +EOF + +cat >$fio_aw_config <<EOF +# atomic write to mixed mappings of written/unwritten/holes +[atomic_write_job] +ioengine=libaio +rw=randwrite +direct=1 +atomic=1 +random_generator=lfsr +group_reporting=1 + +filename=$testfile +bs=$aw_bsize +size=$aw_io_size +offset_increment=$aw_io_inc +iodepth=$depth +numjobs=$threads + +verify_state_save=0 +verify=crc32c +do_verify=0 +EOF + +cat >$fio_verify_config <<EOF +# verify atomic writes done by previous job +[verify_job] +ioengine=libaio +rw=read +random_generator=lfsr +group_reporting=1 + +filename=$testfile +size=$filesize +bs=$aw_bsize +iodepth=$depth + +verify_state_save=0 +verify_only=1 +verify=crc32c +verify_fatal=1 +verify_write_sequence=0 +EOF + +_require_fio $fio_aw_config +_require_fio $fio_verify_config + +cat $fio_prep_config >> $seqres.full +cat $fio_aw_config >> $seqres.full +cat $fio_verify_config >> $seqres.full + +$XFS_IO_PROG -fc "truncate $filesize" $testfile >> $seqres.full + +#prepare file with mixed mappings +$FIO_PROG $fio_prep_config >> $seqres.full + +# do atomic writes without verifying +$FIO_PROG $fio_aw_config >> $seqres.full + +# verify data is not torn +$FIO_PROG $fio_verify_config >> $seqres.full + +# success, all done +echo Silence is golden +status=0 +exit
tests/generic/1227.out new file +2 -0
@@ -0,0 +1,2 @@ +QA output created by 1227 +Silence is golden
Signature · 1 line
2.49.0
PATCH v5 7/12 generic: Add atomic write multi-fsblock O_[D]SYNC tests
In reply to Ojaswin MujooThis adds various atomic write multi-fsblock stresst tests with mixed mappings and O_SYNC, to ensure the data and metadata is atomically persisted even if there is a shutdown.
--- tests/generic/1228 | 137 +++++++++++++++++++++++++++++++++++++++++ tests/generic/1228.out | 2 + 2 files changed, 139 insertions(+) create mode 100755 tests/generic/1228 create mode 100644 tests/generic/1228.out
tests/generic/1228 new file +137 -0
@@ -0,0 +1,137 @@ +#! /bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2025 IBM Corporation. All Rights Reserved. +# +# FS QA Test 1228 +# +# Atomic write multi-fsblock data integrity tests with mixed mappings +# and O_SYNC +# +. ./common/preamble +. ./common/atomicwrites +_begin_fstest auto quick rw atomicwrites + +_require_scratch_write_atomic_multi_fsblock +_require_atomic_write_test_commands +_require_scratch_shutdown +_require_xfs_io_command "truncate" + +_scratch_mkfs >> $seqres.full +_scratch_mount >> $seqres.full + +check_data_integrity() { + actual=$(_hexdump $testfile) + if [[ "$expected" != "$actual" ]] + then + echo "Integrity check failed" + echo "Integrity check failed" >> $seqres.full + echo "# Expected file contents:" >> $seqres.full + echo "$expected" >> $seqres.full + echo "# Actual file contents:" >> $seqres.full + echo "$actual" >> $seqres.full + + _fail "Data integrity check failed. The atomic write was torn." + fi +} + +prep_mixed_mapping() { + $XFS_IO_PROG -c "truncate 0" $testfile >> $seqres.full + local off=0 + local mapping="" + + local operations=("W" "H" "U") + local num_blocks=$((awu_max / blksz)) + for ((i=0; i<num_blocks; i++)); do + local index=$((RANDOM % ${#operations[@]})) + local map="${operations[$index]}" + local mapping="${mapping}${map}" + + case "$map" in + "W") + $XFS_IO_PROG -dc "pwrite -S 0x61 -b $blksz $off $blksz" $testfile > /dev/null + ;; + "H") + # No operation needed for hole + ;; + "U") + $XFS_IO_PROG -c "falloc $off $blksz" $testfile >> /dev/null + ;; + esac + off=$((off + blksz)) + done + + echo "+ + Mixed mapping prep done. Full mapping pattern: $mapping" >> $seqres.full + + sync $testfile +} + +verify_atomic_write() { + test $bytes_written -eq $awu_max || _fail "atomic write len=$awu_max assertion failed" + check_data_integrity +} + +mixed_mapping_test() { + prep_mixed_mapping + + echo -"+ + Performing O_DSYNC atomic write from 0 to $awu_max" >> $seqres.full + if [[ "$1" == "shutdown" ]] + then + bytes_written=$($XFS_IO_PROG -x -dc \ + "pwrite -DA -V1 -b $awu_max 0 $awu_max" \ + -c "shutdown" $testfile | grep wrote | \ + awk -F'[/ ]' '{print $2}') + _scratch_cycle_mount >>$seqres.full 2>&1 || _fail "remount failed" + else + bytes_written=$($XFS_IO_PROG -dc \ + "pwrite -DA -V1 -b $awu_max 0 $awu_max" $testfile | \ + grep wrote | awk -F'[/ ]' '{print $2}') + fi + + verify_atomic_write +} + +testfile=$SCRATCH_MNT/testfile +touch $testfile + +awu_max=$(_get_atomic_write_unit_max $testfile) +blksz=$(_get_block_size $SCRATCH_MNT) + +# Create an expected pattern to compare with +$XFS_IO_PROG -tc "pwrite -b $awu_max 0 $awu_max" $testfile >> $seqres.full +expected=$(_hexdump $testfile) +echo "# Expected file contents:" >> $seqres.full +echo "$expected" >> $seqres.full +echo >> $seqres.full + +echo "# Test 1: Do O_DSYNC atomic write on random mixed mapping:" >> $seqres.full +echo >> $seqres.full +for ((iteration=1; iteration<=10; iteration++)); do + echo "=== Mixed Mapping Test Iteration $iteration ===" >> $seqres.full + + echo "+ Testing without shutdown..." >> $seqres.full + mixed_mapping_test + echo "Passed!" >> $seqres.full + + echo "+ Testing with sudden shutdown..." >> $seqres.full + mixed_mapping_test "shutdown" + echo "Passed!" >> $seqres.full + + echo "Iteration $iteration completed: OK" >> $seqres.full + echo >> $seqres.full +done +echo "# Test 1: Do O_SYNC atomic write on random mixed mapping (10 iterations): OK" >> $seqres.full + + +echo >> $seqres.full +echo "# Test 2: Do extending O_SYNC atomic writes: " >> $seqres.full +bytes_written=$($XFS_IO_PROG -x -dstc "pwrite -A -V1 -b $awu_max 0 $awu_max" \ + -c "shutdown" $testfile | grep wrote | awk -F'[/ ]' '{print $2}') +_scratch_cycle_mount >>$seqres.full 2>&1 || _fail "remount failed" +verify_atomic_write +echo "# Test 2: Do extending O_SYNC atomic writes: OK" >> $seqres.full + +# success, all done +echo "Silence is golden" +status=0 +exit +
tests/generic/1228.out new file +2 -0
@@ -0,0 +1,2 @@ +QA output created by 1228 +Silence is golden
Signature · 1 line
2.49.0
PATCH v5 8/12 generic: Stress fsx with atomic writes enabled
In reply to Ojaswin MujooStress file with atomic writes to ensure we excercise codepaths where we are mixing different FS operations with atomic writes
--- tests/generic/1229 | 68 ++++++++++++++++++++++++++++++++++++++++++ tests/generic/1229.out | 2 ++ 2 files changed, 70 insertions(+) create mode 100755 tests/generic/1229 create mode 100644 tests/generic/1229.out
tests/generic/1229 new file +68 -0
@@ -0,0 +1,68 @@ +#! /bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2025 IBM Corporation. All Rights Reserved. +# +# FS QA Test 1229 +# +# fuzz fsx with atomic writes +# +. ./common/preamble +. ./common/atomicwrites +_begin_fstest rw auto quick atomicwrites + +_require_odirect +_require_scratch_write_atomic + +_scratch_mkfs >> $seqres.full 2>&1 +_scratch_mount >> $seqres.full 2>&1 + +testfile=$SCRATCH_MNT/testfile +touch $testfile + +awu_max=$(_get_atomic_write_unit_max $testfile) +blksz=$(_get_block_size $SCRATCH_MNT) +bsize=`$here/src/min_dio_alignment $SCRATCH_MNT $SCRATCH_DEV` + +set_fsx_avoid() { + local file=$1 + + case "$FSTYP" in + "ext4") + local dev=$(findmnt -n -o SOURCE --target $testfile) + + # fsx insert/collpase range support for ext4+bigalloc is + # currently broken, so disable it. Also disable incase we can't + # detect bigalloc to be on safer side. + if [ -z "$DUMPE2FS_PROG" ]; then + echo "dumpe2fs not found, disabling insert/collapse range" >> $seqres.full + FSX_AVOID+=" -I -C" + return + fi + + $DUMPE2FS_PROG -h $dev 2>&1 | grep -q bigalloc && { + echo "fsx insert/collapse range not supported with bigalloc. Disabling.." >> $seqres.full + FSX_AVOID+=" -I -C" + } + ;; + *) + ;; + esac +} + +# fsx usage: +# +# -N numops: total # operations to do +# -l flen: the upper bound on file size +# -o oplen: the upper bound on operation size (64k default) +# -Z: O_DIRECT () + +set_fsx_avoid +_run_fsx_on_file $testfile -N 10000 -o $awu_max -A -l 500000 -r $bsize -w $bsize -Z $FSX_AVOID >> $seqres.full +if [[ "$?" != "0" ]] +then + _fail "fsx returned error: $?" +fi + +echo "Silence is golden" +status=0 +exit
tests/generic/1229.out new file +2 -0
@@ -0,0 +1,2 @@ +QA output created by 1229 +Silence is golden
Signature · 1 line
2.49.0
PATCH v5 9/12 generic: Add sudden shutdown tests for multi block atomic writes
In reply to Ojaswin MujooThis test is intended to ensure that multi blocks atomic writes maintain atomic guarantees across sudden FS shutdowns.
The way we work is that we lay out a file with random mix of written, unwritten and hole extents. Then we start performing atomic writes sequentially on the file while we parallely shutdown the FS. Then we note the last offset where the atomic write happened just before shut down and then make sure blocks around it either have completely old data or completely new data, ie the write was not torn during shutdown.
We repeat the same with completely written, completely unwritten and completely empty file to ensure these cases are not torn either. Finally, we have a similar test for append atomic writes
--- tests/generic/1230 | 397 +++++++++++++++++++++++++++++++++++++++++ tests/generic/1230.out | 2 + 2 files changed, 399 insertions(+) create mode 100755 tests/generic/1230 create mode 100644 tests/generic/1230.out
tests/generic/1230 new file +397 -0
@@ -0,0 +1,397 @@ +#! /bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2025 IBM Corporation. All Rights Reserved. +# +# FS QA Test No. 1230 +# +# Test multi block atomic writes with sudden FS shutdowns to ensure +# the FS is not tearing the write operation +. ./common/preamble +. ./common/atomicwrites +_begin_fstest auto atomicwrites + +_require_scratch_write_atomic_multi_fsblock +_require_atomic_write_test_commands +_require_scratch_shutdown +_require_xfs_io_command "truncate" + +_scratch_mkfs >> $seqres.full 2>&1 +_scratch_mount >> $seqres.full + +testfile=$SCRATCH_MNT/testfile +touch $testfile + +awu_max=$(_get_atomic_write_unit_max $testfile) +blksz=$(_get_block_size $SCRATCH_MNT) +echo "Awu max: $awu_max" >> $seqres.full + +num_blocks=$((awu_max / blksz)) +# keep initial value high for dry run. This will be +# tweaked in dry_run() based on device write speed. +filesize=$(( 10 * 1024 * 1024 * 1024 )) + +_cleanup() { + [ -n "$awloop_pid" ] && kill $awloop_pid &> /dev/null + wait +} + +atomic_write_loop() { + local off=0 + local size=$awu_max + for ((i=0; i<$((filesize / $size )); i++)); do + # Due to sudden shutdown this can produce errors so just + # redirect them to seqres.full + $XFS_IO_PROG -c "open -fsd $testfile" -c "pwrite -S 0x61 -DA -V1 -b $size $off $size" >> /dev/null 2>>$seqres.full + echo "Written to offset: $off" >> $tmp.aw + off=$((off + $size)) + done +} + +# This test has the following flow: +# 1. Start doing sequential atomic writes in bg, upto $filesize +# 2. Sleep for 0.2s and shutdown the FS +# 3. kill the atomic write process +# 4. verify the writes were not torn +# +# We ideally want the shutdown to happen while an atomic write is ongoing +# but this gets tricky since faster devices can actually finish the whole +# atomic write loop before sleep 0.2s completes, resulting in the shutdown +# happening after the write loop which is not what we want. A simple solution +# to this is to increase $filesize so step 1 takes long enough but a big +# $filesize leads to create_mixed_mappings() taking very long, which is not +# ideal. +# +# Hence, use the dry_run function to figure out the rough device speed and set +# $filesize accordingly. +dry_run() { + echo >> $seqres.full + echo "# Estimating ideal filesize..." >> $seqres.full + atomic_write_loop & + awloop_pid=$! + + local i=0 + # Wait for atleast first write to be recorded or 10s + while [ ! -f "$tmp.aw" -a $i -le 50 ]; do i=$((i + 1)); sleep 0.2; done + + if [[ $i -gt 50 ]] + then + _fail "atomic write process took too long to start" + fi + + echo >> $seqres.full + echo "# Shutting down filesystem while write is running" >> $seqres.full + _scratch_shutdown + + kill $awloop_pid 2>/dev/null # the process might have finished already + wait $awloop_pid + unset $awloop_pid + + bytes_written=$(tail -n 1 $tmp.aw | cut -d" " -f4) + echo "# Bytes written in 0.2s: $bytes_written" >> $seqres.full + + filesize=$((bytes_written * 3)) + echo "# Setting \$filesize=$filesize" >> $seqres.full + + rm $tmp.aw + sleep 0.5 + + _scratch_cycle_mount + +} + +create_mixed_mappings() { + local file=$1 + local size_bytes=$2 + + echo "# Filling file $file with alternate mappings till size $size_bytes" >> $seqres.full + #Fill the file with alternate written and unwritten blocks + local off=0 + local operations=("W" "U") + + for ((i=0; i<$((size_bytes / blksz )); i++)); do + index=$(($i % ${#operations[@]})) + map="${operations[$index]}" + + case "$map" in + "W") + $XFS_IO_PROG -fc "pwrite -b $blksz $off $blksz" $file >> /dev/null + ;; + "U") + $XFS_IO_PROG -fc "falloc $off $blksz" $file >> /dev/null + ;; + esac + off=$((off + blksz)) + done + + sync $file +} + +populate_expected_data() { + # create a dummy file with expected old data for different cases + create_mixed_mappings $testfile.exp_old_mixed $awu_max + expected_data_old_mixed=$(od -An -t x1 -j 0 -N $awu_max $testfile.exp_old_mixed) + + $XFS_IO_PROG -fc "falloc 0 $awu_max" $testfile.exp_old_zeroes >> $seqres.full + expected_data_old_zeroes=$(od -An -t x1 -j 0 -N $awu_max $testfile.exp_old_zeroes) + + $XFS_IO_PROG -fc "pwrite -b $awu_max 0 $awu_max" $testfile.exp_old_mapped >> $seqres.full + expected_data_old_mapped=$(od -An -t x1 -j 0 -N $awu_max $testfile.exp_old_mapped) + + # create a dummy file with expected new data + $XFS_IO_PROG -fc "pwrite -S 0x61 -b $awu_max 0 $awu_max" $testfile.exp_new >> $seqres.full + expected_data_new=$(od -An -t x1 -j 0 -N $awu_max $testfile.exp_new) +} + +verify_data_blocks() { + local verify_start=$1 + local verify_end=$2 + local expected_data_old="$3" + local expected_data_new="$4" + + echo >> $seqres.full + echo "# Checking data integrity from $verify_start to $verify_end" >> $seqres.full + + # After an atomic write, for every chunk we ensure that the underlying + # data is either the old data or new data as writes shouldn't get torn. + local off=$verify_start + while [[ "$off" -lt "$verify_end" ]] + do + #actual_data=$(xxd -s $off -l $awu_max -p $testfile) + actual_data=$(od -An -t x1 -j $off -N $awu_max $testfile) + if [[ "$actual_data" != "$expected_data_new" ]] && [[ "$actual_data" != "$expected_data_old" ]] + then + echo "Checksum match failed at off: $off size: $awu_max" + echo "Expected contents: (Either of the 2 below):" + echo + echo "Expected old: " + echo "$expected_data_old" + echo + echo "Expected new: " + echo "$expected_data_new" + echo + echo "Actual contents: " + echo "$actual_data" + + _fail + fi + echo -n "Check at offset $off suceeded! " >> $seqres.full + if [[ "$actual_data" == "$expected_data_new" ]] + then + echo "matched new" >> $seqres.full + elif [[ "$actual_data" == "$expected_data_old" ]] + then + echo "matched old" >> $seqres.full + fi + off=$(( off + awu_max )) + done +} + +# test data integrity for file by shutting down in between atomic writes +test_data_integrity() { + echo >> $seqres.full + echo "# Writing atomically to file in background" >> $seqres.full + atomic_write_loop & + awloop_pid=$! + + local i=0 + # Wait for atleast first write to be recorded or 10s + while [ ! -f "$tmp.aw" -a $i -le 50 ]; do i=$((i + 1)); sleep 0.2; done + + if [[ $i -gt 50 ]] + then + _fail "atomic write process took too long to start" + fi + + echo >> $seqres.full + echo "# Shutting down filesystem while write is running" >> $seqres.full + _scratch_shutdown + + kill $awloop_pid 2>/dev/null # the process might have finished already + wait $awloop_pid + unset $awloop_pid + + last_offset=$(tail -n 1 $tmp.aw | cut -d" " -f4) + if [[ -z $last_offset ]] + then + last_offset=0 + fi + + echo >> $seqres.full + echo "# Last offset of atomic write: $last_offset" >> $seqres.full + + rm $tmp.aw + sleep 0.5 + + _scratch_cycle_mount + + # we want to verify all blocks around which the shutdown happended + verify_start=$(( last_offset - (awu_max * 5))) + if [[ $verify_start < 0 ]] + then + verify_start=0 + fi + + verify_end=$(( last_offset + (awu_max * 5))) + if [[ "$verify_end" -gt "$filesize" ]] + then + verify_end=$filesize + fi +} + +# test data integrity for file wiht written and unwritten mappings +test_data_integrity_mixed() { + $XFS_IO_PROG -fc "truncate 0" $testfile >> $seqres.full + + echo >> $seqres.full + echo "# Creating testfile with mixed mappings" >> $seqres.full + create_mixed_mappings $testfile $filesize + + test_data_integrity + + verify_data_blocks $verify_start $verify_end "$expected_data_old_mixed" "$expected_data_new" +} + +# test data integrity for file with completely written mappings +test_data_integrity_writ() { + $XFS_IO_PROG -fc "truncate 0" $testfile >> $seqres.full + + echo >> $seqres.full + echo "# Creating testfile with fully written mapping" >> $seqres.full + $XFS_IO_PROG -c "pwrite -b $filesize 0 $filesize" $testfile >> $seqres.full + sync $testfile + + test_data_integrity + + verify_data_blocks $verify_start $verify_end "$expected_data_old_mapped" "$expected_data_new" +} + +# test data integrity for file with completely unwritten mappings +test_data_integrity_unwrit() { + $XFS_IO_PROG -fc "truncate 0" $testfile >> $seqres.full + + echo >> $seqres.full + echo "# Creating testfile with fully unwritten mappings" >> $seqres.full + $XFS_IO_PROG -c "falloc 0 $filesize" $testfile >> $seqres.full + sync $testfile + + test_data_integrity + + verify_data_blocks $verify_start $verify_end "$expected_data_old_zeroes" "$expected_data_new" +} + +# test data integrity for file with no mappings +test_data_integrity_hole() { + $XFS_IO_PROG -fc "truncate 0" $testfile >> $seqres.full + + echo >> $seqres.full + echo "# Creating testfile with no mappings" >> $seqres.full + $XFS_IO_PROG -c "truncate $filesize" $testfile >> $seqres.full + sync $testfile + + test_data_integrity + + verify_data_blocks $verify_start $verify_end "$expected_data_old_zeroes" "$expected_data_new" +} + +test_filesize_integrity() { + $XFS_IO_PROG -c "truncate 0" $testfile >> $seqres.full + + echo >> $seqres.full + echo "# Performing extending atomic writes over file in background" >> $seqres.full + atomic_write_loop & + awloop_pid=$! + + local i=0 + # Wait for atleast first write to be recorded or 10s + while [ ! -f "$tmp.aw" -a $i -le 50 ]; do i=$((i + 1)); sleep 0.2; done + + if [[ $i -gt 50 ]] + then + _fail "atomic write process took too long to start" + fi + + echo >> $seqres.full + echo "# Shutting down filesystem while write is running" >> $seqres.full + _scratch_shutdown + + kill $awloop_pid 2>/dev/null # the process might have finished already + wait $awloop_pid + unset $awloop_pid + + local last_offset=$(tail -n 1 $tmp.aw | cut -d" " -f4) + if [[ -z $last_offset ]] + then + last_offset=0 + fi + + echo >> $seqres.full + echo "# Last offset of atomic write: $last_offset" >> $seqres.full + rm $tmp.aw + sleep 0.5 + + _scratch_cycle_mount + local filesize=$(_get_filesize $testfile) + echo >> $seqres.full + echo "# Filesize after shutdown: $filesize" >> $seqres.full + + # To confirm that the write went atomically, we check: + # 1. The last block should be a multiple of awu_max + # 2. The last block should be the completely new data + + if (( $filesize % $awu_max )) + then + echo "Filesize after shutdown ($filesize) not a multiple of atomic write unit ($awu_max)" + fi + + verify_start=$(( filesize - (awu_max * 5))) + if [[ $verify_start < 0 ]] + then + verify_start=0 + fi + + local verify_end=$filesize + + # Here the blocks should always match new data hence, for simplicity of + # code, just corrupt the $expected_data_old buffer so it never matches + local expected_data_old="POISON" + verify_data_blocks $verify_start $verify_end "$expected_data_old" "$expected_data_new" +} + +$XFS_IO_PROG -fc "truncate 0" $testfile >> $seqres.full + +dry_run + +echo >> $seqres.full +echo "# Populating expected data buffers" >> $seqres.full +populate_expected_data + +# Loop 20 times to shake out any races due to shutdown +for ((iter=0; iter<20; iter++)) +do + echo >> $seqres.full + echo "------ Iteration $iter ------" >> $seqres.full + + echo >> $seqres.full + echo "# Starting data integrity test for atomic writes over mixed mapping" >> $seqres.full + test_data_integrity_mixed + + echo >> $seqres.full + echo "# Starting data integrity test for atomic writes over fully written mapping" >> $seqres.full + test_data_integrity_writ + + echo >> $seqres.full + echo "# Starting data integrity test for atomic writes over fully unwritten mapping" >> $seqres.full + test_data_integrity_unwrit + + echo >> $seqres.full + echo "# Starting data integrity test for atomic writes over holes" >> $seqres.full + test_data_integrity_hole + + echo >> $seqres.full + echo "# Starting filesize integrity test for atomic writes" >> $seqres.full + test_filesize_integrity +done + +echo "Silence is golden" +status=0 +exit
tests/generic/1230.out new file +2 -0
@@ -0,0 +1,2 @@ +QA output created by 1230 +Silence is golden
Signature · 1 line
2.49.0
PATCH v5 10/12 ext4: test atomic write and ioend codepaths with bigalloc
In reply to Ojaswin MujooFrom: "Ritesh Harjani (IBM)" <[email protected]>
This test does a lot of parallel RWF_ATOMIC IO on a preallocated file to stress the write and end-io unwritten conversion code paths. We brute force this for different blocksize and clustersizes and after each iteration we ensure the data was not torn or corrupted using fio crc verification.
Note that in this test we use overlapping atomic writes of same io size. Although serializing racing writes is not guaranteed for RWF_ATOMIC, NVMe and SCSI provide this guarantee as an inseparable feature to power-fail atomicity. Keeping the iosize as same also ensures that ext4 doesn't tear the write due to racing ioend unwritten conversion.
The value of this test is that we make sure the RWF_ATOMIC is handled correctly by ext4 as well as test that the block layer doesn't split or only generate multiple bios for an atomic write.
--- tests/ext4/061 | 155 +++++++++++++++++++++++++++++++++++++++++++++ tests/ext4/061.out | 2 + 2 files changed, 157 insertions(+) create mode 100755 tests/ext4/061 create mode 100644 tests/ext4/061.out
tests/ext4/061 new file +155 -0
@@ -0,0 +1,155 @@ +#! /bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2025 IBM Corporation. All Rights Reserved. +# +# FS QA Test 061 +# +# This test does a lot of parallel RWF_ATOMIC IO on a preallocated file to +# stress the write and end-io unwritten conversion code paths. We brute force +# this for all possible blocksize and clustersizes and after each iteration we +# ensure the data was not torn or corrupted using fio crc verification. +# +# Note that in this test we use overlapping atomic writes of same io size. +# Although serializing racing writes is not guaranteed for RWF_ATOMIC, NVMe and +# SCSI provide this guarantee as an inseparable feature to power-fail +# atomicity. Keeping the iosize as same also ensures that ext4 doesn't tear the +# write due to racing ioend unwritten conversion. +# +# The value of this test is that we make sure the RWF_ATOMIC is handled +# correctly by ext4 as well as test that the block layer doesn't split or only +# generate multiple bios for an atomic write. + +. ./common/preamble +. ./common/atomicwrites + +_begin_fstest auto rw stress atomicwrites + +_require_scratch_write_atomic +_require_aiodio +_require_fio_version "3.38+" + +FIO_LOAD=$(($(nproc) * 2 * LOAD_FACTOR)) +SIZE=$((100*1024*1024)) + +# Calculate fsblocksize as per bdev atomic write units. +bdev_awu_min=$(_get_atomic_write_unit_min $SCRATCH_DEV) +bdev_awu_max=$(_get_atomic_write_unit_max $SCRATCH_DEV) +bs=$(_max 4096 "$bdev_awu_min") + +function create_fio_configs() +{ + local bsize=$1 + create_fio_aw_config $bsize + create_fio_verify_config $bsize +} + +function create_fio_verify_config() +{ + local bsize=$1 +cat >$fio_verify_config <<EOF + [aio-dio-aw-verify] + direct=1 + ioengine=libaio + rw=read + bs=$bsize + fallocate=native + filename=$SCRATCH_MNT/test-file + size=$SIZE + iodepth=$FIO_LOAD + numjobs=$FIO_LOAD + atomic=1 + group_reporting=1 + + verify_only=1 + verify_state_save=0 + verify=crc32c + verify_fatal=1 + verify_write_sequence=0 +EOF +} + +function create_fio_aw_config() +{ + local bsize=$1 +cat >$fio_aw_config <<EOF + [aio-dio-aw] + direct=1 + ioengine=libaio + rw=randwrite + bs=$bsize + fallocate=native + filename=$SCRATCH_MNT/test-file + size=$SIZE + iodepth=$FIO_LOAD + numjobs=$FIO_LOAD + group_reporting=1 + atomic=1 + + verify_state_save=0 + verify=crc32c + do_verify=0 + +EOF +} + +run_test_one() { + local bs=$1 + local cs=$2 + local iosize=$3 + + MKFS_OPTIONS="-O bigalloc -b $bs -C $cs" + _scratch_mkfs_ext4 >> $seqres.full 2>&1 || continue + if _try_scratch_mount >> $seqres.full 2>&1; then + echo "== Testing: bs=$bs cs=$cs iosize=$iosize ==" >> $seqres.full + + touch $SCRATCH_MNT/f1 + create_fio_configs $iosize + + cat $fio_aw_config >> $seqres.full + echo >> $seqres.full + cat $fio_verify_config >> $seqres.full + + $FIO_PROG $fio_aw_config >> $seqres.full + ret1=$? + + $FIO_PROG $fio_verify_config >> $seqres.full + ret2=$? + + _scratch_unmount + + [[ $ret1 -eq 0 && $ret2 -eq 0 ]] || _fail "fio with atomic write failed" + fi +} + +run_test() { + local bs=$1 + + # cluster sizes above 16 x blocksize are experimental so avoid them + # Also, cap cluster size at 128kb to keep it reasonable for large + # blocks size + max_cs=$(_min $((16 * bs)) "$bdev_awu_max" $((128 * 1024))) + + # Fuzz for combinations of blocksize, clustersize and + # iosize that cover most of the cases + run_test_one $bs $bs $bs + run_test_one $bs $max_cs $bs + run_test_one $bs $max_cs $max_cs + run_test_one $bs $max_cs $(_max "$((max_cs/2))" $bs) +} + +# Let's create a sample fio config to check whether fio supports all options. +fio_aw_config=$tmp.aw.fio +fio_verify_config=$tmp.verify.fio +fio_out=$tmp.fio.out + +create_fio_configs $bs +_require_fio $fio_aw_config + +for ((bs=$bs; bs <= $(_get_page_size); bs = $bs << 1)); do + run_test $bs +done + +# success, all done +echo Silence is golden +status=0 +exit
tests/ext4/061.out new file +2 -0
@@ -0,0 +1,2 @@ +QA output created by 061 +Silence is golden
Signature · 1 line
2.49.0
PATCH v5 11/12 ext4: Test atomic writes allocation and write codepaths with bigalloc
In reply to Ojaswin MujooFrom: "Ritesh Harjani (IBM)" <[email protected]>
This test does a parallel RWF_ATOMIC IO on a multiple truncated files in a small FS. The idea is to stress ext4 allocator to ensure we are able to handle low space scenarios correctly with atomic writes. We brute force this for different blocksize and clustersizes and after each iteration we ensure the data was not torn or corrupted using fio crc verification.
Note that in this test we use overlapping atomic writes of same io size. Although serializing racing writes is not guaranteed for RWF_ATOMIC, NVMe and SCSI provide this guarantee as an inseparable feature to power-fail atomicity. Keeping the iosize as same also ensures that ext4 doesn't tear the write due to racing ioend unwritten conversion.
The value of this test is that we make sure the RWF_ATOMIC is handled correctly by ext4 as well as test that the block layer doesn't split or only generate multiple bios for an atomic write.
--- tests/ext4/062 | 203 +++++++++++++++++++++++++++++++++++++++++++++ tests/ext4/062.out | 2 + 2 files changed, 205 insertions(+) create mode 100755 tests/ext4/062 create mode 100644 tests/ext4/062.out
tests/ext4/062 new file +203 -0
@@ -0,0 +1,203 @@ +#! /bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2025 IBM Corporation. All Rights Reserved. +# +# FS QA Test 062 +# +# This test does a parallel RWF_ATOMIC IO on a multiple truncated files in a +# small FS. The idea is to stress ext4 allocator to ensure we are able to +# handle low space scenarios correctly with atomic writes.. We brute force this +# for all possible blocksize and clustersizes and after each iteration we +# ensure the data was not torn or corrupted using fio crc verification. +# +# Note that in this test we use overlapping atomic writes of same io size. +# Although serializing racing writes is not guaranteed for RWF_ATOMIC, NVMe and +# SCSI provide this guarantee as an inseparable feature to power-fail +# atomicity. Keeping the iosize as same also ensures that ext4 doesn't tear the +# write due to racing ioend unwritten conversion. +# +# The value of this test is that we make sure the RWF_ATOMIC is handled +# correctly by ext4 as well as test that the block layer doesn't split or only +# generate multiple bios for an atomic write. +# + +. ./common/preamble +. ./common/atomicwrites + +_begin_fstest auto rw stress atomicwrites + +_require_scratch_write_atomic +_require_aiodio +_require_fio_version "3.38+" + +FSSIZE=$((360*1024*1024)) +FIO_LOAD=$(($(nproc) * LOAD_FACTOR)) + +# Calculate bs as per bdev atomic write units. +bdev_awu_min=$(_get_atomic_write_unit_min $SCRATCH_DEV) +bdev_awu_max=$(_get_atomic_write_unit_max $SCRATCH_DEV) +bs=$(_max 4096 "$bdev_awu_min") + +function create_fio_configs() +{ + local bsize=$1 + create_fio_aw_config $bsize + create_fio_verify_config $bsize +} + +function create_fio_verify_config() +{ + local bsize=$1 +cat >$fio_verify_config <<EOF + [global] + direct=1 + ioengine=libaio + rw=read + bs=$bsize + fallocate=truncate + size=$((FSSIZE / 12)) + iodepth=$FIO_LOAD + numjobs=$FIO_LOAD + group_reporting=1 + atomic=1 + + verify_only=1 + verify_state_save=0 + verify=crc32c + verify_fatal=1 + verify_write_sequence=0 + + [verify-job1] + filename=$SCRATCH_MNT/testfile-job1 + + [verify-job2] + filename=$SCRATCH_MNT/testfile-job2 + + [verify-job3] + filename=$SCRATCH_MNT/testfile-job3 + + [verify-job4] + filename=$SCRATCH_MNT/testfile-job4 + + [verify-job5] + filename=$SCRATCH_MNT/testfile-job5 + + [verify-job6] + filename=$SCRATCH_MNT/testfile-job6 + + [verify-job7] + filename=$SCRATCH_MNT/testfile-job7 + + [verify-job8] + filename=$SCRATCH_MNT/testfile-job8 + +EOF +} + +function create_fio_aw_config() +{ + local bsize=$1 +cat >$fio_aw_config <<EOF + [global] + direct=1 + ioengine=libaio + rw=randwrite + bs=$bsize + fallocate=truncate + size=$((FSSIZE / 12)) + iodepth=$FIO_LOAD + numjobs=$FIO_LOAD + group_reporting=1 + atomic=1 + + verify_state_save=0 + verify=crc32c + do_verify=0 + + [write-job1] + filename=$SCRATCH_MNT/testfile-job1 + + [write-job2] + filename=$SCRATCH_MNT/testfile-job2 + + [write-job3] + filename=$SCRATCH_MNT/testfile-job3 + + [write-job4] + filename=$SCRATCH_MNT/testfile-job4 + + [write-job5] + filename=$SCRATCH_MNT/testfile-job5 + + [write-job6] + filename=$SCRATCH_MNT/testfile-job6 + + [write-job7] + filename=$SCRATCH_MNT/testfile-job7 + + [write-job8] + filename=$SCRATCH_MNT/testfile-job8 + +EOF +} + +run_test_one() { + local bs=$1 + local cs=$2 + local iosize=$3 + + MKFS_OPTIONS="-O bigalloc -b $bs -C $cs" + _scratch_mkfs_sized "$FSSIZE" >> $seqres.full 2>&1 || continue + if _try_scratch_mount >> $seqres.full 2>&1; then + echo "Testing: bs=$bs cs=$cs iosize=$iosize" >> $seqres.full + + touch $SCRATCH_MNT/f1 + create_fio_configs $iosize + + cat $fio_aw_config >> $seqres.full + cat $fio_verify_config >> $seqres.full + + $FIO_PROG $fio_aw_config >> $seqres.full + ret1=$? + + $FIO_PROG $fio_verify_config >> $seqres.full + ret2=$? + + _scratch_unmount + + [[ $ret1 -eq 0 && $ret2 -eq 0 ]] || _fail "fio with atomic write failed" + fi +} + +run_test() { + local bs=$1 + + # cluster sizes above 16 x blocksize are experimental so avoid them + # Also, cap cluster size at 128kb to keep it reasonable for large + # blocks size + max_cs=$(_min $((16 * bs)) "$bdev_awu_max" $((128 * 1024))) + + # Fuzz for combinations of blocksize, clustersize and + # iosize that cover most of the cases + run_test_one $bs $bs $bs + run_test_one $bs $max_cs $bs + run_test_one $bs $max_cs $max_cs + run_test_one $bs $max_cs $(_max "$((max_cs/2))" $bs) +} + +# Let's create a sample fio config to check whether fio supports all options. +fio_aw_config=$tmp.aw.fio +fio_verify_config=$tmp.verify.fio +fio_out=$tmp.fio.out + +create_fio_configs $bs +_require_fio $fio_aw_config + +for ((bs=$bs; bs <= $(_get_page_size); bs = $bs << 1)); do + run_test $bs $cs $iosize +done + +# success, all done +echo Silence is golden +status=0 +exit
tests/ext4/062.out new file +2 -0
@@ -0,0 +1,2 @@ +QA output created by 062 +Silence is golden
Signature · 1 line
2.49.0
PATCH v5 12/12 ext4: Atomic write test for extent split across leaf nodes
In reply to Ojaswin MujooIn ext4, even if an allocated range is physically and logically contiguous, it can still be split into 2 extents. This is because ext4 does not merge extents across leaf nodes. This is an issue for atomic writes since even for a continuous extent the map block could (in rare cases) return a shorter map, hence tearning the write. This test creates such a file and ensures that the atomic write handles this case correctly
--- tests/ext4/063 | 129 +++++++++++++++++++++++++++++++++++++++++++++ tests/ext4/063.out | 2 + 2 files changed, 131 insertions(+) create mode 100755 tests/ext4/063 create mode 100644 tests/ext4/063.out
tests/ext4/063 new file +129 -0
@@ -0,0 +1,129 @@ +#! /bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2025 IBM Corporation. All Rights Reserved. +# +# In ext4, even if an allocated range is physically and logically contiguous, +# it can still be split into 2 or more extents. This is because ext4 does not +# merge extents across leaf nodes. This is an issue for atomic writes since +# even for a continuous extent the map block could (in rare cases) return a +# shorter map, hence tearing the write. This test creates such a file and +# ensures that the atomic write handles this case correctly +# +. ./common/preamble +. ./common/atomicwrites +_begin_fstest auto atomicwrites + +_require_scratch_write_atomic_multi_fsblock +_require_atomic_write_test_commands +_require_command "$DEBUGFS_PROG" debugfs + +prep() { + local bs=`_get_block_size $SCRATCH_MNT` + local ex_hdr_bytes=12 + local ex_entry_bytes=12 + local entries_per_blk=$(( (bs - ex_hdr_bytes) / ex_entry_bytes )) + + # fill the extent tree leaf with bs len extents at alternate offsets. + # The tree should look as follows + # + # +---------+---------+ + # | index 1 | index 2 | + # +-----+---+-----+---+ + # +------+ +-----------+ + # | | + # +-------+-------+---+---------+ +-----+----+ + # | ex 1 | ex 2 | | ex n | | ex n+1 | + # | off:0 | off:2 |...| off:678 | | off:680 | + # | len:1 | len:1 | | len:1 | | len:1 | + # +-------+-------+---+---------+ +----------+ + # + for i in $(seq 0 $entries_per_blk) + do + $XFS_IO_PROG -fc "pwrite -b $bs $((i * 2 * bs)) $bs" $testfile > /dev/null + done + sync $testfile + + echo >> $seqres.full + echo "Create file with extents spanning 2 leaves. Extents:">> $seqres.full + echo "...">> $seqres.full + $DEBUGFS_PROG -R "ex `basename $testfile`" $SCRATCH_DEV |& tail >> $seqres.full + + # Now try to insert a new extent ex(new) between ex(n) and ex(n+1). + # Since this is a new FS the allocator would find continuous blocks + # such that ex(n) ex(new) ex(n+1) are physically(and logically) + # contiguous. However, since we don't merge extents across leaf we will + # end up with a tree as: + # + # +---------+---------+ + # | index 1 | index 2 | + # +-----+---+-----+---+ + # +------+ +------------+ + # | | + # +-------+-------+---+---------+ +------+-----------+ + # | ex 1 | ex 2 | | ex n | | ex n+1 (merged) | + # | off:0 | off:2 |...| off:678 | | off:679 | + # | len:1 | len:1 | | len:1 | | len:2 | + # +-------+-------+---+---------+ +------------------+ + # + echo >> $seqres.full + torn_ex_offset=$((((entries_per_blk * 2) - 1) * bs)) + $XFS_IO_PROG -c "pwrite $torn_ex_offset $bs" $testfile >> /dev/null + sync $testfile + + echo >> $seqres.full + echo "Perform 1 block write at $torn_ex_offset to create torn extent. Extents:">> $seqres.full + echo "...">> $seqres.full + $DEBUGFS_PROG -R "ex `basename $testfile`" $SCRATCH_DEV |& tail >> $seqres.full + + _scratch_cycle_mount +} + +_scratch_mkfs >> $seqres.full +_scratch_mount >> $seqres.full + +testfile=$SCRATCH_MNT/testfile +touch $testfile +awu_max=$(_get_atomic_write_unit_max $testfile) + +echo >> $seqres.full +echo "# Prepping the file" >> $seqres.full +prep + +torn_aw_offset=$((torn_ex_offset - (torn_ex_offset % awu_max))) + +echo >> $seqres.full +echo "# Performing atomic IO on the torn extent range. Command: " >> $seqres.full +echo $XFS_IO_PROG -c "open -fsd $testfile" -c "pwrite -S 0x61 -DA -V1 -b $awu_max $torn_aw_offset $awu_max" >> $seqres.full +$XFS_IO_PROG -c "open -fsd $testfile" -c "pwrite -S 0x61 -DA -V1 -b $awu_max $torn_aw_offset $awu_max" >> $seqres.full + +echo >> $seqres.full +echo "Extent state after atomic write:">> $seqres.full +echo "...">> $seqres.full +$DEBUGFS_PROG -R "ex `basename $testfile`" $SCRATCH_DEV |& tail >> $seqres.full + +echo >> $seqres.full +echo "# Checking data integrity" >> $seqres.full + +# create a dummy file with expected data +$XFS_IO_PROG -fc "pwrite -S 0x61 -b $awu_max 0 $awu_max" $testfile.exp >> /dev/null +expected_data=$(od -An -t x1 -j 0 -N $awu_max $testfile.exp) + +# We ensure that the data after atomic writes should match the expected data +actual_data=$(od -An -t x1 -j $torn_aw_offset -N $awu_max $testfile) +if [[ "$actual_data" != "$expected_data" ]] +then + echo "Checksum match failed at off: $torn_aw_offset size: $awu_max" + echo + echo "Expected: " + echo "$expected_data" + echo + echo "Actual contents: " + echo "$actual_data" + + _fail +fi + +echo -n "Data verification at offset $torn_aw_offset suceeded!" >> $seqres.full +echo "Silence is golden" +status=0 +exit
tests/ext4/063.out new file +2 -0
@@ -0,0 +1,2 @@ +QA output created by 063 +Silence is golden
Signature · 1 line
2.49.0
Re: [PATCH v5 02/12] common/rc: Add _require_fio_version helper
In reply to Ojaswin MujooQuoted 10 lines · Ojaswin Mujoo
The main motivation of adding this function on top of
_require_fiois that there has been a case in fio where atomic= option was added but later it was changed to noop since kernel didn't yet have support for atomic writes. It was then again utilized to do atomic writes in a later version, once kernel got the support. Due to this there is a point in fio where_require_fiow/ atomic=1 will succeed even though it would not be doing atomic writes.Hence, add an explicit helper to ensure tests to require specific versions of fio to work past such issues.
Actually I'm wondering if fstests really needs to care about this. This's just a temporary issue of fio, not kernel or any fs usespace program. Do we need to add a seperated helper only for a temporary fio issue? If fio doesn't break fstests running, let it run. Just the testers install proper fio (maybe latest) they need. What do you and others think?
Thanks,
Zorro
Quoted 52 lines
Signed-off-by Ojaswin Mujoo <[email protected]>--- common/rc | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+)common/rc +32
-0@@ -5997,6 +5997,38 @@ _max() { echo $ret } +# Check the required fio version. Examples: +# _require_fio_version 3.38 (matches 3.38 only) +# _require_fio_version 3.38+ (matches 3.38 and above) +# _require_fio_version 3.38- (matches 3.38 and below) +_require_fio_version() { + local req_ver="$1" + local fio_ver + + _require_fio + _require_math + + fio_ver=$(fio -v | cut -d"-" -f2) + + case "$req_ver" in + *+) + req_ver=${req_ver%+} + test $(_math "$fio_ver >= $req_ver") -eq 1 || \ + _notrun "need fio >= $req_ver (found $fio_ver)" + ;; + *-) + req_ver=${req_ver%-} + test $(_math "$fio_ver <= $req_ver") -eq 1 || \ + _notrun "need fio <= $req_ver (found $fio_ver)" + ;; + *) + req_ver=${req_ver%-} + test $(_math "$fio_ver == $req_ver") -eq 1 || \ + _notrun "need fio = $req_ver (found $fio_ver)" + ;; + esac +} + ################################################################################ # make sure this script returns success /bin/trueSignature · 1 line
2.49.0
Re: [PATCH v5 02/12] common/rc: Add _require_fio_version helper
Quoted 12 lines · Zorro Lang
Quoted 10 lines · Ojaswin Mujoo
The main motivation of adding this function on top of
_require_fiois that there has been a case in fio where atomic= option was added but later it was changed to noop since kernel didn't yet have support for atomic writes. It was then again utilized to do atomic writes in a later version, once kernel got the support. Due to this there is a point in fio where_require_fiow/ atomic=1 will succeed even though it would not be doing atomic writes.Hence, add an explicit helper to ensure tests to require specific versions of fio to work past such issues.
Quoted 8 lines
Actually I'm wondering if fstests really needs to care about this. This's just a temporary issue of fio, not kernel or any fs usespace program. Do we need to add a seperated helper only for a temporary fio issue? If fio doesn't break fstests running, let it run. Just the testers install proper fio (maybe latest) they need. What do you and others think?
Thanks,
Zorro
Hey Zorro,
Sure I'm okay with not keeping the helper and letting the user make sure the fio version is correct.
@John, does that sound okay?
Regards,
ojaswin
Quoted 54 lines
Quoted 53 lines
Signed-off-by Ojaswin Mujoo <[email protected]>--- common/rc | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+)common/rc +32
-0@@ -5997,6 +5997,38 @@ _max() { echo $ret } +# Check the required fio version. Examples: +# _require_fio_version 3.38 (matches 3.38 only) +# _require_fio_version 3.38+ (matches 3.38 and above) +# _require_fio_version 3.38- (matches 3.38 and below) +_require_fio_version() { + local req_ver="$1" + local fio_ver + + _require_fio + _require_math + + fio_ver=$(fio -v | cut -d"-" -f2) + + case "$req_ver" in + *+) + req_ver=${req_ver%+} + test $(_math "$fio_ver >= $req_ver") -eq 1 || \ + _notrun "need fio >= $req_ver (found $fio_ver)" + ;; + *-) + req_ver=${req_ver%-} + test $(_math "$fio_ver <= $req_ver") -eq 1 || \ + _notrun "need fio <= $req_ver (found $fio_ver)" + ;; + *) + req_ver=${req_ver%-} + test $(_math "$fio_ver == $req_ver") -eq 1 || \ + _notrun "need fio = $req_ver (found $fio_ver)" + ;; + esac +} + ################################################################################ # make sure this script returns success /bin/trueSignature · 1 line
2.49.0
Re: [PATCH v5 02/12] common/rc: Add _require_fio_version helper
Quoted 10 lines · Ojaswin Mujoo
Quoted 9 lines · Zorro Lang
Quoted 8 lines · Ojaswin Mujoo
The main motivation of adding this function on top of
_require_fiois that there has been a case in fio where atomic= option was added but later it was changed to noop since kernel didn't yet have support for atomic writes. It was then again utilized to do atomic writes in a later version, once kernel got the support. Due to this there is a point in fio where_require_fiow/ atomic=1 will succeed even though it would not be doing atomic writes.
Quoted 8 lines
Quoted 8 lines
Quoted 2 lines
Hence, add an explicit helper to ensure tests to require specific versions of fio to work past such issues.
Actually I'm wondering if fstests really needs to care about this. This's just a temporary issue of fio, not kernel or any fs usespace program. Do we need to add a seperated helper only for a temporary fio issue? If fio doesn't break fstests running, let it run. Just the testers install proper fio (maybe latest) they need. What do you and others think?
Are there obvious failures if you try to run these new atomic write tests on a system with the weird versions of fio that have the no-op atomic= functionality? I'm concerned that some QA person is going to do that unwittingly and report that everything is ok when in reality they didn't actually test anything.
--D
Quoted 67 lines
Quoted 2 lines
Thanks,
ZorroHey Zorro,
Sure I'm okay with not keeping the helper and letting the user make sure the fio version is correct.
@John, does that sound okay?
Regards,
ojaswinQuoted 54 lines
Quoted 53 lines
Signed-off-by Ojaswin Mujoo <[email protected]>--- common/rc | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+)common/rc +32
-0@@ -5997,6 +5997,38 @@ _max() { echo $ret } +# Check the required fio version. Examples: +# _require_fio_version 3.38 (matches 3.38 only) +# _require_fio_version 3.38+ (matches 3.38 and above) +# _require_fio_version 3.38- (matches 3.38 and below) +_require_fio_version() { + local req_ver="$1" + local fio_ver + + _require_fio + _require_math + + fio_ver=$(fio -v | cut -d"-" -f2) + + case "$req_ver" in + *+) + req_ver=${req_ver%+} + test $(_math "$fio_ver >= $req_ver") -eq 1 || \ + _notrun "need fio >= $req_ver (found $fio_ver)" + ;; + *-) + req_ver=${req_ver%-} + test $(_math "$fio_ver <= $req_ver") -eq 1 || \ + _notrun "need fio <= $req_ver (found $fio_ver)" + ;; + *) + req_ver=${req_ver%-} + test $(_math "$fio_ver == $req_ver") -eq 1 || \ + _notrun "need fio = $req_ver (found $fio_ver)" + ;; + esac +} + ################################################################################ # make sure this script returns success /bin/trueSignature · 1 line
2.49.0
Re: [PATCH v5 10/12] ext4: test atomic write and ioend codepaths with bigalloc
In reply to Ojaswin MujooQuoted 14 lines · Ojaswin Mujoo
From: "Ritesh Harjani (IBM)" <[email protected]>
This test does a lot of parallel
RWF_ATOMICIO on a preallocated file to stress the write and end-io unwritten conversion code paths. We brute force this for different blocksize and clustersizes and after each iteration we ensure the data was not torn or corrupted using fio crc verification.Note that in this test we use overlapping atomic writes of same io size. Although serializing racing writes is not guaranteed for
RWF_ATOMIC, NVMe and SCSI provide this guarantee as an inseparable feature to power-fail atomicity. Keeping the iosize as same also ensures that ext4 doesn't tear the write due to racing ioend unwritten conversion.
Quoted 6 lines
The value of this test is that we make sure the
RWF_ATOMICis handled correctly by ext4 as well as test that the block layer doesn't split or only generate multiple bios for an atomic write.Signed-off-by Ritesh Harjani (IBM) <[email protected]>Signed-off-by Ojaswin Mujoo <[email protected]>
Looks good to me!
--D
Quoted 180 lines
--- tests/ext4/061 | 155 +++++++++++++++++++++++++++++++++++++++++++++ tests/ext4/061.out | 2 + 2 files changed, 157 insertions(+) create mode 100755 tests/ext4/061 create mode 100644 tests/ext4/061.outtests/ext4/061 new file +155
-0@@ -0,0 +1,155 @@ +#! /bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2025 IBM Corporation. All Rights Reserved. +# +# FS QA Test 061 +# +# This test does a lot of parallel RWF_ATOMIC IO on a preallocated file to +# stress the write and end-io unwritten conversion code paths. We brute force +# this for all possible blocksize and clustersizes and after each iteration we +# ensure the data was not torn or corrupted using fio crc verification. +# +# Note that in this test we use overlapping atomic writes of same io size. +# Although serializing racing writes is not guaranteed for RWF_ATOMIC, NVMe and +# SCSI provide this guarantee as an inseparable feature to power-fail +# atomicity. Keeping the iosize as same also ensures that ext4 doesn't tear the +# write due to racing ioend unwritten conversion. +# +# The value of this test is that we make sure the RWF_ATOMIC is handled +# correctly by ext4 as well as test that the block layer doesn't split or only +# generate multiple bios for an atomic write. + +. ./common/preamble +. ./common/atomicwrites + +_begin_fstest auto rw stress atomicwrites + +_require_scratch_write_atomic +_require_aiodio +_require_fio_version "3.38+" + +FIO_LOAD=$(($(nproc) * 2 * LOAD_FACTOR)) +SIZE=$((100*1024*1024)) + +# Calculate fsblocksize as per bdev atomic write units. +bdev_awu_min=$(_get_atomic_write_unit_min $SCRATCH_DEV) +bdev_awu_max=$(_get_atomic_write_unit_max $SCRATCH_DEV) +bs=$(_max 4096 "$bdev_awu_min") + +function create_fio_configs() +{ + local bsize=$1 + create_fio_aw_config $bsize + create_fio_verify_config $bsize +} + +function create_fio_verify_config() +{ + local bsize=$1 +cat >$fio_verify_config <<EOF + [aio-dio-aw-verify] + direct=1 + ioengine=libaio + rw=read + bs=$bsize + fallocate=native + filename=$SCRATCH_MNT/test-file + size=$SIZE + iodepth=$FIO_LOAD + numjobs=$FIO_LOAD + atomic=1 + group_reporting=1 + + verify_only=1 + verify_state_save=0 + verify=crc32c + verify_fatal=1 + verify_write_sequence=0 +EOF +} + +function create_fio_aw_config() +{ + local bsize=$1 +cat >$fio_aw_config <<EOF + [aio-dio-aw] + direct=1 + ioengine=libaio + rw=randwrite + bs=$bsize + fallocate=native + filename=$SCRATCH_MNT/test-file + size=$SIZE + iodepth=$FIO_LOAD + numjobs=$FIO_LOAD + group_reporting=1 + atomic=1 + + verify_state_save=0 + verify=crc32c + do_verify=0 + +EOF +} + +run_test_one() { + local bs=$1 + local cs=$2 + local iosize=$3 + + MKFS_OPTIONS="-O bigalloc -b $bs -C $cs" + _scratch_mkfs_ext4 >> $seqres.full 2>&1 || continue + if _try_scratch_mount >> $seqres.full 2>&1; then + echo "== Testing: bs=$bs cs=$cs iosize=$iosize ==" >> $seqres.full + + touch $SCRATCH_MNT/f1 + create_fio_configs $iosize + + cat $fio_aw_config >> $seqres.full + echo >> $seqres.full + cat $fio_verify_config >> $seqres.full + + $FIO_PROG $fio_aw_config >> $seqres.full + ret1=$? + + $FIO_PROG $fio_verify_config >> $seqres.full + ret2=$? + + _scratch_unmount + + [[ $ret1 -eq 0 && $ret2 -eq 0 ]] || _fail "fio with atomic write failed" + fi +} + +run_test() { + local bs=$1 + + # cluster sizes above 16 x blocksize are experimental so avoid them + # Also, cap cluster size at 128kb to keep it reasonable for large + # blocks size + max_cs=$(_min $((16 * bs)) "$bdev_awu_max" $((128 * 1024))) + + # Fuzz for combinations of blocksize, clustersize and + # iosize that cover most of the cases + run_test_one $bs $bs $bs + run_test_one $bs $max_cs $bs + run_test_one $bs $max_cs $max_cs + run_test_one $bs $max_cs $(_max "$((max_cs/2))" $bs) +} + +# Let's create a sample fio config to check whether fio supports all options. +fio_aw_config=$tmp.aw.fio +fio_verify_config=$tmp.verify.fio +fio_out=$tmp.fio.out + +create_fio_configs $bs +_require_fio $fio_aw_config + +for ((bs=$bs; bs <= $(_get_page_size); bs = $bs << 1)); do + run_test $bs +done + +# success, all done +echo Silence is golden +status=0 +exittests/ext4/061.out new file +2
-0@@ -0,0 +1,2 @@ +QA output created by 061 +Silence is goldenSignature · 1 line
2.49.0
Re: [PATCH v5 02/12] common/rc: Add _require_fio_version helper
In reply to Darrick J. WongQuoted 20 lines · Darrick J. Wong
Quoted 10 lines · Ojaswin Mujoo
Quoted 9 lines · Zorro Lang
Quoted 8 lines · Ojaswin Mujoo
The main motivation of adding this function on top of
_require_fiois that there has been a case in fio where atomic= option was added but later it was changed to noop since kernel didn't yet have support for atomic writes. It was then again utilized to do atomic writes in a later version, once kernel got the support. Due to this there is a point in fio where_require_fiow/ atomic=1 will succeed even though it would not be doing atomic writes.Quoted 8 lines
Quoted 8 lines
Quoted 2 lines
Hence, add an explicit helper to ensure tests to require specific versions of fio to work past such issues.
Actually I'm wondering if fstests really needs to care about this. This's just a temporary issue of fio, not kernel or any fs usespace program. Do we need to add a seperated helper only for a temporary fio issue? If fio doesn't break fstests running, let it run. Just the testers install proper fio (maybe latest) they need. What do you and others think?
Quoted 5 lines
Are there obvious failures if you try to run these new atomic write tests on a system with the weird versions of fio that have the no-op atomic= functionality? I'm concerned that some QA person is going to do that unwittingly and report that everything is ok when in reality they didn't actually test anything.
I think John has a bit more background but afaict, RWF_ATOMIC support was added (fio commit: d01612f3ae25) but then removed (commit:a25ba6c64fe1) since the feature didn't make it to kernel in time. However the option seemed to be kept in place. Later, commit 40f1fc11d added the support back in a later version of fio.
So yes, I think there are some version where fio will accept atomic=1 but not act upon it and the tests may start failing with no apparent reason.
Regards,
ojaswin
Quoted 70 lines
--D
Quoted 67 lines
Quoted 2 lines
Thanks,
ZorroHey Zorro,
Sure I'm okay with not keeping the helper and letting the user make sure the fio version is correct.
@John, does that sound okay?
Regards,
ojaswinQuoted 54 lines
Quoted 53 lines
Signed-off-by Ojaswin Mujoo <[email protected]>--- common/rc | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+)common/rc +32
-0@@ -5997,6 +5997,38 @@ _max() { echo $ret } +# Check the required fio version. Examples: +# _require_fio_version 3.38 (matches 3.38 only) +# _require_fio_version 3.38+ (matches 3.38 and above) +# _require_fio_version 3.38- (matches 3.38 and below) +_require_fio_version() { + local req_ver="$1" + local fio_ver + + _require_fio + _require_math + + fio_ver=$(fio -v | cut -d"-" -f2) + + case "$req_ver" in + *+) + req_ver=${req_ver%+} + test $(_math "$fio_ver >= $req_ver") -eq 1 || \ + _notrun "need fio >= $req_ver (found $fio_ver)" + ;; + *-) + req_ver=${req_ver%-} + test $(_math "$fio_ver <= $req_ver") -eq 1 || \ + _notrun "need fio <= $req_ver (found $fio_ver)" + ;; + *) + req_ver=${req_ver%-} + test $(_math "$fio_ver == $req_ver") -eq 1 || \ + _notrun "need fio = $req_ver (found $fio_ver)" + ;; + esac +} + ################################################################################ # make sure this script returns success /bin/trueSignature · 1 line
2.49.0
Re: [PATCH v5 02/12] common/rc: Add _require_fio_version helper
Quoted 33 lines · Ojaswin Mujoo
Quoted 20 lines · Darrick J. Wong
Quoted 10 lines · Ojaswin Mujoo
Quoted 9 lines · Zorro Lang
Quoted 8 lines · Ojaswin Mujoo
The main motivation of adding this function on top of
_require_fiois that there has been a case in fio where atomic= option was added but later it was changed to noop since kernel didn't yet have support for atomic writes. It was then again utilized to do atomic writes in a later version, once kernel got the support. Due to this there is a point in fio where_require_fiow/ atomic=1 will succeed even though it would not be doing atomic writes.Quoted 8 lines
Quoted 8 lines
Quoted 2 lines
Hence, add an explicit helper to ensure tests to require specific versions of fio to work past such issues.
Actually I'm wondering if fstests really needs to care about this. This's just a temporary issue of fio, not kernel or any fs usespace program. Do we need to add a seperated helper only for a temporary fio issue? If fio doesn't break fstests running, let it run. Just the testers install proper fio (maybe latest) they need. What do you and others think?
Quoted 5 lines
Are there obvious failures if you try to run these new atomic write tests on a system with the weird versions of fio that have the no-op atomic= functionality? I'm concerned that some QA person is going to do that unwittingly and report that everything is ok when in reality they didn't actually test anything.
I think John has a bit more background but afaict,
RWF_ATOMICsupport was added (fio commit:d01612f3ae25) but then removed (commit:a25ba6c64fe1) since the feature didn't make it to kernel in time. However the option seemed to be kept in place. Later, commit40f1fc11dadded the support back in a later version of fio.
Quoted 3 lines
So yes, I think there are some version where fio will accept atomic=1 but not act upon it and the tests may start failing with no apparent reason.
The concern from Darrick might be a problem. May I ask which fio commit brought in this issue, and which fio commit fixed it? If this issue be brought in and fixed within a fio release, it might be better. But if it crosses fio release, that might be bad, then we might be better to have this helper.
Thanks,
Zorro
Quoted 74 lines
Regards,
ojaswinQuoted 70 lines
--D
Quoted 67 lines
Quoted 2 lines
Thanks,
ZorroHey Zorro,
Sure I'm okay with not keeping the helper and letting the user make sure the fio version is correct.
@John, does that sound okay?
Regards,
ojaswinQuoted 54 lines
Quoted 53 lines
Signed-off-by Ojaswin Mujoo <[email protected]>--- common/rc | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+)common/rc +32
-0@@ -5997,6 +5997,38 @@ _max() { echo $ret } +# Check the required fio version. Examples: +# _require_fio_version 3.38 (matches 3.38 only) +# _require_fio_version 3.38+ (matches 3.38 and above) +# _require_fio_version 3.38- (matches 3.38 and below) +_require_fio_version() { + local req_ver="$1" + local fio_ver + + _require_fio + _require_math + + fio_ver=$(fio -v | cut -d"-" -f2) + + case "$req_ver" in + *+) + req_ver=${req_ver%+} + test $(_math "$fio_ver >= $req_ver") -eq 1 || \ + _notrun "need fio >= $req_ver (found $fio_ver)" + ;; + *-) + req_ver=${req_ver%-} + test $(_math "$fio_ver <= $req_ver") -eq 1 || \ + _notrun "need fio <= $req_ver (found $fio_ver)" + ;; + *) + req_ver=${req_ver%-} + test $(_math "$fio_ver == $req_ver") -eq 1 || \ + _notrun "need fio = $req_ver (found $fio_ver)" + ;; + esac +} + ################################################################################ # make sure this script returns success /bin/trueSignature · 1 line
2.49.0
Re: [PATCH v5 02/12] common/rc: Add _require_fio_version helper
Quoted 38 lines · Zorro Lang
Quoted 33 lines · Ojaswin Mujoo
Quoted 20 lines · Darrick J. Wong
Quoted 10 lines · Ojaswin Mujoo
Quoted 9 lines · Zorro Lang
Quoted 8 lines · Ojaswin Mujoo
The main motivation of adding this function on top of
_require_fiois that there has been a case in fio where atomic= option was added but later it was changed to noop since kernel didn't yet have support for atomic writes. It was then again utilized to do atomic writes in a later version, once kernel got the support. Due to this there is a point in fio where_require_fiow/ atomic=1 will succeed even though it would not be doing atomic writes.Quoted 8 lines
Quoted 8 lines
Quoted 2 lines
Hence, add an explicit helper to ensure tests to require specific versions of fio to work past such issues.
Actually I'm wondering if fstests really needs to care about this. This's just a temporary issue of fio, not kernel or any fs usespace program. Do we need to add a seperated helper only for a temporary fio issue? If fio doesn't break fstests running, let it run. Just the testers install proper fio (maybe latest) they need. What do you and others think?
Quoted 5 lines
Are there obvious failures if you try to run these new atomic write tests on a system with the weird versions of fio that have the no-op atomic= functionality? I'm concerned that some QA person is going to do that unwittingly and report that everything is ok when in reality they didn't actually test anything.
I think John has a bit more background but afaict,
RWF_ATOMICsupport was added (fio commit:d01612f3ae25) but then removed (commit:a25ba6c64fe1) since the feature didn't make it to kernel in time. However the option seemed to be kept in place. Later, commit40f1fc11dadded the support back in a later version of fio.Quoted 3 lines
So yes, I think there are some version where fio will accept atomic=1 but not act upon it and the tests may start failing with no apparent reason.
Quoted 5 lines
The concern from Darrick might be a problem. May I ask which fio commit brought in this issue, and which fio commit fixed it? If this issue be brought in and fixed within a fio release, it might be better. But if it crosses fio release, that might be bad, then we might be better to have this helper.
Hi Zorro, yes it does seem to cross version boundaries. The functionality was removed in fio v3.33 and added back in v3.38. I confirmed this by running generic/1226 with both (for v3.33 run i commented out a few fio options that were added later but kept atomic=1):
Command: sudo perf record -e iomap:iomap_dio_rw_begin ./check generic/1226
perf script sample with fio v3.33:
fio 6626 [000] 777.668017: iomap:iomap_dio_rw_begin: <.sniip.> flags DIRECT|WRITE|AIO_RW dio_flags aio 1
perf script sample with fio v3.39:
fio 9830 [000] 895.042747: iomap:iomap_dio_rw_begin: <.snip> flags ATOMIC|DIRECT|WRITE|AIO_RW dio_flags aio 1
So as we can see, even though the test passes with atomic=1, fio is not sending the RWF_ATOMIC flag in the older version.
In which case I believe it should be okay to keep the helper, right?
Thanks,
Ojaswin
Quoted 12 lines
Thanks,
ZorroQuoted 8 lines
Regards,
ojaswinQuoted 5 lines
--D
Quoted 2 lines
Quoted 2 lines
Thanks,
Zorro
Zorro Lang
Reviewed-byRe: [PATCH v5 02/12] common/rc: Add _require_fio_version helper
Quoted 39 lines · Ojaswin Mujoo
Quoted 38 lines · Zorro Lang
Quoted 36 lines · Ojaswin Mujoo
Quoted 20 lines · Darrick J. Wong
Quoted 10 lines · Ojaswin Mujoo
Quoted 9 lines · Zorro Lang
Quoted 8 lines · Ojaswin Mujoo
The main motivation of adding this function on top of
_require_fiois that there has been a case in fio where atomic= option was added but later it was changed to noop since kernel didn't yet have support for atomic writes. It was then again utilized to do atomic writes in a later version, once kernel got the support. Due to this there is a point in fio where_require_fiow/ atomic=1 will succeed even though it would not be doing atomic writes.Quoted 8 lines
Quoted 8 lines
Quoted 2 lines
Hence, add an explicit helper to ensure tests to require specific versions of fio to work past such issues.
Actually I'm wondering if fstests really needs to care about this. This's just a temporary issue of fio, not kernel or any fs usespace program. Do we need to add a seperated helper only for a temporary fio issue? If fio doesn't break fstests running, let it run. Just the testers install proper fio (maybe latest) they need. What do you and others think?
Quoted 5 lines
Are there obvious failures if you try to run these new atomic write tests on a system with the weird versions of fio that have the no-op atomic= functionality? I'm concerned that some QA person is going to do that unwittingly and report that everything is ok when in reality they didn't actually test anything.
I think John has a bit more background but afaict,
RWF_ATOMICsupport was added (fio commit:d01612f3ae25) but then removed (commit:a25ba6c64fe1) since the feature didn't make it to kernel in time. However the option seemed to be kept in place. Later, commit40f1fc11dadded the support back in a later version of fio.So yes, I think there are some version where fio will accept atomic=1 but not act upon it and the tests may start failing with no apparent reason.
Quoted 8 lines
Quoted 5 lines
The concern from Darrick might be a problem. May I ask which fio commit brought in this issue, and which fio commit fixed it? If this issue be brought in and fixed within a fio release, it might be better. But if it crosses fio release, that might be bad, then we might be better to have this helper.
Hi Zorro, yes it does seem to cross version boundaries. The functionality was removed in fio v3.33 and added back in v3.38. I
Thanks, if so I think let's have this helper for that issue :) But I think we still prioritize _require_fio. If it helpless, then call _require_fio_version.
Thanks,
Zorro
Quoted 35 lines
confirmed this by running generic/1226 with both (for v3.33 run i commented out a few fio options that were added later but kept atomic=1):
Command: sudo perf record -e iomap:iomap_dio_rw_begin ./check generic/1226
perf script sample with fio v3.33:
fio 6626 [000] 777.668017: iomap:iomap_dio_rw_begin: <.sniip.> flags DIRECT|WRITE|AIO_RW
dio_flagsaio 1perf script sample with fio v3.39:
fio 9830 [000] 895.042747: iomap:iomap_dio_rw_begin: <.snip> flags ATOMIC|DIRECT|WRITE|AIO_RW
dio_flagsaio 1So as we can see, even though the test passes with atomic=1, fio is not sending the
RWF_ATOMICflag in the older version.In which case I believe it should be okay to keep the helper, right?
Thanks,
OjaswinQuoted 12 lines
Thanks,
ZorroQuoted 8 lines
Regards,
ojaswinQuoted 5 lines
--D
Quoted 2 lines
Quoted 2 lines
Thanks,
Zorro
Re: [PATCH v5 02/12] common/rc: Add _require_fio_version helper
In reply to Zorro LangQuoted 6 lines · Zorro Lang
Quoted 6 lines
I think John has a bit more background but afaict,
RWF_ATOMICsupport was added (fio commit:d01612f3ae25) but then removed (commit:a25ba6c64fe1) since the feature didn't make it to kernel in time. However the option seemed to be kept in place. Later, commit40f1fc11dadded the support back in a later version of fio.
Quoted 8 lines
Quoted 3 lines
So yes, I think there are some version where fio will accept atomic=1 but not act upon it and the tests may start failing with no apparent reason.
The concern from Darrick might be a problem. May I ask which fio commit brought in this issue, and which fio commit fixed it? If this issue be brought in and fixed within a fio release, it might be better. But if it crosses fio release, that might be bad, then we might be better to have this helper.
The history is that fio atomic write support was originally added some time ago for out-of-kernel atomic write support, which was O_ATOMIC flag. Since O_ATOMIC never made it into the kernel, the feature was removed, but the plumbing for atomic writes stayed in fio - specifically the "atomic=" option. So I just reused that plumbing in d01612f3ae25 to support RWF_ATOMIC.
The point is that we should check the fio version, as different versions can give different behaviour for "atomic" option, those being:
a. O_ATOMIC (we definitely don't want this)
b. no nothing (bad)
c. use RWF_ATOMIC
Thanks,
John
Re: [PATCH v5 02/12] common/rc: Add _require_fio_version helper
In reply to Ojaswin MujooQuoted 21 lines · Ojaswin Mujoo
The main motivation of adding this function on top of
_require_fiois that there has been a case in fio where atomic= option was added but later it was changed to noop since kernel didn't yet have support for atomic writes. It was then again utilized to do atomic writes in a later version, once kernel got the support. Due to this there is a point in fio where_require_fiow/ atomic=1 will succeed even though it would not be doing atomic writes.Hence, add an explicit helper to ensure tests to require specific versions of fio to work past such issues.
Signed-off-by Ojaswin Mujoo <[email protected]>--- common/rc | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+)common/rc +0
-0
common/rc _max() { · line 6003
Quoted 7 lines
common/rc +4
-0@@ -5997,6 +5997,38 @@ _max() { echo $ret } +# Check the required fio version. Examples: +# _require_fio_version 3.38 (matches 3.38 only) +# _require_fio_version 3.38+ (matches 3.38 and above) +# _require_fio_version 3.38- (matches 3.38 and below)
This requires the user to know the version which corresponds to the feature. Is that how things are done for other such utilities and their versions vs features?
I was going to suggest exporting something like _require_fio_atomic_writes(), and _require_fio_atomic_writes() calls _require_fio_version() to check the version.
Thanks,
John
Re: [PATCH v5 04/12] ltp/fsx.c: Add atomic writes support to fsx
In reply to Ojaswin MujooQuoted 6 lines · Ojaswin Mujoo
Implement atomic write support to help fuzz atomic writes with fsx.
Suggested-by Ritesh Harjani (IBM) <[email protected]>Reviewed-by Darrick J. Wong <[email protected]>Signed-off-by Ojaswin Mujoo <[email protected]>
Generally this looks ok, but I do have some comments, so please check them:
Quoted 74 lines
--- ltp/fsx.c | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 110 insertions(+), 5 deletions(-)ltp/fsx.c +20
-0@@ -40,6 +40,7 @@#include <liburing.h> #endif #include <sys/syscall.h> +#include "statx.h" #ifndef MAP_FILE # define MAP_FILE 0@@ -49,6 +50,10 @@#define RWF_DONTCACHE 0x80#endif +#ifndef RWF_ATOMIC +#define RWF_ATOMIC 0x40 +#endif +#define NUMPRINTCOLUMNS 32/* # columns of data to print on each line */ /* Operation flags (bitmask) */ @@ -110,6 +115,7 @@ enum {OP_READ_DONTCACHE ,OP_WRITE ,OP_WRITE_DONTCACHE , +OP_WRITE_ATOMIC ,OP_MAPREAD ,OP_MAPWRITE , OP_MAX_LITE,@@ -200,6 +206,11 @@ int uring = 0;int mark_nr =0 ;int dontcache_io =1 ;int hugepages =0 ;/* -h flag */ +int do_atomic_writes =1 ;/* -a flag disables */ + +/* User for atomic writes */ +int awu_min =0 ; +int awu_max =0 ;/* Stores info needed to periodically collapse hugepages */ struct hugepages_collapse_info {@@ -288,6 +299,7 @@ static const char *op_names[] = { [OP_READ_DONTCACHE] = "read_dontcache", [OP_WRITE] = "write", [OP_WRITE_DONTCACHE] = "write_dontcache", + [OP_WRITE_ATOMIC] = "write_atomic", [OP_MAPREAD] = "mapread", [OP_MAPWRITE] = "mapwrite", [OP_TRUNCATE] = "truncate",@@ -422,6 +434,7 @@ logdump(void)prt ("\t***RRRR***" );break ;case OP_WRITE_DONTCACHE : +case OP_WRITE_ATOMIC :case OP_WRITE : prt("WRITE 0x%x thru 0x%x\t(0x%x bytes)", lp->args[0], lp->args[0] + lp->args[1] - 1,@@ -1073,6 +1086,25 @@ update_file_size(unsigned offset, unsigned size) file_size = offset + size; } +static int is_power_of_2 (unsigned n) { +return ((n & (n -1 )) ==0 ); +} + +/* +* Round down n to nearest power of 2. +* If n is already a power of 2, return n;
ltp/fsx.c update_file_size(unsigned offset, unsigned size) · line 1105
Quoted 10 lines
ltp/fsx.c +10
-0+*/ +static int rounddown_pow_of_2 (int n) { +int i =0 ; + +if (is_power_of_2 (n)) +return n; + +for (; (1 << i) < n; i++); + +return 1 << (i -1 );
Is this the neatest way to do this?
ltp/fsx.c dowrite(unsigned offset, unsigned size, int flags) · line 1117
Quoted 11 lines
ltp/fsx.c +4
-0+} +void dowrite (unsigned offset,unsigned size,int flags) {@@ -1081,6 +1113,27 @@ dowrite(unsigned offset, unsigned size, int flags) offset -= offset % writebdy;if (o_direct) size -= size % writebdy; +if (flags &RWF_ATOMIC ) { +/* atomic write len must be inbetween awu_min and awu_max */
in between
ltp/fsx.c dowrite(unsigned offset, unsigned size, int flags) · line 1124
Quoted 7 lines
ltp/fsx.c +7
-0+if (size < awu_min) + size = awu_min; +if (size > awu_max) + size = awu_max; + +/* atomic writes need power-of-2 sizes */ + size =rounddown_pow_of_2 (size);
you could have:
if (size < awu_min)
size = awu_min; else if (size > awu_max) size = awu_max; else size = rounddown_pow_of_2(size);
Quoted 28 lines
ltp/fsx.c +16
-1+ +/* atomic writes need naturally aligned offsets */ + offset -= offset % size; + +/* Skip the write if we are crossing max filesize */ +if ((offset + size) > maxfilelen) { +if (!quiet && testcalls > simulatedopcount) +prt ("skipping atomic write past maxfilelen\n" ); +log4 (OP_WRITE_ATOMIC , offset, size,FL_SKIPPED ); +return ; + } + }if (size ==0 ) {if (!quiet && testcalls > simulatedopcount && !o_direct)prt ("skipping zero size write\n" );@@ -1088,7 +1141,10 @@ dowrite(unsigned offset, unsigned size, int flags)return ; } -log4 (OP_WRITE , offset, size,FL_NONE ); +if (flags &RWF_ATOMIC ) +log4 (OP_WRITE_ATOMIC , offset, size,FL_NONE ); +else +log4 (OP_WRITE , offset, size,FL_NONE );gendata (original_buf, good_buf, offset, size);if (offset + size > file_size) {
ltp/fsx.c dowrite(unsigned offset, unsigned size, int flags) · line 1169
Quoted 8 lines
ltp/fsx.c +3
-2@@ -1108,8 +1164,9 @@ dowrite(unsigned offset, unsigned size, int flags) (monitorstart == -1 || (offset + size > monitorstart && (monitorend == -1 || offset <= monitorend)))))) -prt ("%lld write\t0x%x thru\t0x%x\t(0x%x bytes)\tdontcache=%d\n" , testcalls, - offset, offset + size -1 , size, (flags &RWF_DONTCACHE ) !=0 ); +prt ("%lld write\t0x%x thru\t0x%x\t(0x%x bytes)\tdontcache=%d atomic_wr=%d\n" , testcalls, + offset, offset + size -1 , size, (flags &RWF_DONTCACHE ) !=0 , + (flags &RWF_ATOMIC ) !=0 );
nit:
!!(flags & RWF_ATOMIC)
I find that a bit neater, but I suppose you are following the example for RWF_DONTCACHE
Quoted 24 lines
ltp/fsx.c +17
-0iret =fsxwrite (fd, good_buf + offset, size, offset, flags);if (iret != size) {if (iret ==-1 )@@ -1785,6 +1842,36 @@ do_dedupe_range(unsigned offset, unsigned length, unsigned dest) }#endif +int test_atomic_writes (void ) { +int ret; +struct statx stx; + +if (o_direct !=O_DIRECT ) { +fprintf (stderr,"main: atomic writes need O_DIRECT (-Z), " +"disabling!\n" ); +return 0 ; + } + + ret =xfstests_statx (AT_FDCWD , fname,0 ,STATX_WRITE_ATOMIC , &stx); +if (ret <0 ) { +fprintf (stderr,"main: Statx failed with %d." +" Failed to determine atomic write limits, " +" disabling!\n" , ret); +return 0 ; + }
ltp/fsx.c do_dedupe_range(unsigned offset, unsigned length, unsigned dest) · line 1871
Quoted 10 lines
ltp/fsx.c +10
-0+ +if (stx.stx_attributes &STATX_ATTR_WRITE_ATOMIC && + stx.stx_atomic_write_unit_min >0 ) { + awu_min = stx.stx_atomic_write_unit_min; + awu_max = stx.stx_atomic_write_unit_max; +return 1 ; + } + +fprintf (stderr,"main: IO Stack does not support " +"atomic writes, disabling!\n" );
Do we really need to spread this over multiple lines?
Maybe that is the coding standard - I don't know.
Quoted 48 lines
ltp/fsx.c +16
-1+return 0 ; +} +#ifdef HAVE_COPY_FILE_RANGE int test_copy_range (void )@@ -2356,6 +2443,12 @@ have_op: goto out; }break ; +case OP_WRITE_ATOMIC : +if (!do_atomic_writes) { +log4 (OP_WRITE_ATOMIC , offset, size,FL_SKIPPED ); + goto out; + } +break ; }switch (op) {@@ -2385,6 +2478,11 @@ have_op:dowrite (offset, size,0 );break ; +case OP_WRITE_ATOMIC : +TRIM_OFF_LEN (offset, size, maxfilelen); +dowrite (offset, size,RWF_ATOMIC ); +break ; +case OP_MAPREAD :TRIM_OFF_LEN (offset, size, file_size);domapread (offset, size);@@ -2511,13 +2609,14 @@ voidusage (void) { fprintf(stdout, "usage: %s", - "fsx [-dfhknqxyzBEFHIJKLORWXZ0]\n\ + "fsx [-adfhknqxyzBEFHIJKLORWXZ0]\n\ [-b opnum] [-c Prob] [-g filldata] [-i logdev] [-j logid]\n\ [-l flen] [-m start:end] [-o oplen] [-p progressinterval]\n\ [-r readbdy] [-s style] [-t truncbdy] [-w writebdy]\n\ [-A|-U] [-D startingop] [-N numops] [-P dirpath] [-S seed]\n\ [--replay-ops=opsfile] [--record-ops[=opsfile]] [--duration=seconds]\n\ ... fname\n\ + -a: disable atomic writes\n\ -b opnum: beginning operation number (default 1)\n\ -c P: 1 in P chance of file close+open at each op (default infinity)\n\ -d: debug output for all operations\n\
ltp/fsx.c main(int argc, char **argv) · line 3166
Quoted 10 lines
ltp/fsx.c +4
-1@@ -3059,9 +3158,13 @@ main(int argc, char **argv)setvbuf (stdout, (char *)0 , _IOLBF,0 );/* line buffered stdout */ while ((ch =getopt_long (argc, argv, -"0b:c:de:fg:hi:j:kl:m:no:p:qr:s:t:uw:xyABD:EFJKHzCILN:OP:RS:UWXZ" , +"0ab:c:de:fg:hi:j:kl:m:no:p:qr:s:t:uw:xyABD:EFJKHzCILN:OP:RS:UWXZ" , longopts,NULL )) !=EOF )switch (ch) { +case 'a' : +prt ("main(): Atomic writes disabled\n" ); + do_atomic_writes =0 ;
why an opt-out (and not opt-in)?
Quoted 13 lines
ltp/fsx.c +3
-0+break ;case 'b' : simulatedopcount =getnum (optarg, &endp);if (!quiet)@@ -3475,6 +3578,8 @@ main(int argc, char **argv) exchange_range_calls =test_exchange_range ();if (dontcache_io) dontcache_io =test_dontcache_io (); +if (do_atomic_writes) + do_atomic_writes =test_atomic_writes ();while (keep_running ())if (!test ())
Re: [PATCH v5 05/12] generic: Add atomic write test using fio crc check verifier
In reply to Ojaswin MujooQuoted 9 lines · Ojaswin Mujoo
This adds atomic write test using fio based on it's crc check verifier. fio adds a crc header for each data block, which is verified later to ensure there is no data corruption or torn write.
This test essentially does a lot of parallel
RWF_ATOMICIO on a preallocated file to stress the write and end-io unwritten conversion code paths. The idea is to increase code coverage to ensureRWF_ATOMIChasn't introduced any issues.
Quoted 8 lines
Avoid doing overlapping parallel atomic writes because it might give unexpected results. Use offset_increment=, size= fio options to achieve this behavior.
Co-developed-by Ritesh Harjani (IBM)<[email protected]>Signed-off-by Ritesh Harjani (IBM)<[email protected]>Reviewed-by Darrick J. Wong<[email protected]>Signed-off-by Ojaswin Mujoo<[email protected]>
I am still a bit skeptical of the value of this test, but here is my tag anyway:
Re: [PATCH v5 06/12] generic: Add atomic write test using fio verify on file mixed mappings
In reply to Ojaswin MujooQuoted 9 lines · Ojaswin Mujoo
This test uses fio to first create a file with mixed mappings. Then it does atomic writes using aio dio with parallel jobs to the same file with mixed mappings. Finally, we perform a fio verify step to ensure there is no data corruption or torn write.
The aim is to stress the FS block allocation and extent handling logic to ensure it handles mixed mappings with
RWF_ATOMICcorrectly without tearing or losing data.
Quoted 8 lines
Avoid doing overlapping parallel atomic writes because it might give unexpected results. Use offset_increment=, size= fio options to achieve this behavior.
Co-developed-by Ritesh Harjani (IBM)<[email protected]>Signed-off-by Ritesh Harjani (IBM)<[email protected]>Reviewed-by Darrick J. Wong<[email protected]>Signed-off-by Ojaswin Mujoo<[email protected]>
Re: [PATCH v5 07/12] generic: Add atomic write multi-fsblock O_[D]SYNC tests
In reply to Ojaswin MujooQuoted 1 line · Ojaswin Mujoo
This adds various atomic write multi-fsblock stresst tests
stress
Quoted 6 lines
with mixed mappings and
O_SYNC, to ensure the data and metadata is atomically persisted even if there is a shutdown.Suggested-by Ritesh Harjani (IBM) <[email protected]>Reviewed-by Darrick J. Wong <[email protected]>Signed-off-by Ojaswin Mujoo <[email protected]>
Regardless of any comments made (which are minor):
Quoted 125 lines
--- tests/generic/1228 | 137 +++++++++++++++++++++++++++++++++++++++++ tests/generic/1228.out | 2 + 2 files changed, 139 insertions(+)create mode 100755 tests/generic/1228
create mode 100644 tests/generic/1228.outtests/generic/1228 new file +112
-0@@ -0,0 +1,137 @@ +#! /bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2025 IBM Corporation. All Rights Reserved. +# +# FS QA Test 1228 +# +# Atomic write multi-fsblock data integrity tests with mixed mappings +# and O_SYNC +# +. ./common/preamble +. ./common/atomicwrites +_begin_fstest auto quick rw atomicwrites + +_require_scratch_write_atomic_multi_fsblock +_require_atomic_write_test_commands +_require_scratch_shutdown +_require_xfs_io_command "truncate" + +_scratch_mkfs >> $seqres.full +_scratch_mount >> $seqres.full + +check_data_integrity() { + actual=$(_hexdump $testfile) + if [[ "$expected" != "$actual" ]] + then + echo "Integrity check failed" + echo "Integrity check failed" >> $seqres.full + echo "# Expected file contents:" >> $seqres.full + echo "$expected" >> $seqres.full + echo "# Actual file contents:" >> $seqres.full + echo "$actual" >> $seqres.full + + _fail "Data integrity check failed. The atomic write was torn." + fi +} + +prep_mixed_mapping() { + $XFS_IO_PROG -c "truncate 0" $testfile >> $seqres.full + local off=0 + local mapping="" + + local operations=("W" "H" "U") + local num_blocks=$((awu_max / blksz)) + for ((i=0; i<num_blocks; i++)); do + local index=$((RANDOM % ${#operations[@]})) + local map="${operations[$index]}" + local mapping="${mapping}${map}" + + case "$map" in + "W") + $XFS_IO_PROG -dc "pwrite -S 0x61 -b $blksz $off $blksz" $testfile > /dev/null + ;; + "H") + # No operation needed for hole + ;; + "U") + $XFS_IO_PROG -c "falloc $off $blksz" $testfile >> /dev/null + ;; + esac + off=$((off + blksz)) + done + + echo "+ + Mixed mapping prep done. Full mapping pattern: $mapping" >> $seqres.full + + sync $testfile +} + +verify_atomic_write() { + test $bytes_written -eq $awu_max || _fail "atomic write len=$awu_max assertion failed" + check_data_integrity +} + +mixed_mapping_test() { + prep_mixed_mapping + + echo -"+ + Performing O_DSYNC atomic write from 0 to $awu_max" >> $seqres.full + if [[ "$1" == "shutdown" ]] + then + bytes_written=$($XFS_IO_PROG -x -dc \ + "pwrite -DA -V1 -b $awu_max 0 $awu_max" \ + -c "shutdown" $testfile | grep wrote | \ + awk -F'[/ ]' '{print $2}') + _scratch_cycle_mount >>$seqres.full 2>&1 || _fail "remount failed" + else + bytes_written=$($XFS_IO_PROG -dc \ + "pwrite -DA -V1 -b $awu_max 0 $awu_max" $testfile | \ + grep wrote | awk -F'[/ ]' '{print $2}') + fi + + verify_atomic_write +} + +testfile=$SCRATCH_MNT/testfile +touch $testfile + +awu_max=$(_get_atomic_write_unit_max $testfile) +blksz=$(_get_block_size $SCRATCH_MNT) + +# Create an expected pattern to compare with +$XFS_IO_PROG -tc "pwrite -b $awu_max 0 $awu_max" $testfile >> $seqres.full +expected=$(_hexdump $testfile) +echo "# Expected file contents:" >> $seqres.full +echo "$expected" >> $seqres.full +echo >> $seqres.full + +echo "# Test 1: Do O_DSYNC atomic write on random mixed mapping:" >> $seqres.full +echo >> $seqres.full +for ((iteration=1; iteration<=10; iteration++)); do + echo "=== Mixed Mapping Test Iteration $iteration ===" >> $seqres.full + + echo "+ Testing without shutdown..." >> $seqres.full + mixed_mapping_test
tests/generic/1228 line 122
Quoted 10 lines
tests/generic/1228 new file +10
-0+ echo "Passed!" >> $seqres.full + + echo "+ Testing with sudden shutdown..." >> $seqres.full + mixed_mapping_test "shutdown" + echo "Passed!" >> $seqres.full + + echo "Iteration $iteration completed: OK" >> $seqres.full + echo >> $seqres.full +done +echo "# Test 1: Do O_SYNC atomic write on random mixed mapping (10 iterations): OK" >> $seqres.full
big nit: you should keep the loop count in a global variable
Quoted 23 lines
tests/generic/1228 new file +15
-0+ + +echo >> $seqres.full +echo "# Test 2: Do extending O_SYNC atomic writes: " >> $seqres.full +bytes_written=$($XFS_IO_PROG -x -dstc "pwrite -A -V1 -b $awu_max 0 $awu_max" \ + -c "shutdown" $testfile | grep wrote | awk -F'[/ ]' '{print $2}') +_scratch_cycle_mount >>$seqres.full 2>&1 || _fail "remount failed" +verify_atomic_write +echo "# Test 2: Do extending O_SYNC atomic writes: OK" >> $seqres.full + +# success, all done +echo "Silence is golden" +status=0 +exit +tests/generic/1228.out new file +2
-0@@ -0,0 +1,2 @@ +QA output created by 1228 +Silence is golden
Re: [PATCH v5 08/12] generic: Stress fsx with atomic writes enabled
In reply to Ojaswin MujooQuoted 1 line · Ojaswin Mujoo
Stress file with atomic writes to ensure we excercise codepaths
exercise
Quoted 5 lines
where we are mixing different FS operations with atomic writes
Suggested-by Ritesh Harjani (IBM) <[email protected]>Reviewed-by Darrick J. Wong <[email protected]>Signed-off-by Ojaswin Mujoo <[email protected]>
Quoted 37 lines
--- tests/generic/1229 | 68 ++++++++++++++++++++++++++++++++++++++++++ tests/generic/1229.out | 2 ++ 2 files changed, 70 insertions(+)create mode 100755 tests/generic/1229
create mode 100644 tests/generic/1229.outtests/generic/1229 new file +24
-0@@ -0,0 +1,68 @@ +#! /bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2025 IBM Corporation. All Rights Reserved. +# +# FS QA Test 1229 +# +# fuzz fsx with atomic writes +# +. ./common/preamble +. ./common/atomicwrites +_begin_fstest rw auto quick atomicwrites + +_require_odirect +_require_scratch_write_atomic + +_scratch_mkfs >> $seqres.full 2>&1 +_scratch_mount >> $seqres.full 2>&1 + +testfile=$SCRATCH_MNT/testfile +touch $testfile + +awu_max=$(_get_atomic_write_unit_max $testfile) +blksz=$(_get_block_size $SCRATCH_MNT) +bsize=`$here/src/min_dio_alignment $SCRATCH_MNT $SCRATCH_DEV`
tests/generic/1229 line 34
Quoted 10 lines
tests/generic/1229 new file +10
-0+ +set_fsx_avoid() { + local file=$1 + + case "$FSTYP" in + "ext4") + local dev=$(findmnt -n -o SOURCE --target $testfile) + + # fsx insert/collpase range support for ext4+bigalloc is + # currently broken, so disable it. Also disable incase we can't
in case
Quoted 42 lines
tests/generic/1229 new file +34
-0+ # detect bigalloc to be on safer side. + if [ -z "$DUMPE2FS_PROG" ]; then + echo "dumpe2fs not found, disabling insert/collapse range" >> $seqres.full + FSX_AVOID+=" -I -C" + return + fi + + $DUMPE2FS_PROG -h $dev 2>&1 | grep -q bigalloc && { + echo "fsx insert/collapse range not supported with bigalloc. Disabling.." >> $seqres.full + FSX_AVOID+=" -I -C" + } + ;; + *) + ;; + esac +} + +# fsx usage: +# +# -N numops: total # operations to do +# -l flen: the upper bound on file size +# -o oplen: the upper bound on operation size (64k default) +# -Z: O_DIRECT () + +set_fsx_avoid +_run_fsx_on_file $testfile -N 10000 -o $awu_max -A -l 500000 -r $bsize -w $bsize -Z $FSX_AVOID >> $seqres.full +if [[ "$?" != "0" ]] +then + _fail "fsx returned error: $?" +fi + +echo "Silence is golden" +status=0 +exittests/generic/1229.out new file +2
-0@@ -0,0 +1,2 @@ +QA output created by 1229 +Silence is golden
Re: [PATCH v5 09/12] generic: Add sudden shutdown tests for multi block atomic writes
In reply to Ojaswin MujooQuoted 10 lines · Ojaswin Mujoo
This test is intended to ensure that multi blocks atomic writes maintain atomic guarantees across sudden FS shutdowns.
The way we work is that we lay out a file with random mix of written, unwritten and hole extents. Then we start performing atomic writes sequentially on the file while we parallely shutdown the FS. Then we note the last offset where the atomic write happened just before shut down and then make sure blocks around it either have completely old data or completely new data, ie the write was not torn during shutdown.
Quoted 7 lines
We repeat the same with completely written, completely unwritten and completely empty file to ensure these cases are not torn either. Finally, we have a similar test for append atomic writes
Suggested-by Ritesh Harjani (IBM) <[email protected]>Reviewed-by Darrick J. Wong <[email protected]>Signed-off-by Ojaswin Mujoo <[email protected]>
Please check comments, below, thanks!
Quoted 19 lines
--- tests/generic/1230 | 397 +++++++++++++++++++++++++++++++++++++++++ tests/generic/1230.out | 2 + 2 files changed, 399 insertions(+)create mode 100755 tests/generic/1230
create mode 100644 tests/generic/1230.outtests/generic/1230 new file +6
-0@@ -0,0 +1,397 @@ +#! /bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2025 IBM Corporation. All Rights Reserved. +# +# FS QA Test No. 1230 +#
tests/generic/1230 line 16
Quoted 10 lines
tests/generic/1230 new file +10
-0+# Test multi block atomic writes with sudden FS shutdowns to ensure +# the FS is not tearing the write operation +. ./common/preamble +. ./common/atomicwrites +_begin_fstest auto atomicwrites + +_require_scratch_write_atomic_multi_fsblock +_require_atomic_write_test_commands +_require_scratch_shutdown +_require_xfs_io_command "truncate"
is a similar fallocate test needed?
Quoted 5 lines
tests/generic/1230 new file +5
-0+ +_scratch_mkfs >> $seqres.full 2>&1 +_scratch_mount >> $seqres.full + +testfile=$SCRATCH_MNT/testfile
tests/generic/1230 line 31
Quoted 10 lines
tests/generic/1230 new file +10
-0+touch $testfile + +awu_max=$(_get_atomic_write_unit_max $testfile) +blksz=$(_get_block_size $SCRATCH_MNT) +echo "Awu max: $awu_max" >> $seqres.full + +num_blocks=$((awu_max / blksz)) +# keep initial value high for dry run. This will be +# tweaked in dry_run() based on device write speed. +filesize=$(( 10 * 1024 * 1024 * 1024 ))
could this cause some out-of-space issue? That's 10GB, right?
Quoted 10 lines
tests/generic/1230 new file +10
-0+ +_cleanup() { + [ -n "$awloop_pid" ] && kill $awloop_pid &> /dev/null + wait +} + +atomic_write_loop() { + local off=0 + local size=$awu_max + for ((i=0; i<$((filesize / $size )); i++)); do
tests/generic/1230 line 51
Quoted 10 lines
tests/generic/1230 new file +10
-0+ # Due to sudden shutdown this can produce errors so just + # redirect them to seqres.full + $XFS_IO_PROG -c "open -fsd $testfile" -c "pwrite -S 0x61 -DA -V1 -b $size $off $size" >> /dev/null 2>>$seqres.full + echo "Written to offset: $off" >> $tmp.aw + off=$((off + $size)) + done +} + +# This test has the following flow: +# 1. Start doing sequential atomic writes in bg, upto $filesize
bg?
Quoted 41 lines
tests/generic/1230 new file +41
-0+# 2. Sleep for 0.2s and shutdown the FS +# 3. kill the atomic write process +# 4. verify the writes were not torn +# +# We ideally want the shutdown to happen while an atomic write is ongoing +# but this gets tricky since faster devices can actually finish the whole +# atomic write loop before sleep 0.2s completes, resulting in the shutdown +# happening after the write loop which is not what we want. A simple solution +# to this is to increase $filesize so step 1 takes long enough but a big +# $filesize leads to create_mixed_mappings() taking very long, which is not +# ideal. +# +# Hence, use the dry_run function to figure out the rough device speed and set +# $filesize accordingly. +dry_run() { + echo >> $seqres.full + echo "# Estimating ideal filesize..." >> $seqres.full + atomic_write_loop & + awloop_pid=$! + + local i=0 + # Wait for atleast first write to be recorded or 10s + while [ ! -f "$tmp.aw" -a $i -le 50 ]; do i=$((i + 1)); sleep 0.2; done + + if [[ $i -gt 50 ]] + then + _fail "atomic write process took too long to start" + fi + + echo >> $seqres.full + echo "# Shutting down filesystem while write is running" >> $seqres.full + _scratch_shutdown + + kill $awloop_pid 2>/dev/null # the process might have finished already + wait $awloop_pid + unset $awloop_pid + + bytes_written=$(tail -n 1 $tmp.aw | cut -d" " -f4) + echo "# Bytes written in 0.2s: $bytes_written" >> $seqres.full + + filesize=$((bytes_written * 3))
tests/generic/1230 line 102
Quoted 10 lines
tests/generic/1230 new file +10
-0+ echo "# Setting \$filesize=$filesize" >> $seqres.full + + rm $tmp.aw + sleep 0.5 + + _scratch_cycle_mount + +} + +create_mixed_mappings() {
Is this same as patch 08/12?
Quoted 5 lines
tests/generic/1230 new file +5
-0+ local file=$1 + local size_bytes=$2 + + echo "# Filling file $file with alternate mappings till size $size_bytes" >> $seqres.full + #Fill the file with alternate written and unwritten blocks
tests/generic/1230 line 117
Quoted 10 lines
tests/generic/1230 new file +10
-0+ local off=0 + local operations=("W" "U") + + for ((i=0; i<$((size_bytes / blksz )); i++)); do + index=$(($i % ${#operations[@]})) + map="${operations[$index]}" + + case "$map" in + "W") + $XFS_IO_PROG -fc "pwrite -b $blksz $off $blksz" $file >> /dev/null
does this just write random data? I don't see any pattern being set.
Quoted 40 lines
tests/generic/1230 new file +40
-0+ ;; + "U") + $XFS_IO_PROG -fc "falloc $off $blksz" $file >> /dev/null + ;; + esac + off=$((off + blksz)) + done + + sync $file +} + +populate_expected_data() { + # create a dummy file with expected old data for different cases + create_mixed_mappings $testfile.exp_old_mixed $awu_max + expected_data_old_mixed=$(od -An -t x1 -j 0 -N $awu_max $testfile.exp_old_mixed) + + $XFS_IO_PROG -fc "falloc 0 $awu_max" $testfile.exp_old_zeroes >> $seqres.full + expected_data_old_zeroes=$(od -An -t x1 -j 0 -N $awu_max $testfile.exp_old_zeroes) + + $XFS_IO_PROG -fc "pwrite -b $awu_max 0 $awu_max" $testfile.exp_old_mapped >> $seqres.full + expected_data_old_mapped=$(od -An -t x1 -j 0 -N $awu_max $testfile.exp_old_mapped) + + # create a dummy file with expected new data + $XFS_IO_PROG -fc "pwrite -S 0x61 -b $awu_max 0 $awu_max" $testfile.exp_new >> $seqres.full + expected_data_new=$(od -An -t x1 -j 0 -N $awu_max $testfile.exp_new) +} + +verify_data_blocks() { + local verify_start=$1 + local verify_end=$2 + local expected_data_old="$3" + local expected_data_new="$4" + + echo >> $seqres.full + echo "# Checking data integrity from $verify_start to $verify_end" >> $seqres.full + + # After an atomic write, for every chunk we ensure that the underlying + # data is either the old data or new data as writes shouldn't get torn. + local off=$verify_start + while [[ "$off" -lt "$verify_end" ]]
tests/generic/1230 line 167
Quoted 10 lines
tests/generic/1230 new file +10
-0+ do + #actual_data=$(xxd -s $off -l $awu_max -p $testfile) + actual_data=$(od -An -t x1 -j $off -N $awu_max $testfile) + if [[ "$actual_data" != "$expected_data_new" ]] && [[ "$actual_data" != "$expected_data_old" ]] + then + echo "Checksum match failed at off: $off size: $awu_max" + echo "Expected contents: (Either of the 2 below):" + echo + echo "Expected old: " + echo "$expected_data_old"
it would be nice if this was deterministic - see comment in create_mixed_mappings
tests/generic/1230 line 170
Quoted 3 lines
tests/generic/1230 new file +3
-0+ echo + echo "Expected new: " + echo "$expected_data_new"
nit: I am not sure what is meant by "expected". I would just have "new data". We don't know what to expect, as it could be old or new, right?
Quoted 15 lines
tests/generic/1230 new file +15
-0+ echo + echo "Actual contents: " + echo "$actual_data" + + _fail + fi + echo -n "Check at offset $off suceeded! " >> $seqres.full + if [[ "$actual_data" == "$expected_data_new" ]] + then + echo "matched new" >> $seqres.full + elif [[ "$actual_data" == "$expected_data_old" ]] + then + echo "matched old" >> $seqres.full + fi + off=$(( off + awu_max ))
tests/generic/1230 line 195
Quoted 10 lines
tests/generic/1230 new file +10
-0+ done +} + +# test data integrity for file by shutting down in between atomic writes +test_data_integrity() { + echo >> $seqres.full + echo "# Writing atomically to file in background" >> $seqres.full + atomic_write_loop & + awloop_pid=$! +
from here ...
Quoted 6 lines
tests/generic/1230 new file +6
-0+ local i=0 + # Wait for atleast first write to be recorded or 10s + while [ ! -f "$tmp.aw" -a $i -le 50 ]; do i=$((i + 1)); sleep 0.2; done + + if [[ $i -gt 50 ]] + then
tests/generic/1230 line 87
Quoted 10 lines
tests/generic/1230 new file +10
-0+ _fail "atomic write process took too long to start" + fi + + echo >> $seqres.full + echo "# Shutting down filesystem while write is running" >> $seqres.full + _scratch_shutdown + + kill $awloop_pid 2>/dev/null # the process might have finished already + wait $awloop_pid + unset $awloop_pid
... to here looks similar in many functions. Can we factor it out?
Quoted 20 lines
tests/generic/1230 new file +20
-0+ + last_offset=$(tail -n 1 $tmp.aw | cut -d" " -f4) + if [[ -z $last_offset ]] + then + last_offset=0 + fi + + echo >> $seqres.full + echo "# Last offset of atomic write: $last_offset" >> $seqres.full + + rm $tmp.aw + sleep 0.5 + + _scratch_cycle_mount + + # we want to verify all blocks around which the shutdown happended + verify_start=$(( last_offset - (awu_max * 5))) + if [[ $verify_start < 0 ]] + then + verify_start=0
tests/generic/1230 line 241
Quoted 10 lines
tests/generic/1230 new file +10
-0+ fi + + verify_end=$(( last_offset + (awu_max * 5))) + if [[ "$verify_end" -gt "$filesize" ]] + then + verify_end=$filesize + fi +} + +# test data integrity for file wiht written and unwritten mappings
with
Quoted 4 lines
tests/generic/1230 new file +4
-0+test_data_integrity_mixed() { + $XFS_IO_PROG -fc "truncate 0" $testfile >> $seqres.full + + echo >> $seqres.full
tests/generic/1230 line 255
Quoted 10 lines
tests/generic/1230 new file +10
-0+ echo "# Creating testfile with mixed mappings" >> $seqres.full + create_mixed_mappings $testfile $filesize + + test_data_integrity + + verify_data_blocks $verify_start $verify_end "$expected_data_old_mixed" "$expected_data_new" +} + +# test data integrity for file with completely written mappings +test_data_integrity_writ() {
please spell "writ" out fully, which I think should be "written"
Quoted 4 lines
tests/generic/1230 new file +4
-0+ $XFS_IO_PROG -fc "truncate 0" $testfile >> $seqres.full + + echo >> $seqres.full + echo "# Creating testfile with fully written mapping" >> $seqres.full
tests/generic/1230 line 269
Quoted 10 lines
tests/generic/1230 new file +10
-0+ $XFS_IO_PROG -c "pwrite -b $filesize 0 $filesize" $testfile >> $seqres.full + sync $testfile + + test_data_integrity + + verify_data_blocks $verify_start $verify_end "$expected_data_old_mapped" "$expected_data_new" +} + +# test data integrity for file with completely unwritten mappings +test_data_integrity_unwrit() {
same as above
Quoted 112 lines
tests/generic/1230 new file +112
-0+ $XFS_IO_PROG -fc "truncate 0" $testfile >> $seqres.full + + echo >> $seqres.full + echo "# Creating testfile with fully unwritten mappings" >> $seqres.full + $XFS_IO_PROG -c "falloc 0 $filesize" $testfile >> $seqres.full + sync $testfile + + test_data_integrity + + verify_data_blocks $verify_start $verify_end "$expected_data_old_zeroes" "$expected_data_new" +} + +# test data integrity for file with no mappings +test_data_integrity_hole() { + $XFS_IO_PROG -fc "truncate 0" $testfile >> $seqres.full + + echo >> $seqres.full + echo "# Creating testfile with no mappings" >> $seqres.full + $XFS_IO_PROG -c "truncate $filesize" $testfile >> $seqres.full + sync $testfile + + test_data_integrity + + verify_data_blocks $verify_start $verify_end "$expected_data_old_zeroes" "$expected_data_new" +} + +test_filesize_integrity() { + $XFS_IO_PROG -c "truncate 0" $testfile >> $seqres.full + + echo >> $seqres.full + echo "# Performing extending atomic writes over file in background" >> $seqres.full + atomic_write_loop & + awloop_pid=$! + + local i=0 + # Wait for atleast first write to be recorded or 10s + while [ ! -f "$tmp.aw" -a $i -le 50 ]; do i=$((i + 1)); sleep 0.2; done + + if [[ $i -gt 50 ]] + then + _fail "atomic write process took too long to start" + fi + + echo >> $seqres.full + echo "# Shutting down filesystem while write is running" >> $seqres.full + _scratch_shutdown + + kill $awloop_pid 2>/dev/null # the process might have finished already + wait $awloop_pid + unset $awloop_pid + + local last_offset=$(tail -n 1 $tmp.aw | cut -d" " -f4) + if [[ -z $last_offset ]] + then + last_offset=0 + fi + + echo >> $seqres.full + echo "# Last offset of atomic write: $last_offset" >> $seqres.full + rm $tmp.aw + sleep 0.5 + + _scratch_cycle_mount + local filesize=$(_get_filesize $testfile) + echo >> $seqres.full + echo "# Filesize after shutdown: $filesize" >> $seqres.full + + # To confirm that the write went atomically, we check: + # 1. The last block should be a multiple of awu_max + # 2. The last block should be the completely new data + + if (( $filesize % $awu_max )) + then + echo "Filesize after shutdown ($filesize) not a multiple of atomic write unit ($awu_max)" + fi + + verify_start=$(( filesize - (awu_max * 5))) + if [[ $verify_start < 0 ]] + then + verify_start=0 + fi + + local verify_end=$filesize + + # Here the blocks should always match new data hence, for simplicity of + # code, just corrupt the $expected_data_old buffer so it never matches + local expected_data_old="POISON" + verify_data_blocks $verify_start $verify_end "$expected_data_old" "$expected_data_new" +} + +$XFS_IO_PROG -fc "truncate 0" $testfile >> $seqres.full + +dry_run + +echo >> $seqres.full +echo "# Populating expected data buffers" >> $seqres.full +populate_expected_data + +# Loop 20 times to shake out any races due to shutdown +for ((iter=0; iter<20; iter++)) +do + echo >> $seqres.full + echo "------ Iteration $iter ------" >> $seqres.full + + echo >> $seqres.full + echo "# Starting data integrity test for atomic writes over mixed mapping" >> $seqres.full + test_data_integrity_mixed + + echo >> $seqres.full + echo "# Starting data integrity test for atomic writes over fully written mapping" >> $seqres.full + test_data_integrity_writ +
tests/generic/1230 line 391
Quoted 10 lines
tests/generic/1230 new file +10
-0+ echo >> $seqres.full + echo "# Starting data integrity test for atomic writes over fully unwritten mapping" >> $seqres.full + test_data_integrity_unwrit + + echo >> $seqres.full + echo "# Starting data integrity test for atomic writes over holes" >> $seqres.full + test_data_integrity_hole + + echo >> $seqres.full + echo "# Starting filesize integrity test for atomic writes" >> $seqres.full
what does "Starting filesize integrity test" mean?
Quoted 14 lines
tests/generic/1230 new file +6
-0+ test_filesize_integrity +done + +echo "Silence is golden" +status=0 +exittests/generic/1230.out new file +2
-0@@ -0,0 +1,2 @@ +QA output created by 1230 +Silence is golden
Re: [PATCH v5 10/12] ext4: test atomic write and ioend codepaths with bigalloc
In reply to Ojaswin MujooQuoted 14 lines · Ojaswin Mujoo
From: "Ritesh Harjani (IBM)" <[email protected]>
This test does a lot of parallel
RWF_ATOMICIO on a preallocated file to stress the write and end-io unwritten conversion code paths. We brute force this for different blocksize and clustersizes and after each iteration we ensure the data was not torn or corrupted using fio crc verification.Note that in this test we use overlapping atomic writes of same io size. Although serializing racing writes is not guaranteed for
RWF_ATOMIC, NVMe and SCSI provide this guarantee as an inseparable feature to power-fail atomicity. Keeping the iosize as same also ensures that ext4 doesn't tear the write due to racing ioend unwritten conversion.
Quoted 6 lines
The value of this test is that we make sure the
RWF_ATOMICis handled correctly by ext4 as well as test that the block layer doesn't split or only generate multiple bios for an atomic write.Signed-off-by Ritesh Harjani (IBM) <[email protected]>Signed-off-by Ojaswin Mujoo <[email protected]>
Re: [PATCH v5 11/12] ext4: Test atomic writes allocation and write codepaths with bigalloc
In reply to Ojaswin MujooQuoted 117 lines · Ojaswin Mujoo
From: "Ritesh Harjani (IBM)" <[email protected]>
This test does a parallel
RWF_ATOMICIO on a multiple truncated files in a small FS. The idea is to stress ext4 allocator to ensure we are able to handle low space scenarios correctly with atomic writes. We brute force this for different blocksize and clustersizes and after each iteration we ensure the data was not torn or corrupted using fio crc verification.Note that in this test we use overlapping atomic writes of same io size. Although serializing racing writes is not guaranteed for
RWF_ATOMIC, NVMe and SCSI provide this guarantee as an inseparable feature to power-fail atomicity. Keeping the iosize as same also ensures that ext4 doesn't tear the write due to racing ioend unwritten conversion.The value of this test is that we make sure the
RWF_ATOMICis handled correctly by ext4 as well as test that the block layer doesn't split or only generate multiple bios for an atomic write.Signed-off-by Ritesh Harjani (IBM) <[email protected]>Reviewed-by Darrick J. Wong <[email protected]>Signed-off-by Ojaswin Mujoo <[email protected]>--- tests/ext4/062 | 203 +++++++++++++++++++++++++++++++++++++++++++++ tests/ext4/062.out | 2 + 2 files changed, 205 insertions(+)create mode 100755 tests/ext4/062
create mode 100644 tests/ext4/062.outtests/ext4/062 new file +82
-0@@ -0,0 +1,203 @@ +#! /bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2025 IBM Corporation. All Rights Reserved. +# +# FS QA Test 062 +# +# This test does a parallel RWF_ATOMIC IO on a multiple truncated files in a +# small FS. The idea is to stress ext4 allocator to ensure we are able to +# handle low space scenarios correctly with atomic writes.. We brute force this +# for all possible blocksize and clustersizes and after each iteration we +# ensure the data was not torn or corrupted using fio crc verification. +# +# Note that in this test we use overlapping atomic writes of same io size. +# Although serializing racing writes is not guaranteed for RWF_ATOMIC, NVMe and +# SCSI provide this guarantee as an inseparable feature to power-fail +# atomicity. Keeping the iosize as same also ensures that ext4 doesn't tear the +# write due to racing ioend unwritten conversion. +# +# The value of this test is that we make sure the RWF_ATOMIC is handled +# correctly by ext4 as well as test that the block layer doesn't split or only +# generate multiple bios for an atomic write. +# + +. ./common/preamble +. ./common/atomicwrites + +_begin_fstest auto rw stress atomicwrites + +_require_scratch_write_atomic +_require_aiodio +_require_fio_version "3.38+" + +FSSIZE=$((360*1024*1024)) +FIO_LOAD=$(($(nproc) * LOAD_FACTOR)) + +# Calculate bs as per bdev atomic write units. +bdev_awu_min=$(_get_atomic_write_unit_min $SCRATCH_DEV) +bdev_awu_max=$(_get_atomic_write_unit_max $SCRATCH_DEV) +bs=$(_max 4096 "$bdev_awu_min") + +function create_fio_configs() +{ + local bsize=$1 + create_fio_aw_config $bsize + create_fio_verify_config $bsize +} + +function create_fio_verify_config() +{ + local bsize=$1 +cat >$fio_verify_config <<EOF + [global] + direct=1 + ioengine=libaio + rw=read + bs=$bsize + fallocate=truncate + size=$((FSSIZE / 12)) + iodepth=$FIO_LOAD + numjobs=$FIO_LOAD + group_reporting=1 + atomic=1 + + verify_only=1 + verify_state_save=0 + verify=crc32c + verify_fatal=1 + verify_write_sequence=0 + + [verify-job1] + filename=$SCRATCH_MNT/testfile-job1 + + [verify-job2] + filename=$SCRATCH_MNT/testfile-job2 + + [verify-job3] + filename=$SCRATCH_MNT/testfile-job3 + + [verify-job4] + filename=$SCRATCH_MNT/testfile-job4 + + [verify-job5]
tests/ext4/062 line 92
Quoted 10 lines
tests/ext4/062 new file +10
-0+ filename=$SCRATCH_MNT/testfile-job5 + + [verify-job6] + filename=$SCRATCH_MNT/testfile-job6 + + [verify-job7] + filename=$SCRATCH_MNT/testfile-job7 + + [verify-job8] + filename=$SCRATCH_MNT/testfile-job8
do you really need multiple jobs for verify?
Re: [PATCH v5 02/12] common/rc: Add _require_fio_version helper
In reply to John GarryQuoted 17 lines · John Garry
Quoted 16 lines · Ojaswin Mujoo
The main motivation of adding this function on top of
_require_fiois that there has been a case in fio where atomic= option was added but later it was changed to noop since kernel didn't yet have support for atomic writes. It was then again utilized to do atomic writes in a later version, once kernel got the support. Due to this there is a point in fio where_require_fiow/ atomic=1 will succeed even though it would not be doing atomic writes.Hence, add an explicit helper to ensure tests to require specific versions of fio to work past such issues.
Signed-off-by Ojaswin Mujoo <[email protected]>--- common/rc | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+)
Quoted 15 lines
common/rc _max() { · line 6002
Quoted 11 lines
common/rc +4
-0@@ -5997,6 +5997,38 @@ _max() { echo $ret } +# Check the required fio version. Examples: +# _require_fio_version 3.38 (matches 3.38 only) +# _require_fio_version 3.38+ (matches 3.38 and above) +# _require_fio_version 3.38- (matches 3.38 and below)This requires the user to know the version which corresponds to the feature. Is that how things are done for other such utilities and their versions vs features?
Hi John,
So there are not many such helpers but the 2 I could see were used this way:
tests/btrfs/284:
_require_btrfs_send_version 2
tests/nfs/001:
_require_test_nfs_version 4
So I though of keeping it this way.
Regards,
ojaswin
Quoted 11 lines
I was going to suggest exporting something like
_require_fio_atomic_writes(), and_require_fio_atomic_writes()calls_require_fio_version()to check the version.Thanks,
John
Re: [PATCH v5 02/12] common/rc: Add _require_fio_version helper
Quoted 8 lines · Ojaswin Mujoo
Quoted 3 lines
This requires the user to know the version which corresponds to the feature. Is that how things are done for other such utilities and their versions vs features?
Hi John,
So there are not many such helpers but the 2 I could see were used this way:
Quoted 7 lines
tests/btrfs/284:
_require_btrfs_send_version2tests/nfs/001:
_require_test_nfs_version4So I though of keeping it this way.
What about the example of _require_xfs_io_command param, which checks if $param is supported?
We could have _require_fio_option atomics, which checks if a specific version is available which supports atomic? Or a more straightforward would be _require_fio_with_atomics.
Cheers
Re: [PATCH v5 04/12] ltp/fsx.c: Add atomic writes support to fsx
In reply to John GarryQuoted 85 lines · John Garry
Quoted 6 lines · Ojaswin Mujoo
Implement atomic write support to help fuzz atomic writes with fsx.
Suggested-by Ritesh Harjani (IBM) <[email protected]>Reviewed-by Darrick J. Wong <[email protected]>Signed-off-by Ojaswin Mujoo <[email protected]>Generally this looks ok, but I do have some comments, so please check them:
Reviewed-by John Garry <[email protected]>Quoted 73 lines
--- ltp/fsx.c | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 110 insertions(+), 5 deletions(-)ltp/fsx.c +24
-0@@ -40,6 +40,7 @@#include <liburing.h> #endif #include <sys/syscall.h> +#include "statx.h" #ifndef MAP_FILE # define MAP_FILE 0@@ -49,6 +50,10 @@#define RWF_DONTCACHE 0x80#endif +#ifndef RWF_ATOMIC +#define RWF_ATOMIC 0x40 +#endif +#define NUMPRINTCOLUMNS 32/* # columns of data to print on each line */ /* Operation flags (bitmask) */ @@ -110,6 +115,7 @@ enum {OP_READ_DONTCACHE ,OP_WRITE ,OP_WRITE_DONTCACHE , +OP_WRITE_ATOMIC ,OP_MAPREAD ,OP_MAPWRITE , OP_MAX_LITE,@@ -200,6 +206,11 @@ int uring = 0;int mark_nr =0 ;int dontcache_io =1 ;int hugepages =0 ;/* -h flag */ +int do_atomic_writes =1 ;/* -a flag disables */ + +/* User for atomic writes */ +int awu_min =0 ; +int awu_max =0 ;/* Stores info needed to periodically collapse hugepages */ struct hugepages_collapse_info {@@ -288,6 +299,7 @@ static const char *op_names[] = { [OP_READ_DONTCACHE] = "read_dontcache", [OP_WRITE] = "write", [OP_WRITE_DONTCACHE] = "write_dontcache", + [OP_WRITE_ATOMIC] = "write_atomic", [OP_MAPREAD] = "mapread", [OP_MAPWRITE] = "mapwrite", [OP_TRUNCATE] = "truncate",@@ -422,6 +434,7 @@ logdump(void)prt ("\t***RRRR***" );break ;case OP_WRITE_DONTCACHE : +case OP_WRITE_ATOMIC :case OP_WRITE : prt("WRITE 0x%x thru 0x%x\t(0x%x bytes)", lp->args[0], lp->args[0] + lp->args[1] - 1,@@ -1073,6 +1086,25 @@ update_file_size(unsigned offset, unsigned size) file_size = offset + size; } +static int is_power_of_2 (unsigned n) { +return ((n & (n -1 )) ==0 ); +} + +/* +* Round down n to nearest power of 2. +* If n is already a power of 2, return n; +*/ +static int rounddown_pow_of_2 (int n) { +int i =0 ; +
Quoted 8 lines
ltp/fsx.c update_file_size(unsigned offset, unsigned size) · line 1105
Quoted 6 lines
ltp/fsx.c +6
-0+if (is_power_of_2 (n)) +return n; + +for (; (1 << i) < n; i++); + +return 1 << (i -1 );Is this the neatest way to do this?
Well it is a straigforward o(logn) way. Do you have something else in mind?
Quoted 25 lines
ltp/fsx.c dowrite(unsigned offset, unsigned size, int flags) · line 1117
Quoted 12 lines
ltp/fsx.c +4
-0+} +void dowrite (unsigned offset,unsigned size,int flags) {@@ -1081,6 +1113,27 @@ dowrite(unsigned offset, unsigned size, int flags) offset -= offset % writebdy;if (o_direct) size -= size % writebdy; +if (flags &RWF_ATOMIC ) { +/* atomic write len must be inbetween awu_min and awu_max */ in between
ltp/fsx.c dowrite(unsigned offset, unsigned size, int flags) · line 1124
Quoted 7 lines
ltp/fsx.c +7
-0+if (size < awu_min) + size = awu_min; +if (size > awu_max) + size = awu_max; + +/* atomic writes need power-of-2 sizes */ + size =rounddown_pow_of_2 (size);you could have:
Quoted 6 lines
if (size <
awu_min)size = awu_min; else if (size > awu_max) size = awu_max; else size = rounddown_pow_of_2(size);
sure I can do that
Quoted 38 lines
Quoted 27 lines
ltp/fsx.c +16
-1+ +/* atomic writes need naturally aligned offsets */ + offset -= offset % size; + +/* Skip the write if we are crossing max filesize */ +if ((offset + size) > maxfilelen) { +if (!quiet && testcalls > simulatedopcount) +prt ("skipping atomic write past maxfilelen\n" ); +log4 (OP_WRITE_ATOMIC , offset, size,FL_SKIPPED ); +return ; + } + }if (size ==0 ) {if (!quiet && testcalls > simulatedopcount && !o_direct)prt ("skipping zero size write\n" );@@ -1088,7 +1141,10 @@ dowrite(unsigned offset, unsigned size, int flags)return ; } -log4 (OP_WRITE , offset, size,FL_NONE ); +if (flags &RWF_ATOMIC ) +log4 (OP_WRITE_ATOMIC , offset, size,FL_NONE ); +else +log4 (OP_WRITE , offset, size,FL_NONE );gendata (original_buf, good_buf, offset, size);if (offset + size > file_size) {ltp/fsx.c dowrite(unsigned offset, unsigned size, int flags) · line 1169
Quoted 8 lines
ltp/fsx.c +3
-2@@ -1108,8 +1164,9 @@ dowrite(unsigned offset, unsigned size, int flags) (monitorstart == -1 || (offset + size > monitorstart && (monitorend == -1 || offset <= monitorend)))))) -prt ("%lld write\t0x%x thru\t0x%x\t(0x%x bytes)\tdontcache=%d\n" , testcalls, - offset, offset + size -1 , size, (flags &RWF_DONTCACHE ) !=0 ); +prt ("%lld write\t0x%x thru\t0x%x\t(0x%x bytes)\tdontcache=%d atomic_wr=%d\n" , testcalls, + offset, offset + size -1 , size, (flags &RWF_DONTCACHE ) !=0 , + (flags &RWF_ATOMIC ) !=0 );nit:
Quoted 4 lines
!!(flags & RWF_ATOMIC)I find that a bit neater, but I suppose you are following the example for
RWF_DONTCACHE
Yep.
Quoted 37 lines
Quoted 24 lines
ltp/fsx.c +17
-0iret =fsxwrite (fd, good_buf + offset, size, offset, flags);if (iret != size) {if (iret ==-1 )@@ -1785,6 +1842,36 @@ do_dedupe_range(unsigned offset, unsigned length, unsigned dest) }#endif +int test_atomic_writes (void ) { +int ret; +struct statx stx; + +if (o_direct !=O_DIRECT ) { +fprintf (stderr,"main: atomic writes need O_DIRECT (-Z), " +"disabling!\n" ); +return 0 ; + } + + ret =xfstests_statx (AT_FDCWD , fname,0 ,STATX_WRITE_ATOMIC , &stx); +if (ret <0 ) { +fprintf (stderr,"main: Statx failed with %d." +" Failed to determine atomic write limits, " +" disabling!\n" , ret); +return 0 ; + }ltp/fsx.c do_dedupe_range(unsigned offset, unsigned length, unsigned dest) · line 1870
Quoted 10 lines
ltp/fsx.c +10
-0+ +if (stx.stx_attributes &STATX_ATTR_WRITE_ATOMIC && + stx.stx_atomic_write_unit_min >0 ) { + awu_min = stx.stx_atomic_write_unit_min; + awu_max = stx.stx_atomic_write_unit_max; +return 1 ; + } + +fprintf (stderr,"main: IO Stack does not support " +"atomic writes, disabling!\n" );Do we really need to spread this over multiple lines?
Quoted 1 line
Maybe that is the coding standard - I don't know.
Yea, I wouldve liked to keep it on the same but looking at above fprintf()s seems like we break the strings.
Quoted 50 lines
Quoted 50 lines
ltp/fsx.c +16
-2+return 0 ; +} +#ifdef HAVE_COPY_FILE_RANGE int test_copy_range (void )@@ -2356,6 +2443,12 @@ have_op: goto out; }break ; +case OP_WRITE_ATOMIC : +if (!do_atomic_writes) { +log4 (OP_WRITE_ATOMIC , offset, size,FL_SKIPPED ); + goto out; + } +break ; }switch (op) {@@ -2385,6 +2478,11 @@ have_op:dowrite (offset, size,0 );break ; +case OP_WRITE_ATOMIC : +TRIM_OFF_LEN (offset, size, maxfilelen); +dowrite (offset, size,RWF_ATOMIC ); +break ; +case OP_MAPREAD :TRIM_OFF_LEN (offset, size, file_size);domapread (offset, size);@@ -2511,13 +2609,14 @@ voidusage (void) { fprintf(stdout, "usage: %s", - "fsx [-dfhknqxyzBEFHIJKLORWXZ0]\n\ + "fsx [-adfhknqxyzBEFHIJKLORWXZ0]\n\ [-b opnum] [-c Prob] [-g filldata] [-i logdev] [-j logid]\n\ [-l flen] [-m start:end] [-o oplen] [-p progressinterval]\n\ [-r readbdy] [-s style] [-t truncbdy] [-w writebdy]\n\ [-A|-U] [-D startingop] [-N numops] [-P dirpath] [-S seed]\n\ [--replay-ops=opsfile] [--record-ops[=opsfile]] [--duration=seconds]\n\ ... fname\n\ + -a: disable atomic writes\n\ -b opnum: beginning operation number (default 1)\n\ -c P: 1 in P chance of file close+open at each op (default infinity)\n\ -d: debug output for all operations\n\@@ -3059,9 +3158,13 @@ main(int argc, char **argv)setvbuf (stdout, (char *)0 , _IOLBF,0 );/* line buffered stdout */ while ((ch = getopt_long(argc, argv, - "0b:c:de:fg:hi:j:kl:m:no:p:qr:s:t:uw:xyABD:EFJKHzCILN:OP:RS:UWXZ",
Quoted 8 lines
ltp/fsx.c main(int argc, char **argv) · line 3166
Quoted 6 lines
ltp/fsx.c +4
-0+ "0ab:c:de:fg:hi:j:kl:m:no:p:qr:s:t:uw:xyABD:EFJKHzCILN:OP:RS:UWXZ", longopts, NULL)) != EOF)switch (ch) { +case 'a' : +prt ("main(): Atomic writes disabled\n" ); + do_atomic_writes =0 ;why an opt-out (and not opt-in)?
That's something Darrick suggested a few iterations back so we automatically get the testing if the stack supports it.
Thanks for the review. I'll incorporate the minor changes in next version.
Regards,
ojaswin
Quoted 14 lines
Quoted 13 lines
ltp/fsx.c +3
-0+break ;case 'b' : simulatedopcount =getnum (optarg, &endp);if (!quiet)@@ -3475,6 +3578,8 @@ main(int argc, char **argv) exchange_range_calls =test_exchange_range ();if (dontcache_io) dontcache_io =test_dontcache_io (); +if (do_atomic_writes) + do_atomic_writes =test_atomic_writes ();while (keep_running ())if (!test ())
Re: [PATCH v5 07/12] generic: Add atomic write multi-fsblock O_[D]SYNC tests
In reply to John GarryQuoted 145 lines · John Garry
Quoted 1 line · Ojaswin Mujoo
This adds various atomic write multi-fsblock stresst tests
stress
Quoted 6 lines
with mixed mappings and
O_SYNC, to ensure the data and metadata is atomically persisted even if there is a shutdown.Suggested-by Ritesh Harjani (IBM) <[email protected]>Reviewed-by Darrick J. Wong <[email protected]>Signed-off-by Ojaswin Mujoo <[email protected]>Regardless of any comments made (which are minor):
Reviewed-by John Garry <[email protected]>Quoted 129 lines
--- tests/generic/1228 | 137 +++++++++++++++++++++++++++++++++++++++++ tests/generic/1228.out | 2 + 2 files changed, 139 insertions(+)create mode 100755 tests/generic/1228
create mode 100644 tests/generic/1228.outtests/generic/1228 new file +116
-0@@ -0,0 +1,137 @@ +#! /bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2025 IBM Corporation. All Rights Reserved. +# +# FS QA Test 1228 +# +# Atomic write multi-fsblock data integrity tests with mixed mappings +# and O_SYNC +# +. ./common/preamble +. ./common/atomicwrites +_begin_fstest auto quick rw atomicwrites + +_require_scratch_write_atomic_multi_fsblock +_require_atomic_write_test_commands +_require_scratch_shutdown +_require_xfs_io_command "truncate" + +_scratch_mkfs >> $seqres.full +_scratch_mount >> $seqres.full + +check_data_integrity() { + actual=$(_hexdump $testfile) + if [[ "$expected" != "$actual" ]] + then + echo "Integrity check failed" + echo "Integrity check failed" >> $seqres.full + echo "# Expected file contents:" >> $seqres.full + echo "$expected" >> $seqres.full + echo "# Actual file contents:" >> $seqres.full + echo "$actual" >> $seqres.full + + _fail "Data integrity check failed. The atomic write was torn." + fi +} + +prep_mixed_mapping() { + $XFS_IO_PROG -c "truncate 0" $testfile >> $seqres.full + local off=0 + local mapping="" + + local operations=("W" "H" "U") + local num_blocks=$((awu_max / blksz)) + for ((i=0; i<num_blocks; i++)); do + local index=$((RANDOM % ${#operations[@]})) + local map="${operations[$index]}" + local mapping="${mapping}${map}" + + case "$map" in + "W") + $XFS_IO_PROG -dc "pwrite -S 0x61 -b $blksz $off $blksz" $testfile > /dev/null + ;; + "H") + # No operation needed for hole + ;; + "U") + $XFS_IO_PROG -c "falloc $off $blksz" $testfile >> /dev/null + ;; + esac + off=$((off + blksz)) + done + + echo "+ + Mixed mapping prep done. Full mapping pattern: $mapping" >> $seqres.full + + sync $testfile +} + +verify_atomic_write() { + test $bytes_written -eq $awu_max || _fail "atomic write len=$awu_max assertion failed" + check_data_integrity +} + +mixed_mapping_test() { + prep_mixed_mapping + + echo -"+ + Performing O_DSYNC atomic write from 0 to $awu_max" >> $seqres.full + if [[ "$1" == "shutdown" ]] + then + bytes_written=$($XFS_IO_PROG -x -dc \ + "pwrite -DA -V1 -b $awu_max 0 $awu_max" \ + -c "shutdown" $testfile | grep wrote | \ + awk -F'[/ ]' '{print $2}') + _scratch_cycle_mount >>$seqres.full 2>&1 || _fail "remount failed" + else + bytes_written=$($XFS_IO_PROG -dc \ + "pwrite -DA -V1 -b $awu_max 0 $awu_max" $testfile | \ + grep wrote | awk -F'[/ ]' '{print $2}') + fi + + verify_atomic_write +} + +testfile=$SCRATCH_MNT/testfile +touch $testfile + +awu_max=$(_get_atomic_write_unit_max $testfile) +blksz=$(_get_block_size $SCRATCH_MNT) + +# Create an expected pattern to compare with +$XFS_IO_PROG -tc "pwrite -b $awu_max 0 $awu_max" $testfile >> $seqres.full +expected=$(_hexdump $testfile) +echo "# Expected file contents:" >> $seqres.full +echo "$expected" >> $seqres.full +echo >> $seqres.full + +echo "# Test 1: Do O_DSYNC atomic write on random mixed mapping:" >> $seqres.full +echo >> $seqres.full +for ((iteration=1; iteration<=10; iteration++)); do + echo "=== Mixed Mapping Test Iteration $iteration ===" >> $seqres.full + + echo "+ Testing without shutdown..." >> $seqres.full + mixed_mapping_test + echo "Passed!" >> $seqres.full + + echo "+ Testing with sudden shutdown..." >> $seqres.full + mixed_mapping_test "shutdown"
Quoted 8 lines
tests/generic/1228 line 122
Quoted 6 lines
tests/generic/1228 new file +6
-0+ echo "Passed!" >> $seqres.full + + echo "Iteration $iteration completed: OK" >> $seqres.full + echo >> $seqres.full +done +echo "# Test 1: Do O_SYNC atomic write on random mixed mapping (10 iterations): OK" >> $seqres.fullbig nit: you should keep the loop count in a global variable
I believe you mean something like:
echo " ... ($iters iteration): OK"
If so, sure I agree. I'll make the changes.
Thanks for the review!
Regards,
ojaswin
Quoted 26 lines
Quoted 25 lines
tests/generic/1228 new file +15
-0+ + +echo >> $seqres.full +echo "# Test 2: Do extending O_SYNC atomic writes: " >> $seqres.full +bytes_written=$($XFS_IO_PROG -x -dstc "pwrite -A -V1 -b $awu_max 0 $awu_max" \ + -c "shutdown" $testfile | grep wrote | awk -F'[/ ]' '{print $2}') +_scratch_cycle_mount >>$seqres.full 2>&1 || _fail "remount failed" +verify_atomic_write +echo "# Test 2: Do extending O_SYNC atomic writes: OK" >> $seqres.full + +# success, all done +echo "Silence is golden" +status=0 +exit +tests/generic/1228.out new file +2
-0@@ -0,0 +1,2 @@ +QA output created by 1228 +Silence is golden
Re: [PATCH v5 02/12] common/rc: Add _require_fio_version helper
In reply to John GarryQuoted 15 lines · John Garry
Quoted 14 lines · Ojaswin Mujoo
Quoted 3 lines
This requires the user to know the version which corresponds to the feature. Is that how things are done for other such utilities and their versions vs features?
Hi John,
So there are not many such helpers but the 2 I could see were used this way:
tests/btrfs/284:
_require_btrfs_send_version2tests/nfs/001:
_require_test_nfs_version4
Quoted 8 lines
Quoted 1 line
So I though of keeping it this way.
What about the example of
_require_xfs_io_commandparam, which checks if $param is supported?We could have
_require_fio_optionatomics, which checks if a specific version is available which supports atomic? Or a more straightforward would be_require_fio_with_atomics.
Hey John,
Sure Im okay with having a high level helper. I liked the name you previously suggested:
_require_fio_atomic_writes() {
_require_fio_version 3.38+
}And the tests could use it as:
_require_fio_atomic_writes()
fio_config="abc.fio"
_require_fio $fio_config
------------------------
OR would you prefer:
_require_fio_atomic_writes() {
_require_fio_version 3.38+
_require_fio $fio_config
}And the tests could use it as:
fio_config="abc.fio"
_require_fio_atomic_writes $fio_config
------------------------
Let me know which one would you prefer.
Regards,
ojaswin
Quoted 3 lines
Cheers
Re: [PATCH v5 08/12] generic: Stress fsx with atomic writes enabled
In reply to John GarryQuoted 5 lines · John Garry
Quoted 1 line · Ojaswin Mujoo
Stress file with atomic writes to ensure we excercise codepaths
exercise
Quoted 7 lines
Quoted 5 lines
where we are mixing different FS operations with atomic writes
Suggested-by Ritesh Harjani (IBM) <[email protected]>Reviewed-by Darrick J. Wong <[email protected]>Signed-off-by Ojaswin Mujoo <[email protected]>Reviewed-by John Garry <[email protected]>
Thanks John, I'll add the spelling changes.
Thanks,
ojaswin
Re: [PATCH v5 09/12] generic: Add sudden shutdown tests for multi block atomic writes
In reply to John GarryQuoted 44 lines · John Garry
Quoted 10 lines · Ojaswin Mujoo
This test is intended to ensure that multi blocks atomic writes maintain atomic guarantees across sudden FS shutdowns.
The way we work is that we lay out a file with random mix of written, unwritten and hole extents. Then we start performing atomic writes sequentially on the file while we parallely shutdown the FS. Then we note the last offset where the atomic write happened just before shut down and then make sure blocks around it either have completely old data or completely new data, ie the write was not torn during shutdown.
Quoted 7 lines
We repeat the same with completely written, completely unwritten and completely empty file to ensure these cases are not torn either. Finally, we have a similar test for append atomic writes
Suggested-by Ritesh Harjani (IBM) <[email protected]>Reviewed-by Darrick J. Wong <[email protected]>Signed-off-by Ojaswin Mujoo <[email protected]>Please check comments, below, thanks!
Quoted 23 lines
--- tests/generic/1230 | 397 +++++++++++++++++++++++++++++++++++++++++ tests/generic/1230.out | 2 + 2 files changed, 399 insertions(+)create mode 100755 tests/generic/1230
create mode 100644 tests/generic/1230.outtests/generic/1230 new file +10
-0@@ -0,0 +1,397 @@ +#! /bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2025 IBM Corporation. All Rights Reserved. +# +# FS QA Test No. 1230 +# +# Test multi block atomic writes with sudden FS shutdowns to ensure +# the FS is not tearing the write operation +. ./common/preamble +. ./common/atomicwrites
Quoted 8 lines
tests/generic/1230 line 16
Quoted 6 lines
tests/generic/1230 new file +6
-0+_begin_fstest auto atomicwrites + +_require_scratch_write_atomic_multi_fsblock +_require_atomic_write_test_commands +_require_scratch_shutdown +_require_xfs_io_command "truncate"is a similar fallocate test needed?
Hey John, we run the test for the following cases:
- file has mixed mapping
- file has only hole (trucate case)
- file has only unwritten blocks (falloc case)
- file has only written blocks
So we already do that. It's just that we don't need the _require_xfs_io_command "falloc" since it is already included in _require_atomic_write_test_commnads.
Quoted 10 lines
Quoted 10 lines
tests/generic/1230 new file +9
-0+ +_scratch_mkfs >> $seqres.full 2>&1 +_scratch_mount >> $seqres.full + +testfile=$SCRATCH_MNT/testfile +touch $testfile + +awu_max=$(_get_atomic_write_unit_max $testfile) +blksz=$(_get_block_size $SCRATCH_MNT)
Quoted 8 lines
tests/generic/1230 line 31
Quoted 6 lines
tests/generic/1230 new file +6
-0+echo "Awu max: $awu_max" >> $seqres.full + +num_blocks=$((awu_max / blksz)) +# keep initial value high for dry run. This will be +# tweaked in dry_run() based on device write speed. +filesize=$(( 10 * 1024 * 1024 * 1024 ))could this cause some out-of-space issue? That's 10GB, right?
Hey John, yes this is just a dummy value. We tune the filesize later based on how fast the device is. That will usually be around 3 * (bytes written in 0.2s) (check dry_run function).
Generally this will be a smaller size. ( 3GB on a 5GB/s SSD.) But yes I should probably add a _notrun if our ssd fast enough to fill up the full FS in 0.2s.
Quoted 15 lines
Quoted 15 lines
tests/generic/1230 new file +14
-0+ +_cleanup() { + [ -n "$awloop_pid" ] && kill $awloop_pid &> /dev/null + wait +} + +atomic_write_loop() { + local off=0 + local size=$awu_max + for ((i=0; i<$((filesize / $size )); i++)); do + # Due to sudden shutdown this can produce errors so just + # redirect them to seqres.full + $XFS_IO_PROG -c "open -fsd $testfile" -c "pwrite -S 0x61 -DA -V1 -b $size $off $size" >> /dev/null 2>>$seqres.full + echo "Written to offset: $off" >> $tmp.aw
Quoted 8 lines
tests/generic/1230 line 51
Quoted 6 lines
tests/generic/1230 new file +6
-0+ off=$((off + $size)) + done +} + +# This test has the following flow: +# 1. Start doing sequential atomic writes in bg, upto $filesizebg?
background*. I'll change it.
Quoted 46 lines
Quoted 46 lines
tests/generic/1230 new file +45
-0+# 2. Sleep for 0.2s and shutdown the FS +# 3. kill the atomic write process +# 4. verify the writes were not torn +# +# We ideally want the shutdown to happen while an atomic write is ongoing +# but this gets tricky since faster devices can actually finish the whole +# atomic write loop before sleep 0.2s completes, resulting in the shutdown +# happening after the write loop which is not what we want. A simple solution +# to this is to increase $filesize so step 1 takes long enough but a big +# $filesize leads to create_mixed_mappings() taking very long, which is not +# ideal. +# +# Hence, use the dry_run function to figure out the rough device speed and set +# $filesize accordingly. +dry_run() { + echo >> $seqres.full + echo "# Estimating ideal filesize..." >> $seqres.full + atomic_write_loop & + awloop_pid=$! + + local i=0 + # Wait for atleast first write to be recorded or 10s + while [ ! -f "$tmp.aw" -a $i -le 50 ]; do i=$((i + 1)); sleep 0.2; done + + if [[ $i -gt 50 ]] + then + _fail "atomic write process took too long to start" + fi + + echo >> $seqres.full + echo "# Shutting down filesystem while write is running" >> $seqres.full + _scratch_shutdown + + kill $awloop_pid 2>/dev/null # the process might have finished already + wait $awloop_pid + unset $awloop_pid + + bytes_written=$(tail -n 1 $tmp.aw | cut -d" " -f4) + echo "# Bytes written in 0.2s: $bytes_written" >> $seqres.full + + filesize=$((bytes_written * 3)) + echo "# Setting \$filesize=$filesize" >> $seqres.full + + rm $tmp.aw + sleep 0.5
Quoted 8 lines
tests/generic/1230 line 102
Quoted 6 lines
tests/generic/1230 new file +6
-0+ + _scratch_cycle_mount + +} + +create_mixed_mappings() {Is this same as patch 08/12?
I believe you mean the [D]SYNC tests, yes it is the same.
Quoted 10 lines
Quoted 10 lines
tests/generic/1230 new file +9
-0+ local file=$1 + local size_bytes=$2 + + echo "# Filling file $file with alternate mappings till size $size_bytes" >> $seqres.full + #Fill the file with alternate written and unwritten blocks + local off=0 + local operations=("W" "U") + + for ((i=0; i<$((size_bytes / blksz )); i++)); do
Quoted 8 lines
tests/generic/1230 line 117
Quoted 6 lines
tests/generic/1230 new file +6
-0+ index=$(($i % ${#operations[@]})) + map="${operations[$index]}" + + case "$map" in + "W") + $XFS_IO_PROG -fc "pwrite -b $blksz $off $blksz" $file >> /dev/nulldoes this just write random data? I don't see any pattern being set.
By default pwrite writes 0xcd if no patterns is specified. This helps us reliably check the data back.
Quoted 47 lines
Quoted 47 lines
tests/generic/1230 new file +46
-0+ ;; + "U") + $XFS_IO_PROG -fc "falloc $off $blksz" $file >> /dev/null + ;; + esac + off=$((off + blksz)) + done + + sync $file +} + +populate_expected_data() { + # create a dummy file with expected old data for different cases + create_mixed_mappings $testfile.exp_old_mixed $awu_max + expected_data_old_mixed=$(od -An -t x1 -j 0 -N $awu_max $testfile.exp_old_mixed) + + $XFS_IO_PROG -fc "falloc 0 $awu_max" $testfile.exp_old_zeroes >> $seqres.full + expected_data_old_zeroes=$(od -An -t x1 -j 0 -N $awu_max $testfile.exp_old_zeroes) + + $XFS_IO_PROG -fc "pwrite -b $awu_max 0 $awu_max" $testfile.exp_old_mapped >> $seqres.full + expected_data_old_mapped=$(od -An -t x1 -j 0 -N $awu_max $testfile.exp_old_mapped) + + # create a dummy file with expected new data + $XFS_IO_PROG -fc "pwrite -S 0x61 -b $awu_max 0 $awu_max" $testfile.exp_new >> $seqres.full + expected_data_new=$(od -An -t x1 -j 0 -N $awu_max $testfile.exp_new) +} + +verify_data_blocks() { + local verify_start=$1 + local verify_end=$2 + local expected_data_old="$3" + local expected_data_new="$4" + + echo >> $seqres.full + echo "# Checking data integrity from $verify_start to $verify_end" >> $seqres.full + + # After an atomic write, for every chunk we ensure that the underlying + # data is either the old data or new data as writes shouldn't get torn. + local off=$verify_start + while [[ "$off" -lt "$verify_end" ]] + do + #actual_data=$(xxd -s $off -l $awu_max -p $testfile) + actual_data=$(od -An -t x1 -j $off -N $awu_max $testfile) + if [[ "$actual_data" != "$expected_data_new" ]] && [[ "$actual_data" != "$expected_data_old" ]] + then + echo "Checksum match failed at off: $off size: $awu_max"
Quoted 8 lines
tests/generic/1230 line 167
Quoted 4 lines
tests/generic/1230 new file +4
-0+ echo "Expected contents: (Either of the 2 below):" + echo + echo "Expected old: " + echo "$expected_data_old"it would be nice if this was deterministic - see comment in
create_mixed_mappings
Yes, it is. It will be 0xcdcdcdcd
Quoted 7 lines
tests/generic/1230 line 170
Quoted 4 lines
tests/generic/1230 new file +3
-0+ echo + echo "Expected new: " + echo "$expected_data_new"nit: I am not sure what is meant by "expected". I would just have "new data". We don't know what to expect, as it could be old or new, right?
Yes, so the I was thinking of it this way:
We either expect the data to be the full new (named expected_new) or fully old (named expected_old). Else renaming it to new vs old vs actual makese it a bit more confusing imo
Quoted 39 lines
Quoted 16 lines
tests/generic/1230 new file +15
-0+ echo + echo "Actual contents: " + echo "$actual_data" + + _fail + fi + echo -n "Check at offset $off suceeded! " >> $seqres.full + if [[ "$actual_data" == "$expected_data_new" ]] + then + echo "matched new" >> $seqres.full + elif [[ "$actual_data" == "$expected_data_old" ]] + then + echo "matched old" >> $seqres.full + fi + off=$(( off + awu_max ))tests/generic/1230 line 195
Quoted 10 lines
tests/generic/1230 new file +10
-0+ done +} + +# test data integrity for file by shutting down in between atomic writes +test_data_integrity() { + echo >> $seqres.full + echo "# Writing atomically to file in background" >> $seqres.full + atomic_write_loop & + awloop_pid=$! +from here ...
Quoted 10 lines
tests/generic/1230 new file +10
-0+ local i=0 + # Wait for atleast first write to be recorded or 10s + while [ ! -f "$tmp.aw" -a $i -le 50 ]; do i=$((i + 1)); sleep 0.2; done + + if [[ $i -gt 50 ]] + then + _fail "atomic write process took too long to start" + fi + + echo >> $seqres.full
Quoted 8 lines
tests/generic/1230 line 87
Quoted 6 lines
tests/generic/1230 new file +6
-0+ echo "# Shutting down filesystem while write is running" >> $seqres.full + _scratch_shutdown + + kill $awloop_pid 2>/dev/null # the process might have finished already + wait $awloop_pid + unset $awloop_pid... to here looks similar in many functions. Can we factor it out?
Right thats true, I'll factor this out. Thanks for pointing it out.
Quoted 34 lines
Quoted 21 lines
tests/generic/1230 new file +20
-0+ + last_offset=$(tail -n 1 $tmp.aw | cut -d" " -f4) + if [[ -z $last_offset ]] + then + last_offset=0 + fi + + echo >> $seqres.full + echo "# Last offset of atomic write: $last_offset" >> $seqres.full + + rm $tmp.aw + sleep 0.5 + + _scratch_cycle_mount + + # we want to verify all blocks around which the shutdown happended + verify_start=$(( last_offset - (awu_max * 5))) + if [[ $verify_start < 0 ]] + then + verify_start=0tests/generic/1230 line 241
Quoted 10 lines
tests/generic/1230 new file +10
-0+ fi + + verify_end=$(( last_offset + (awu_max * 5))) + if [[ "$verify_end" -gt "$filesize" ]] + then + verify_end=$filesize + fi +} + +# test data integrity for file wiht written and unwritten mappingswith
Quoted 16 lines
Quoted 4 lines
tests/generic/1230 new file +4
-0+test_data_integrity_mixed() { + $XFS_IO_PROG -fc "truncate 0" $testfile >> $seqres.full + + echo >> $seqres.fulltests/generic/1230 line 255
Quoted 10 lines
tests/generic/1230 new file +10
-0+ echo "# Creating testfile with mixed mappings" >> $seqres.full + create_mixed_mappings $testfile $filesize + + test_data_integrity + + verify_data_blocks $verify_start $verify_end "$expected_data_old_mixed" "$expected_data_new" +} + +# test data integrity for file with completely written mappings +test_data_integrity_writ() {please spell "writ" out fully, which I think should be "written"
Yes, will do.
Quoted 134 lines
Quoted 5 lines
tests/generic/1230 new file +4
-0+ $XFS_IO_PROG -fc "truncate 0" $testfile >> $seqres.full + + echo >> $seqres.full + echo "# Creating testfile with fully written mapping" >> $seqres.fulltests/generic/1230 line 269
Quoted 10 lines
tests/generic/1230 new file +10
-0+ $XFS_IO_PROG -c "pwrite -b $filesize 0 $filesize" $testfile >> $seqres.full + sync $testfile + + test_data_integrity + + verify_data_blocks $verify_start $verify_end "$expected_data_old_mapped" "$expected_data_new" +} + +# test data integrity for file with completely unwritten mappings +test_data_integrity_unwrit() {same as above
Quoted 116 lines
tests/generic/1230 new file +116
-0+ $XFS_IO_PROG -fc "truncate 0" $testfile >> $seqres.full + + echo >> $seqres.full + echo "# Creating testfile with fully unwritten mappings" >> $seqres.full + $XFS_IO_PROG -c "falloc 0 $filesize" $testfile >> $seqres.full + sync $testfile + + test_data_integrity + + verify_data_blocks $verify_start $verify_end "$expected_data_old_zeroes" "$expected_data_new" +} + +# test data integrity for file with no mappings +test_data_integrity_hole() { + $XFS_IO_PROG -fc "truncate 0" $testfile >> $seqres.full + + echo >> $seqres.full + echo "# Creating testfile with no mappings" >> $seqres.full + $XFS_IO_PROG -c "truncate $filesize" $testfile >> $seqres.full + sync $testfile + + test_data_integrity + + verify_data_blocks $verify_start $verify_end "$expected_data_old_zeroes" "$expected_data_new" +} + +test_filesize_integrity() { + $XFS_IO_PROG -c "truncate 0" $testfile >> $seqres.full + + echo >> $seqres.full + echo "# Performing extending atomic writes over file in background" >> $seqres.full + atomic_write_loop & + awloop_pid=$! + + local i=0 + # Wait for atleast first write to be recorded or 10s + while [ ! -f "$tmp.aw" -a $i -le 50 ]; do i=$((i + 1)); sleep 0.2; done + + if [[ $i -gt 50 ]] + then + _fail "atomic write process took too long to start" + fi + + echo >> $seqres.full + echo "# Shutting down filesystem while write is running" >> $seqres.full + _scratch_shutdown + + kill $awloop_pid 2>/dev/null # the process might have finished already + wait $awloop_pid + unset $awloop_pid + + local last_offset=$(tail -n 1 $tmp.aw | cut -d" " -f4) + if [[ -z $last_offset ]] + then + last_offset=0 + fi + + echo >> $seqres.full + echo "# Last offset of atomic write: $last_offset" >> $seqres.full + rm $tmp.aw + sleep 0.5 + + _scratch_cycle_mount + local filesize=$(_get_filesize $testfile) + echo >> $seqres.full + echo "# Filesize after shutdown: $filesize" >> $seqres.full + + # To confirm that the write went atomically, we check: + # 1. The last block should be a multiple of awu_max + # 2. The last block should be the completely new data + + if (( $filesize % $awu_max )) + then + echo "Filesize after shutdown ($filesize) not a multiple of atomic write unit ($awu_max)" + fi + + verify_start=$(( filesize - (awu_max * 5))) + if [[ $verify_start < 0 ]] + then + verify_start=0 + fi + + local verify_end=$filesize + + # Here the blocks should always match new data hence, for simplicity of + # code, just corrupt the $expected_data_old buffer so it never matches + local expected_data_old="POISON" + verify_data_blocks $verify_start $verify_end "$expected_data_old" "$expected_data_new" +} + +$XFS_IO_PROG -fc "truncate 0" $testfile >> $seqres.full + +dry_run + +echo >> $seqres.full +echo "# Populating expected data buffers" >> $seqres.full +populate_expected_data + +# Loop 20 times to shake out any races due to shutdown +for ((iter=0; iter<20; iter++)) +do + echo >> $seqres.full + echo "------ Iteration $iter ------" >> $seqres.full + + echo >> $seqres.full + echo "# Starting data integrity test for atomic writes over mixed mapping" >> $seqres.full + test_data_integrity_mixed + + echo >> $seqres.full + echo "# Starting data integrity test for atomic writes over fully written mapping" >> $seqres.full + test_data_integrity_writ + + echo >> $seqres.full + echo "# Starting data integrity test for atomic writes over fully unwritten mapping" >> $seqres.full + test_data_integrity_unwrit +
Quoted 8 lines
tests/generic/1230 line 391
Quoted 6 lines
tests/generic/1230 new file +6
-0+ echo >> $seqres.full + echo "# Starting data integrity test for atomic writes over holes" >> $seqres.full + test_data_integrity_hole + + echo >> $seqres.full + echo "# Starting filesize integrity test for atomic writes" >> $seqres.fullwhat does "Starting filesize integrity test" mean?
Basically other tests already truncate the file to a higher value and then perform the shut down test. Here we actually do append atomic writes since we want to also stress the i_size update paths during shutdown to ensure that doesn't cause any tearing with atomic writes.
I can maybe rename it to:
echo "# Starting data integrity test for atomic append writes" >> $seqres.full
Thanks for the review!
Regards,
ojaswin
Quoted 16 lines
Quoted 15 lines
tests/generic/1230 new file +6
-0+ test_filesize_integrity +done + +echo "Silence is golden" +status=0 +exittests/generic/1230.out new file +2
-0@@ -0,0 +1,2 @@ +QA output created by 1230 +Silence is golden
Re: [PATCH v5 11/12] ext4: Test atomic writes allocation and write codepaths with bigalloc
In reply to John GarryQuoted 122 lines · John Garry
Quoted 121 lines · Ojaswin Mujoo
From: "Ritesh Harjani (IBM)" <[email protected]>
This test does a parallel
RWF_ATOMICIO on a multiple truncated files in a small FS. The idea is to stress ext4 allocator to ensure we are able to handle low space scenarios correctly with atomic writes. We brute force this for different blocksize and clustersizes and after each iteration we ensure the data was not torn or corrupted using fio crc verification.Note that in this test we use overlapping atomic writes of same io size. Although serializing racing writes is not guaranteed for
RWF_ATOMIC, NVMe and SCSI provide this guarantee as an inseparable feature to power-fail atomicity. Keeping the iosize as same also ensures that ext4 doesn't tear the write due to racing ioend unwritten conversion.The value of this test is that we make sure the
RWF_ATOMICis handled correctly by ext4 as well as test that the block layer doesn't split or only generate multiple bios for an atomic write.Signed-off-by Ritesh Harjani (IBM) <[email protected]>Reviewed-by Darrick J. Wong <[email protected]>Signed-off-by Ojaswin Mujoo <[email protected]>--- tests/ext4/062 | 203 +++++++++++++++++++++++++++++++++++++++++++++ tests/ext4/062.out | 2 + 2 files changed, 205 insertions(+)create mode 100755 tests/ext4/062
create mode 100644 tests/ext4/062.outtests/ext4/062 new file +86
-0@@ -0,0 +1,203 @@ +#! /bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2025 IBM Corporation. All Rights Reserved. +# +# FS QA Test 062 +# +# This test does a parallel RWF_ATOMIC IO on a multiple truncated files in a +# small FS. The idea is to stress ext4 allocator to ensure we are able to +# handle low space scenarios correctly with atomic writes.. We brute force this +# for all possible blocksize and clustersizes and after each iteration we +# ensure the data was not torn or corrupted using fio crc verification. +# +# Note that in this test we use overlapping atomic writes of same io size. +# Although serializing racing writes is not guaranteed for RWF_ATOMIC, NVMe and +# SCSI provide this guarantee as an inseparable feature to power-fail +# atomicity. Keeping the iosize as same also ensures that ext4 doesn't tear the +# write due to racing ioend unwritten conversion. +# +# The value of this test is that we make sure the RWF_ATOMIC is handled +# correctly by ext4 as well as test that the block layer doesn't split or only +# generate multiple bios for an atomic write. +# + +. ./common/preamble +. ./common/atomicwrites + +_begin_fstest auto rw stress atomicwrites + +_require_scratch_write_atomic +_require_aiodio +_require_fio_version "3.38+" + +FSSIZE=$((360*1024*1024)) +FIO_LOAD=$(($(nproc) * LOAD_FACTOR)) + +# Calculate bs as per bdev atomic write units. +bdev_awu_min=$(_get_atomic_write_unit_min $SCRATCH_DEV) +bdev_awu_max=$(_get_atomic_write_unit_max $SCRATCH_DEV) +bs=$(_max 4096 "$bdev_awu_min") + +function create_fio_configs() +{ + local bsize=$1 + create_fio_aw_config $bsize + create_fio_verify_config $bsize +} + +function create_fio_verify_config() +{ + local bsize=$1 +cat >$fio_verify_config <<EOF + [global] + direct=1 + ioengine=libaio + rw=read + bs=$bsize + fallocate=truncate + size=$((FSSIZE / 12)) + iodepth=$FIO_LOAD + numjobs=$FIO_LOAD + group_reporting=1 + atomic=1 + + verify_only=1 + verify_state_save=0 + verify=crc32c + verify_fatal=1 + verify_write_sequence=0 + + [verify-job1] + filename=$SCRATCH_MNT/testfile-job1 + + [verify-job2] + filename=$SCRATCH_MNT/testfile-job2 + + [verify-job3] + filename=$SCRATCH_MNT/testfile-job3 + + [verify-job4] + filename=$SCRATCH_MNT/testfile-job4 + + [verify-job5] + filename=$SCRATCH_MNT/testfile-job5 + + [verify-job6] + filename=$SCRATCH_MNT/testfile-job6
Quoted 8 lines
tests/ext4/062 line 92
Quoted 6 lines
tests/ext4/062 new file +6
-0+ + [verify-job7] + filename=$SCRATCH_MNT/testfile-job7 + + [verify-job8] + filename=$SCRATCH_MNT/testfile-job8do you really need multiple jobs for verify?
Yes since we want each job to verify it's own file.
Quoted 2 lines
Re: [PATCH v5 02/12] common/rc: Add _require_fio_version helper
In reply to John GarryQuoted 17 lines · John Garry
Quoted 16 lines · Ojaswin Mujoo
The main motivation of adding this function on top of
_require_fiois that there has been a case in fio where atomic= option was added but later it was changed to noop since kernel didn't yet have support for atomic writes. It was then again utilized to do atomic writes in a later version, once kernel got the support. Due to this there is a point in fio where_require_fiow/ atomic=1 will succeed even though it would not be doing atomic writes.Hence, add an explicit helper to ensure tests to require specific versions of fio to work past such issues.
Signed-off-by Ojaswin Mujoo <[email protected]>--- common/rc | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+)
Quoted 15 lines
common/rc _max() { · line 6002
Quoted 11 lines
common/rc +4
-0@@ -5997,6 +5997,38 @@ _max() { echo $ret } +# Check the required fio version. Examples: +# _require_fio_version 3.38 (matches 3.38 only) +# _require_fio_version 3.38+ (matches 3.38 and above) +# _require_fio_version 3.38- (matches 3.38 and below)This requires the user to know the version which corresponds to the feature. Is that how things are done for other such utilities and their versions vs features?
I don't like to use "version" to be the _require_ condition either. fstests always recommend "checking if the feature/behavior is really supported" at first, not a hard version limitation. Some old downstream software might backport new upstream commits, the version is useless for them.
Thanks,
Zorro
Quoted 9 lines
I was going to suggest exporting something like
_require_fio_atomic_writes(), and_require_fio_atomic_writes()calls_require_fio_version()to check the version.Thanks,
John
Re: [PATCH v5 02/12] common/rc: Add _require_fio_version helper
In reply to John GarryQuoted 33 lines · John Garry
Quoted 21 lines · Ojaswin Mujoo
The main motivation of adding this function on top of
_require_fiois that there has been a case in fio where atomic= option was added but later it was changed to noop since kernel didn't yet have support for atomic writes. It was then again utilized to do atomic writes in a later version, once kernel got the support. Due to this there is a point in fio where_require_fiow/ atomic=1 will succeed even though it would not be doing atomic writes.Hence, add an explicit helper to ensure tests to require specific versions of fio to work past such issues.
Signed-off-by Ojaswin Mujoo <[email protected]>--- common/rc | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+)common/rc +0
-0common/rc _max() { · line 6002
Quoted 6 lines
common/rc +4
-0@@ -5997,6 +5997,38 @@ _max() { echo $ret } +# Check the required fio version. Examples: +# _require_fio_version 3.38 (matches 3.38 only) +# _require_fio_version 3.38+ (matches 3.38 and above) +# _require_fio_version 3.38- (matches 3.38 and below)This requires the user to know the version which corresponds to the feature. Is that how things are done for other such utilities and their versions vs features?
Quoted 3 lines
I was going to suggest exporting something like
_require_fio_atomic_writes(), and_require_fio_atomic_writes()calls_require_fio_version()to check the version.
(Sorry, I made a half reply in my last email)
This looks better than only using _require_fio_version. But the nature is still checking fio version. If we don't have a better idea to check if fio really support atomic writes, the _require_fio_version is still needed. Or we rename it to "__require_fio_version" (one more "_"), to mark it's not recommended using directly. But that looks a bit like a trick :-D
Thanks,
Zorro
Quoted 5 lines
Thanks,
John
John Garry
Reviewed-byRe: [PATCH v5 11/12] ext4: Test atomic writes allocation and write codepaths with bigalloc
In reply to Ojaswin MujooQuoted 7 lines · Ojaswin Mujoo
Quoted 6 lines
tests/ext4/062 line 92
Quoted 5 lines
tests/ext4/062 new file +5
-0+ [verify-job7] + filename=$SCRATCH_MNT/testfile-job7 + + [verify-job8] + filename=$SCRATCH_MNT/testfile-job8do you really need multiple jobs for verify?
Yes since we want each job to verify it's own file.
ok, I suppose so:
Re: [PATCH v5 04/12] ltp/fsx.c: Add atomic writes support to fsx
In reply to Ojaswin MujooQuoted 15 lines · Ojaswin Mujoo
Quoted 7 lines
Quoted 7 lines
ltp/fsx.c +7
-0+ +/* +* Round down n to nearest power of 2. +* If n is already a power of 2, return n; +*/ +static int rounddown_pow_of_2 (int n) { +int i =0 ;Quoted 8 lines
ltp/fsx.c update_file_size(unsigned offset, unsigned size) · line 1105
Quoted 7 lines
ltp/fsx.c +7
-0+ +if (is_power_of_2 (n)) +return n; + +for (; (1 << i) < n; i++); + +return 1 << (i -1 );Is this the neatest way to do this?
Quoted 2 lines
Well it is a straigforward o(logn) way. Do you have something else in mind?
check what the kernel does is always a good place to start...
Thanks
Re: [PATCH v5 09/12] generic: Add sudden shutdown tests for multi block atomic writes
In reply to Ojaswin MujooQuoted 56 lines · Ojaswin Mujoo
Quoted 44 lines · John Garry
Quoted 10 lines · Ojaswin Mujoo
This test is intended to ensure that multi blocks atomic writes maintain atomic guarantees across sudden FS shutdowns.
The way we work is that we lay out a file with random mix of written, unwritten and hole extents. Then we start performing atomic writes sequentially on the file while we parallely shutdown the FS. Then we note the last offset where the atomic write happened just before shut down and then make sure blocks around it either have completely old data or completely new data, ie the write was not torn during shutdown.
Quoted 7 lines
We repeat the same with completely written, completely unwritten and completely empty file to ensure these cases are not torn either. Finally, we have a similar test for append atomic writes
Suggested-by Ritesh Harjani (IBM) <[email protected]>Reviewed-by Darrick J. Wong <[email protected]>Signed-off-by Ojaswin Mujoo <[email protected]>Please check comments, below, thanks!
Quoted 23 lines
--- tests/generic/1230 | 397 +++++++++++++++++++++++++++++++++++++++++ tests/generic/1230.out | 2 + 2 files changed, 399 insertions(+)create mode 100755 tests/generic/1230
create mode 100644 tests/generic/1230.outtests/generic/1230 new file +10
-0@@ -0,0 +1,397 @@ +#! /bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2025 IBM Corporation. All Rights Reserved. +# +# FS QA Test No. 1230 +# +# Test multi block atomic writes with sudden FS shutdowns to ensure +# the FS is not tearing the write operation +. ./common/preamble +. ./common/atomicwritesQuoted 8 lines
tests/generic/1230 line 16
Quoted 6 lines
tests/generic/1230 new file +6
-0+_begin_fstest auto atomicwrites + +_require_scratch_write_atomic_multi_fsblock +_require_atomic_write_test_commands +_require_scratch_shutdown +_require_xfs_io_command "truncate"is a similar fallocate test needed?
Hey John, we run the test for the following cases:
Quoted 8 lines
- file has mixed mapping
- file has only hole (trucate case)
- file has only unwritten blocks (falloc case)
- file has only written blocksSo we already do that. It's just that we don't need the
_require_xfs_io_command"falloc" since it is already included in_require_atomic_write_test_commnads.
ok
Quoted 20 lines
Quoted 11 lines
Quoted 11 lines
tests/generic/1230 new file +9
-0+ +_scratch_mkfs >> $seqres.full 2>&1 +_scratch_mount >> $seqres.full + +testfile=$SCRATCH_MNT/testfile +touch $testfile + +awu_max=$(_get_atomic_write_unit_max $testfile) +blksz=$(_get_block_size $SCRATCH_MNT)Quoted 8 lines
tests/generic/1230 line 31
Quoted 6 lines
tests/generic/1230 new file +6
-0+echo "Awu max: $awu_max" >> $seqres.full + +num_blocks=$((awu_max / blksz)) +# keep initial value high for dry run. This will be +# tweaked in dry_run() based on device write speed. +filesize=$(( 10 * 1024 * 1024 * 1024 ))could this cause some out-of-space issue? That's 10GB, right?
Quoted 7 lines
Hey John, yes this is just a dummy value. We tune the filesize later based on how fast the device is. That will usually be around 3 * (bytes written in 0.2s) (check
dry_runfunction).Generally this will be a smaller size. ( 3GB on a 5GB/s SSD.) But yes I should probably add a
_notrunif our ssd fast enough to fill up the full FS in 0.2s.
ok, good
Quoted 81 lines
Quoted 16 lines
Quoted 16 lines
tests/generic/1230 new file +14
-0+ +_cleanup() { + [ -n "$awloop_pid" ] && kill $awloop_pid &> /dev/null + wait +} + +atomic_write_loop() { + local off=0 + local size=$awu_max + for ((i=0; i<$((filesize / $size )); i++)); do + # Due to sudden shutdown this can produce errors so just + # redirect them to seqres.full + $XFS_IO_PROG -c "open -fsd $testfile" -c "pwrite -S 0x61 -DA -V1 -b $size $off $size" >> /dev/null 2>>$seqres.full + echo "Written to offset: $off" >> $tmp.awQuoted 8 lines
tests/generic/1230 line 51
Quoted 6 lines
tests/generic/1230 new file +6
-0+ off=$((off + $size)) + done +} + +# This test has the following flow: +# 1. Start doing sequential atomic writes in bg, upto $filesizebg?
background*. I'll change it.
Quoted 46 lines
Quoted 46 lines
tests/generic/1230 new file +45
-0+# 2. Sleep for 0.2s and shutdown the FS +# 3. kill the atomic write process +# 4. verify the writes were not torn +# +# We ideally want the shutdown to happen while an atomic write is ongoing +# but this gets tricky since faster devices can actually finish the whole +# atomic write loop before sleep 0.2s completes, resulting in the shutdown +# happening after the write loop which is not what we want. A simple solution +# to this is to increase $filesize so step 1 takes long enough but a big +# $filesize leads to create_mixed_mappings() taking very long, which is not +# ideal. +# +# Hence, use the dry_run function to figure out the rough device speed and set +# $filesize accordingly. +dry_run() { + echo >> $seqres.full + echo "# Estimating ideal filesize..." >> $seqres.full + atomic_write_loop & + awloop_pid=$! + + local i=0 + # Wait for atleast first write to be recorded or 10s + while [ ! -f "$tmp.aw" -a $i -le 50 ]; do i=$((i + 1)); sleep 0.2; done + + if [[ $i -gt 50 ]] + then + _fail "atomic write process took too long to start" + fi + + echo >> $seqres.full + echo "# Shutting down filesystem while write is running" >> $seqres.full + _scratch_shutdown + + kill $awloop_pid 2>/dev/null # the process might have finished already + wait $awloop_pid + unset $awloop_pid + + bytes_written=$(tail -n 1 $tmp.aw | cut -d" " -f4) + echo "# Bytes written in 0.2s: $bytes_written" >> $seqres.full + + filesize=$((bytes_written * 3)) + echo "# Setting \$filesize=$filesize" >> $seqres.full + + rm $tmp.aw + sleep 0.5Quoted 8 lines
tests/generic/1230 line 102
Quoted 6 lines
tests/generic/1230 new file +6
-0+ + _scratch_cycle_mount + +} + +create_mixed_mappings() {Is this same as patch 08/12?
Quoted 1 line
I believe you mean the [D]SYNC tests, yes it is the same.
then maybe factor out the test, if possible. I assume that this sort of approach is taken for xfstests.
Quoted 20 lines
Quoted 11 lines
Quoted 11 lines
tests/generic/1230 new file +9
-0+ local file=$1 + local size_bytes=$2 + + echo "# Filling file $file with alternate mappings till size $size_bytes" >> $seqres.full + #Fill the file with alternate written and unwritten blocks + local off=0 + local operations=("W" "U") + + for ((i=0; i<$((size_bytes / blksz )); i++)); doQuoted 8 lines
tests/generic/1230 line 117
Quoted 6 lines
tests/generic/1230 new file +6
-0+ index=$(($i % ${#operations[@]})) + map="${operations[$index]}" + + case "$map" in + "W") + $XFS_IO_PROG -fc "pwrite -b $blksz $off $blksz" $file >> /dev/nulldoes this just write random data? I don't see any pattern being set.
Quoted 2 lines
By default pwrite writes
0xcdif no patterns is specified. This helps us reliably check the data back.
ok, understood
Quoted 66 lines
Quoted 48 lines
Quoted 48 lines
tests/generic/1230 new file +46
-0+ ;; + "U") + $XFS_IO_PROG -fc "falloc $off $blksz" $file >> /dev/null + ;; + esac + off=$((off + blksz)) + done + + sync $file +} + +populate_expected_data() { + # create a dummy file with expected old data for different cases + create_mixed_mappings $testfile.exp_old_mixed $awu_max + expected_data_old_mixed=$(od -An -t x1 -j 0 -N $awu_max $testfile.exp_old_mixed) + + $XFS_IO_PROG -fc "falloc 0 $awu_max" $testfile.exp_old_zeroes >> $seqres.full + expected_data_old_zeroes=$(od -An -t x1 -j 0 -N $awu_max $testfile.exp_old_zeroes) + + $XFS_IO_PROG -fc "pwrite -b $awu_max 0 $awu_max" $testfile.exp_old_mapped >> $seqres.full + expected_data_old_mapped=$(od -An -t x1 -j 0 -N $awu_max $testfile.exp_old_mapped) + + # create a dummy file with expected new data + $XFS_IO_PROG -fc "pwrite -S 0x61 -b $awu_max 0 $awu_max" $testfile.exp_new >> $seqres.full + expected_data_new=$(od -An -t x1 -j 0 -N $awu_max $testfile.exp_new) +} + +verify_data_blocks() { + local verify_start=$1 + local verify_end=$2 + local expected_data_old="$3" + local expected_data_new="$4" + + echo >> $seqres.full + echo "# Checking data integrity from $verify_start to $verify_end" >> $seqres.full + + # After an atomic write, for every chunk we ensure that the underlying + # data is either the old data or new data as writes shouldn't get torn. + local off=$verify_start + while [[ "$off" -lt "$verify_end" ]] + do + #actual_data=$(xxd -s $off -l $awu_max -p $testfile) + actual_data=$(od -An -t x1 -j $off -N $awu_max $testfile) + if [[ "$actual_data" != "$expected_data_new" ]] && [[ "$actual_data" != "$expected_data_old" ]] + then + echo "Checksum match failed at off: $off size: $awu_max"Quoted 8 lines
tests/generic/1230 line 167
Quoted 4 lines
tests/generic/1230 new file +4
-0+ echo "Expected contents: (Either of the 2 below):" + echo + echo "Expected old: " + echo "$expected_data_old"it would be nice if this was deterministic - see comment in
create_mixed_mappingsYes, it is. It will be
0xcdcdcdcdQuoted 7 lines
tests/generic/1230 line 170
Quoted 4 lines
tests/generic/1230 new file +3
-0+ echo + echo "Expected new: " + echo "$expected_data_new"nit: I am not sure what is meant by "expected". I would just have "new data". We don't know what to expect, as it could be old or new, right?
Quoted 5 lines
Yes, so the I was thinking of it this way:
We either expect the data to be the full new (named
expected_new) or fully old (namedexpected_old). Else renaming it to new vs old vs actual makese it a bit more confusing imo
ok, good
Quoted 49 lines
Quoted 40 lines
Quoted 17 lines
tests/generic/1230 new file +15
-0+ echo + echo "Actual contents: " + echo "$actual_data" + + _fail + fi + echo -n "Check at offset $off suceeded! " >> $seqres.full + if [[ "$actual_data" == "$expected_data_new" ]] + then + echo "matched new" >> $seqres.full + elif [[ "$actual_data" == "$expected_data_old" ]] + then + echo "matched old" >> $seqres.full + fi + off=$(( off + awu_max ))tests/generic/1230 line 195
Quoted 10 lines
tests/generic/1230 new file +10
-0+ done +} + +# test data integrity for file by shutting down in between atomic writes +test_data_integrity() { + echo >> $seqres.full + echo "# Writing atomically to file in background" >> $seqres.full + atomic_write_loop & + awloop_pid=$! +from here ...
Quoted 10 lines
tests/generic/1230 new file +10
-0+ local i=0 + # Wait for atleast first write to be recorded or 10s + while [ ! -f "$tmp.aw" -a $i -le 50 ]; do i=$((i + 1)); sleep 0.2; done + + if [[ $i -gt 50 ]] + then + _fail "atomic write process took too long to start" + fi + + echo >> $seqres.fullQuoted 8 lines
tests/generic/1230 line 87
Quoted 6 lines
tests/generic/1230 new file +6
-0+ echo "# Shutting down filesystem while write is running" >> $seqres.full + _scratch_shutdown + + kill $awloop_pid 2>/dev/null # the process might have finished already + wait $awloop_pid + unset $awloop_pid... to here looks similar in many functions. Can we factor it out?
Quoted 1 line
Right thats true, I'll factor this out. Thanks for pointing it out.
ok
Quoted 201 lines
Quoted 34 lines
Quoted 21 lines
tests/generic/1230 new file +20
-0+ + last_offset=$(tail -n 1 $tmp.aw | cut -d" " -f4) + if [[ -z $last_offset ]] + then + last_offset=0 + fi + + echo >> $seqres.full + echo "# Last offset of atomic write: $last_offset" >> $seqres.full + + rm $tmp.aw + sleep 0.5 + + _scratch_cycle_mount + + # we want to verify all blocks around which the shutdown happended + verify_start=$(( last_offset - (awu_max * 5))) + if [[ $verify_start < 0 ]] + then + verify_start=0tests/generic/1230 line 241
Quoted 10 lines
tests/generic/1230 new file +10
-0+ fi + + verify_end=$(( last_offset + (awu_max * 5))) + if [[ "$verify_end" -gt "$filesize" ]] + then + verify_end=$filesize + fi +} + +# test data integrity for file wiht written and unwritten mappingswith
Quoted 16 lines
Quoted 4 lines
tests/generic/1230 new file +4
-0+test_data_integrity_mixed() { + $XFS_IO_PROG -fc "truncate 0" $testfile >> $seqres.full + + echo >> $seqres.fulltests/generic/1230 line 255
Quoted 10 lines
tests/generic/1230 new file +10
-0+ echo "# Creating testfile with mixed mappings" >> $seqres.full + create_mixed_mappings $testfile $filesize + + test_data_integrity + + verify_data_blocks $verify_start $verify_end "$expected_data_old_mixed" "$expected_data_new" +} + +# test data integrity for file with completely written mappings +test_data_integrity_writ() {please spell "writ" out fully, which I think should be "written"
Yes, will do.
Quoted 134 lines
Quoted 5 lines
tests/generic/1230 new file +4
-0+ $XFS_IO_PROG -fc "truncate 0" $testfile >> $seqres.full + + echo >> $seqres.full + echo "# Creating testfile with fully written mapping" >> $seqres.fulltests/generic/1230 line 269
Quoted 10 lines
tests/generic/1230 new file +10
-0+ $XFS_IO_PROG -c "pwrite -b $filesize 0 $filesize" $testfile >> $seqres.full + sync $testfile + + test_data_integrity + + verify_data_blocks $verify_start $verify_end "$expected_data_old_mapped" "$expected_data_new" +} + +# test data integrity for file with completely unwritten mappings +test_data_integrity_unwrit() {same as above
Quoted 116 lines
tests/generic/1230 new file +116
-0+ $XFS_IO_PROG -fc "truncate 0" $testfile >> $seqres.full + + echo >> $seqres.full + echo "# Creating testfile with fully unwritten mappings" >> $seqres.full + $XFS_IO_PROG -c "falloc 0 $filesize" $testfile >> $seqres.full + sync $testfile + + test_data_integrity + + verify_data_blocks $verify_start $verify_end "$expected_data_old_zeroes" "$expected_data_new" +} + +# test data integrity for file with no mappings +test_data_integrity_hole() { + $XFS_IO_PROG -fc "truncate 0" $testfile >> $seqres.full + + echo >> $seqres.full + echo "# Creating testfile with no mappings" >> $seqres.full + $XFS_IO_PROG -c "truncate $filesize" $testfile >> $seqres.full + sync $testfile + + test_data_integrity + + verify_data_blocks $verify_start $verify_end "$expected_data_old_zeroes" "$expected_data_new" +} + +test_filesize_integrity() { + $XFS_IO_PROG -c "truncate 0" $testfile >> $seqres.full + + echo >> $seqres.full + echo "# Performing extending atomic writes over file in background" >> $seqres.full + atomic_write_loop & + awloop_pid=$! + + local i=0 + # Wait for atleast first write to be recorded or 10s + while [ ! -f "$tmp.aw" -a $i -le 50 ]; do i=$((i + 1)); sleep 0.2; done + + if [[ $i -gt 50 ]] + then + _fail "atomic write process took too long to start" + fi + + echo >> $seqres.full + echo "# Shutting down filesystem while write is running" >> $seqres.full + _scratch_shutdown + + kill $awloop_pid 2>/dev/null # the process might have finished already + wait $awloop_pid + unset $awloop_pid + + local last_offset=$(tail -n 1 $tmp.aw | cut -d" " -f4) + if [[ -z $last_offset ]] + then + last_offset=0 + fi + + echo >> $seqres.full + echo "# Last offset of atomic write: $last_offset" >> $seqres.full + rm $tmp.aw + sleep 0.5 + + _scratch_cycle_mount + local filesize=$(_get_filesize $testfile) + echo >> $seqres.full + echo "# Filesize after shutdown: $filesize" >> $seqres.full + + # To confirm that the write went atomically, we check: + # 1. The last block should be a multiple of awu_max + # 2. The last block should be the completely new data + + if (( $filesize % $awu_max )) + then + echo "Filesize after shutdown ($filesize) not a multiple of atomic write unit ($awu_max)" + fi + + verify_start=$(( filesize - (awu_max * 5))) + if [[ $verify_start < 0 ]] + then + verify_start=0 + fi + + local verify_end=$filesize + + # Here the blocks should always match new data hence, for simplicity of + # code, just corrupt the $expected_data_old buffer so it never matches + local expected_data_old="POISON" + verify_data_blocks $verify_start $verify_end "$expected_data_old" "$expected_data_new" +} + +$XFS_IO_PROG -fc "truncate 0" $testfile >> $seqres.full + +dry_run + +echo >> $seqres.full +echo "# Populating expected data buffers" >> $seqres.full +populate_expected_data + +# Loop 20 times to shake out any races due to shutdown +for ((iter=0; iter<20; iter++)) +do + echo >> $seqres.full + echo "------ Iteration $iter ------" >> $seqres.full + + echo >> $seqres.full + echo "# Starting data integrity test for atomic writes over mixed mapping" >> $seqres.full + test_data_integrity_mixed + + echo >> $seqres.full + echo "# Starting data integrity test for atomic writes over fully written mapping" >> $seqres.full + test_data_integrity_writ + + echo >> $seqres.full + echo "# Starting data integrity test for atomic writes over fully unwritten mapping" >> $seqres.full + test_data_integrity_unwrit +Quoted 8 lines
tests/generic/1230 line 391
Quoted 6 lines
tests/generic/1230 new file +6
-0+ echo >> $seqres.full + echo "# Starting data integrity test for atomic writes over holes" >> $seqres.full + test_data_integrity_hole + + echo >> $seqres.full + echo "# Starting filesize integrity test for atomic writes" >> $seqres.fullwhat does "Starting filesize integrity test" mean?
Basically other tests already truncate the file to a higher value and then perform the shut down test. Here we actually do append atomic writes since we want to also stress the
i_sizeupdate paths during shutdown to ensure that doesn't cause any tearing with atomic writes.
Quoted 7 lines
I can maybe rename it to:
echo "# Starting data integrity test for atomic append writes" >> $seqres.full
Thanks for the review!
It's just the name "integrity" that throws me a bit..
Re: [PATCH v5 09/12] generic: Add sudden shutdown tests for multi block atomic writes
Quoted 10 lines · John Garry
Quoted 9 lines · Ojaswin Mujoo
Quoted 8 lines · John Garry
Quoted 7 lines · Ojaswin Mujoo
--- tests/generic/1230 | 397 +++++++++++++++++++++++++++++++++++++++++ tests/generic/1230.out | 2 + 2 files changed, 399 insertions(+)create mode 100755 tests/generic/1230
create mode 100644 tests/generic/1230.out
<...>
Quoted 18 lines
Quoted 18 lines
Quoted 17 lines
Quoted 5 lines
tests/generic/1230 new file +5
-0+ + bytes_written=$(tail -n 1 $tmp.aw | cut -d" " -f4) + echo "# Bytes written in 0.2s: $bytes_written" >> $seqres.full + + filesize=$((bytes_written * 3))tests/generic/1230 line 102
Quoted 10 lines
tests/generic/1230 new file +10
-0+ echo "# Setting \$filesize=$filesize" >> $seqres.full + + rm $tmp.aw + sleep 0.5 + + _scratch_cycle_mount + +} + +create_mixed_mappings() {Is this same as patch 08/12?
Quoted 5 lines
Quoted 1 line
I believe you mean the [D]SYNC tests, yes it is the same.
then maybe factor out the test, if possible. I assume that this sort of approach is taken for xfstests.
I'm not sure what you mean by factor out the *test*. We are testing different things there and the only thing common in the tests is creation of mixed mapping files and the check to ensure we didn't tear data.
In case you mean to factor out the create_mixed_mappings() helper into common/rc, sure I can do that but I'm unsure if at this point it would be very useful for other tests.
Quoted 14 lines
Quoted 14 lines
Quoted 14 lines
Quoted 14 lines
tests/generic/1230 new file +12
-0+ local file=$1 + local size_bytes=$2 + + echo "# Filling file $file with alternate mappings till size $size_bytes" >> $seqres.full + #Fill the file with alternate written and unwritten blocks + local off=0 + local operations=("W" "U") + + for ((i=0; i<$((size_bytes / blksz )); i++)); do + index=$(($i % ${#operations[@]})) + map="${operations[$index]}" +
<...>
Quoted 35 lines
Quoted 35 lines
Quoted 18 lines
Quoted 18 lines
tests/generic/1230 new file +18
-0+# Loop 20 times to shake out any races due to shutdown +for ((iter=0; iter<20; iter++)) +do + echo >> $seqres.full + echo "------ Iteration $iter ------" >> $seqres.full + + echo >> $seqres.full + echo "# Starting data integrity test for atomic writes over mixed mapping" >> $seqres.full + test_data_integrity_mixed + + echo >> $seqres.full + echo "# Starting data integrity test for atomic writes over fully written mapping" >> $seqres.full + test_data_integrity_writ + + echo >> $seqres.full + echo "# Starting data integrity test for atomic writes over fully unwritten mapping" >> $seqres.full + test_data_integrity_unwrit +Quoted 8 lines
tests/generic/1230 line 391
Quoted 6 lines
tests/generic/1230 new file +6
-0+ echo >> $seqres.full + echo "# Starting data integrity test for atomic writes over holes" >> $seqres.full + test_data_integrity_hole + + echo >> $seqres.full + echo "# Starting filesize integrity test for atomic writes" >> $seqres.fullwhat does "Starting filesize integrity test" mean?
Basically other tests already truncate the file to a higher value and then perform the shut down test. Here we actually do append atomic writes since we want to also stress the
i_sizeupdate paths during shutdown to ensure that doesn't cause any tearing with atomic writes.I can maybe rename it to:
Quoted 6 lines
Quoted 4 lines
echo "# Starting data integrity test for atomic append writes" >> $seqres.full
Thanks for the review!
It's just the name "integrity" that throws me a bit..
So I mean integrity as in writes are not tearing after the shutdown. That's how we have worded the other sub-tests above.
Regards,
ojaswin
Re: [PATCH v5 04/12] ltp/fsx.c: Add atomic writes support to fsx
In reply to John GarryQuoted 16 lines · John Garry
Quoted 15 lines · Ojaswin Mujoo
Quoted 15 lines
Quoted 4 lines
ltp/fsx.c +4
-0+ +/* +* Round down n to nearest power of 2. +* If n is already a power of 2, return n; ltp/fsx.c update_file_size(unsigned offset, unsigned size) · line 1105
Quoted 10 lines
ltp/fsx.c +10
-0+*/ +static int rounddown_pow_of_2 (int n) { +int i =0 ; + +if (is_power_of_2 (n)) +return n; + +for (; (1 << i) < n; i++); + +return 1 << (i -1 );Is this the neatest way to do this?
Quoted 6 lines
Quoted 2 lines
Well it is a straigforward o(logn) way. Do you have something else in mind?
check what the kernel does is always a good place to start...
Thanks
So kernel pretty much does same thing:
unsigned long __rounddown_pow_of_two(unsigned long n)
{
return 1UL << (fls_long(n) - 1);
}where fls*() variants jump into asm for efficiency. asm is obviously not needed here so we use the for loop to calculate the fls (find last bit set):
for (; (1 << i) < n; i++);
Ideally the compiler should do the right thing and optimize it.
Regards,
ojaswin
Re: [PATCH v5 02/12] common/rc: Add _require_fio_version helper
In reply to Zorro LangQuoted 40 lines · Zorro Lang
Quoted 33 lines · John Garry
Quoted 21 lines · Ojaswin Mujoo
The main motivation of adding this function on top of
_require_fiois that there has been a case in fio where atomic= option was added but later it was changed to noop since kernel didn't yet have support for atomic writes. It was then again utilized to do atomic writes in a later version, once kernel got the support. Due to this there is a point in fio where_require_fiow/ atomic=1 will succeed even though it would not be doing atomic writes.Hence, add an explicit helper to ensure tests to require specific versions of fio to work past such issues.
Signed-off-by Ojaswin Mujoo <[email protected]>--- common/rc | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+)common/rc +0
-0common/rc _max() { · line 6002
Quoted 6 lines
common/rc +4
-0@@ -5997,6 +5997,38 @@ _max() { echo $ret } +# Check the required fio version. Examples: +# _require_fio_version 3.38 (matches 3.38 only) +# _require_fio_version 3.38+ (matches 3.38 and above) +# _require_fio_version 3.38- (matches 3.38 and below)This requires the user to know the version which corresponds to the feature. Is that how things are done for other such utilities and their versions vs features?
Quoted 3 lines
I was going to suggest exporting something like
_require_fio_atomic_writes(), and_require_fio_atomic_writes()calls_require_fio_version()to check the version.(Sorry, I made a half reply in my last email)
Quoted 8 lines
This looks better than only using
_require_fio_version. But the nature is still checking fio version. If we don't have a better idea to check if fio really support atomic writes, the_require_fio_versionis still needed. Or we rename it to "__require_fio_version" (one more "_"), to mark it's not recommended using directly. But that looks a bit like a trick :-DThanks,
Zorro
Hey Zorro, I agree with your points that version might not be the best indicator esp for downstream software, but at this point I'm unsure what's the workaround.
One thing that comes to mind is to let fio do the atomic write and use the tracepoints to confirm if RWF_ATOMIC was passed, but that adds a lot of dependency on tracing framework being present (im unsure if something like this is used somewhere in xfstests before). Further it's messy to figure out that out of all the IO fio command will do, which one to check for RWF_ATOMIC.
It can be done I suppose but is this sort of complexity something we want to add is the question. Or do we just go ahead with the version check.
Regards,
ojaswin
Quoted 8 lines
Quoted 7 lines
Thanks,
John
Re: [PATCH v5 02/12] common/rc: Add _require_fio_version helper
Quoted 28 lines · Ojaswin Mujoo
Quoted 9 lines
Quoted 7 lines
This requires the user to know the version which corresponds to the feature. Is that how things are done for other such utilities and their versions vs features?
I was going to suggest exporting something like
_require_fio_atomic_writes(), and_require_fio_atomic_writes()calls_require_fio_version()to check the version.(Sorry, I made a half reply in my last email)
Quoted 8 lines
This looks better than only using
_require_fio_version. But the nature is still checking fio version. If we don't have a better idea to check if fio really support atomic writes, the_require_fio_versionis still needed. Or we rename it to "__require_fio_version" (one more "_"), to mark it's not recommended using directly. But that looks a bit like a trick 😂Thanks,
ZorroHey Zorro, I agree with your points that version might not be the best indicator esp for downstream software, but at this point I'm unsure what's the workaround.
One thing that comes to mind is to let fio do the atomic write and use the tracepoints to confirm if
RWF_ATOMICwas passed, but that adds a lot of dependency on tracing framework being present (im unsure if something like this is used somewhere in xfstests before). Further it's messy to figure out that out of all the IO fio command will do, which one to check forRWF_ATOMIC.
Quoted 3 lines
It can be done I suppose but is this sort of complexity something we want to add is the question. Or do we just go ahead with the version check.
I think that just checking the version is fine for this specific feature. But I still also think that versioning should be hidden from the end user, i.e. we should provide a helper like _require_fio_atomic_writes
thanks,
John
Re: [PATCH v5 09/12] generic: Add sudden shutdown tests for multi block atomic writes
In reply to Ojaswin MujooQuoted 6 lines · Ojaswin Mujoo
Quoted 6 lines
Quoted 3 lines
Quoted 2 lines
Quoted 1 line
+create_mixed_mappings() {
Is this same as patch 08/12?
I believe you mean the [D]SYNC tests, yes it is the same.
then maybe factor out the test, if possible. I assume that this sort of approach is taken for xfstests.
Quoted 8 lines
I'm not sure what you mean by factor out the*test*. We are testing different things there and the only thing common in the tests is creation of mixed mapping files and the check to ensure we didn't tear data.
In case you mean to factor out the
create_mixed_mappings()helper into common/rc, sure I can do that but I'm unsure if at this point it would be very useful for other tests.
above it was mentioned that the code in create_mixed_mappings was common, so that is why I suggested to factor it out. If it does not make sense, then fine (and don't).
Quoted 27 lines
Quoted 13 lines
Quoted 13 lines
Quoted 13 lines
Quoted 13 lines
tests/generic/1230 new file +12
-0+ local file=$1 + local size_bytes=$2 + + echo "# Filling file $file with alternate mappings till size $size_bytes" >> $seqres.full + #Fill the file with alternate written and unwritten blocks + local off=0 + local operations=("W" "U") + + for ((i=0; i<$((size_bytes / blksz )); i++)); do + index=$(($i % ${#operations[@]})) + map="${operations[$index]}" +<...>
Quoted 11 lines
Quoted 11 lines
Quoted 3 lines
Review
Quoted 2 lines
(quoted diff) +2
-0+ echo >> $seqres.full + echo "# Starting filesize integrity test for atomic writes" >> $seqres.fullwhat does "Starting filesize integrity test" mean?
Basically other tests already truncate the file to a higher value and then perform the shut down test. Here we actually do append atomic writes since we want to also stress the
i_sizeupdate paths during shutdown to ensure that doesn't cause any tearing with atomic writes.I can maybe rename it to:
Quoted 7 lines
Quoted 5 lines
Quoted 4 lines
echo "# Starting data integrity test for atomic append writes" >> $seqres.full
Thanks for the review!
It's just the name "integrity" that throws me a bit..
So I mean integrity as in writes are not tearing after the shutdown. That's how we have worded the other sub-tests above.
you could mention "shutdown" also in the print.
Thanks!
Re: [PATCH v5 04/12] ltp/fsx.c: Add atomic writes support to fsx
In reply to Ojaswin MujooQuoted 27 lines · Ojaswin Mujoo
Quoted 15 lines
Quoted 15 lines
Quoted 15 lines
Quoted 4 lines
ltp/fsx.c +4
-0+ +/* +* Round down n to nearest power of 2. +* If n is already a power of 2, return n; ltp/fsx.c update_file_size(unsigned offset, unsigned size) · line 1105
Quoted 10 lines
ltp/fsx.c +10
-0+*/ +static int rounddown_pow_of_2 (int n) { +int i =0 ; + +if (is_power_of_2 (n)) +return n; + +for (; (1 << i) < n; i++); + +return 1 << (i -1 );Is this the neatest way to do this?
Quoted 5 lines
Quoted 2 lines
Well it is a straigforward o(logn) way. Do you have something else in mind?
check what the kernel does is always a good place to start...
Thanks
So kernel pretty much does same thing:
unsigned long
__rounddown_pow_of_two(unsigned long n){ return 1UL << (fls_long(n) - 1); }
Quoted 7 lines
where fls*() variants jump into asm for efficiency. asm is obviously not needed here so we use the for loop to calculate the fls (find last bit set):
for (; (1 << i) < n; i++);Ideally the compiler should do the right thing and optimize it.
I think that some versions of fls use builtin clz also, but I am not sure if that (clz) is always available for supported toolchains here.
Thanks,
John
Re: [PATCH v5 04/12] ltp/fsx.c: Add atomic writes support to fsx
Quoted 32 lines · John Garry
Quoted 31 lines · Ojaswin Mujoo
Quoted 15 lines
Quoted 15 lines
Quoted 15 lines
Quoted 4 lines
ltp/fsx.c +4
-0+ +/* +* Round down n to nearest power of 2. +* If n is already a power of 2, return n; ltp/fsx.c update_file_size(unsigned offset, unsigned size) · line 1105
Quoted 10 lines
ltp/fsx.c +10
-0+*/ +static int rounddown_pow_of_2 (int n) { +int i =0 ; + +if (is_power_of_2 (n)) +return n; + +for (; (1 << i) < n; i++); + +return 1 << (i -1 );Is this the neatest way to do this?
Quoted 5 lines
Quoted 2 lines
Well it is a straigforward o(logn) way. Do you have something else in mind?
check what the kernel does is always a good place to start...
Thanks
So kernel pretty much does same thing:
unsigned long
__rounddown_pow_of_two(unsigned long n){ return 1UL << (fls_long(n) - 1); }where fls*() variants jump into asm for efficiency. asm is obviously not needed here so we use the for loop to calculate the fls (find last bit set):
Quoted 6 lines
Quoted 3 lines
for (; (1 << i) < n; i++);Ideally the compiler should do the right thing and optimize it.
I think that some versions of fls use builtin clz also, but I am not sure if that (clz) is always available for supported toolchains here.
Right, kernel also tries that whenever possible. Im unsure though if fls() is a libc function. I think its not but I'm not able to find any resources to confirm this.
In case it is not, then I think the current logic should be good enough.
Quoted 3 lines
Thanks,
John
Re: [PATCH v5 09/12] generic: Add sudden shutdown tests for multi block atomic writes
In reply to John GarryQuoted 12 lines · John Garry
Quoted 11 lines · Ojaswin Mujoo
Quoted 6 lines
Quoted 3 lines
Quoted 2 lines
Quoted 1 line
+create_mixed_mappings() {
Is this same as patch 08/12?
I believe you mean the [D]SYNC tests, yes it is the same.
then maybe factor out the test, if possible. I assume that this sort of approach is taken for xfstests.
I'm not sure what you mean by factor out the*test*. We are testing different things there and the only thing common in the tests is creation of mixed mapping files and the check to ensure we didn't tear data.
Quoted 7 lines
Quoted 3 lines
In case you mean to factor out the
create_mixed_mappings()helper into common/rc, sure I can do that but I'm unsure if at this point it would be very useful for other tests.above it was mentioned that the code in
create_mixed_mappingswas common, so that is why I suggested to factor it out. If it does not make sense, then fine (and don't).
Yes I mean Im unsure it will be useful for tests other than the two tests in this patchset.
Quoted 30 lines
Quoted 30 lines
Quoted 14 lines
Quoted 14 lines
Quoted 14 lines
Quoted 14 lines
tests/generic/1230 new file +12
-0+ local file=$1 + local size_bytes=$2 + + echo "# Filling file $file with alternate mappings till size $size_bytes" >> $seqres.full + #Fill the file with alternate written and unwritten blocks + local off=0 + local operations=("W" "U") + + for ((i=0; i<$((size_bytes / blksz )); i++)); do + index=$(($i % ${#operations[@]})) + map="${operations[$index]}" +<...>
Quoted 13 lines
Quoted 13 lines
Quoted 3 lines
Review
Quoted 2 lines
(quoted diff) +2
-0+ echo >> $seqres.full + echo "# Starting filesize integrity test for atomic writes" >> $seqres.fullwhat does "Starting filesize integrity test" mean?
Basically other tests already truncate the file to a higher value and then perform the shut down test. Here we actually do append atomic writes since we want to also stress the
i_sizeupdate paths during shutdown to ensure that doesn't cause any tearing with atomic writes.I can maybe rename it to:
echo "# Starting data integrity test for atomic append writes" >> $seqres.full
Quoted 7 lines
Quoted 5 lines
Quoted 3 lines
Quoted 2 lines
Thanks for the review!
It's just the name "integrity" that throws me a bit..
So I mean integrity as in writes are not tearing after the shutdown. That's how we have worded the other sub-tests above.
you could mention "shutdown" also in the print.
Umm, do you mean something like:
"Starting shutdown data integrity tests ..."
Regards,
ojaswin
Quoted 3 lines
Thanks!
Re: [PATCH v5 02/12] common/rc: Add _require_fio_version helper
In reply to John GarryQuoted 29 lines · John Garry
Quoted 28 lines · Ojaswin Mujoo
Quoted 9 lines
Quoted 7 lines
This requires the user to know the version which corresponds to the feature. Is that how things are done for other such utilities and their versions vs features?
I was going to suggest exporting something like
_require_fio_atomic_writes(), and_require_fio_atomic_writes()calls_require_fio_version()to check the version.(Sorry, I made a half reply in my last email)
Quoted 8 lines
This looks better than only using
_require_fio_version. But the nature is still checking fio version. If we don't have a better idea to check if fio really support atomic writes, the_require_fio_versionis still needed. Or we rename it to "__require_fio_version" (one more "_"), to mark it's not recommended using directly. But that looks a bit like a trick 😂Thanks,
ZorroHey Zorro, I agree with your points that version might not be the best indicator esp for downstream software, but at this point I'm unsure what's the workaround.
One thing that comes to mind is to let fio do the atomic write and use the tracepoints to confirm if
RWF_ATOMICwas passed, but that adds a lot of dependency on tracing framework being present (im unsure if something like this is used somewhere in xfstests before). Further it's messy to figure out that out of all the IO fio command will do, which one to check forRWF_ATOMIC.
Quoted 7 lines
Quoted 3 lines
It can be done I suppose but is this sort of complexity something we want to add is the question. Or do we just go ahead with the version check.
I think that just checking the version is fine for this specific feature. But I still also think that versioning should be hidden from the end user, i.e. we should provide a helper like
_require_fio_atomic_writes
Sure, I'm okay. @Zorro, does that sound okay to you?
Quoted 3 lines
thanks,
John
Re: [PATCH v5 09/12] generic: Add sudden shutdown tests for multi block atomic writes
In reply to Ojaswin MujooQuoted 4 lines · Ojaswin Mujoo
Quoted 1 line
you could mention "shutdown" also in the print.
Umm, do you mean something like:
"Starting shutdown data integrity tests ..."
yes :)
Re: [PATCH v5 09/12] generic: Add sudden shutdown tests for multi block atomic writes
Quoted 7 lines · John Garry
Quoted 4 lines · Ojaswin Mujoo
Quoted 1 line
you could mention "shutdown" also in the print.
Umm, do you mean something like:
"Starting shutdown data integrity tests ..."
yes :)
Sure, will do thanks!
Re: [PATCH v5 02/12] common/rc: Add _require_fio_version helper
In reply to Ojaswin MujooQuoted 17 lines · Ojaswin Mujoo
Quoted 16 lines · John Garry
Quoted 15 lines · Ojaswin Mujoo
Quoted 15 lines
Quoted 7 lines
This requires the user to know the version which corresponds to the feature. Is that how things are done for other such utilities and their versions vs features?
I was going to suggest exporting something like
_require_fio_atomic_writes(), and_require_fio_atomic_writes()calls_require_fio_version()to check the version.(Sorry, I made a half reply in my last email)
This looks better than only using
_require_fio_version. But the nature is still checking fio version. If we don't have a better idea to check if fio really support atomic writes, the_require_fio_versionis still needed. Or we rename it to "__require_fio_version" (one more "_"), to mark it's not recommended using directly. But that looks a bit like a trick 😂
Quoted 5 lines
Quoted 5 lines
Quoted 5 lines
Quoted 2 lines
Thanks,
ZorroHey Zorro, I agree with your points that version might not be the best indicator esp for downstream software, but at this point I'm unsure what's the workaround.
Hi Ojaswin, I don't have better workaround than require_fio_version for now. I mean:
1) name _require_fio_version as __require_fio_version, to mark it's an internal function of another common function.
2) only call __require_fio_version in _require_fio_atomic_writes for now, don't use it in any test cases directly.
Quoted 12 lines
Quoted 12 lines
Quoted 11 lines
One thing that comes to mind is to let fio do the atomic write and use the tracepoints to confirm if
RWF_ATOMICwas passed, but that adds a lot of dependency on tracing framework being present (im unsure if something like this is used somewhere in xfstests before). Further it's messy to figure out that out of all the IO fio command will do, which one to check forRWF_ATOMIC.It can be done I suppose but is this sort of complexity something we want to add is the question. Or do we just go ahead with the version check.
Quoted 5 lines
Quoted 3 lines
I think that just checking the version is fine for this specific feature. But I still also think that versioning should be hidden from the end user, i.e. we should provide a helper like
_require_fio_atomic_writesSure, I'm okay. @Zorro, does that sound okay to you?
Sure, that's what I tried to say as above, sorry if I made you misunderstand :)
Thanks,
Zorro
Quoted 4 lines
Quoted 3 lines
thanks,
John
Re: [PATCH v5 02/12] common/rc: Add _require_fio_version helper
Quoted 24 lines · Zorro Lang
Quoted 17 lines · Ojaswin Mujoo
Quoted 16 lines · John Garry
Quoted 15 lines · Ojaswin Mujoo
Quoted 15 lines
Quoted 7 lines
This requires the user to know the version which corresponds to the feature. Is that how things are done for other such utilities and their versions vs features?
I was going to suggest exporting something like
_require_fio_atomic_writes(), and_require_fio_atomic_writes()calls_require_fio_version()to check the version.(Sorry, I made a half reply in my last email)
This looks better than only using
_require_fio_version. But the nature is still checking fio version. If we don't have a better idea to check if fio really support atomic writes, the_require_fio_versionis still needed. Or we rename it to "__require_fio_version" (one more "_"), to mark it's not recommended using directly. But that looks a bit like a trick 😂Quoted 5 lines
Quoted 5 lines
Quoted 5 lines
Quoted 2 lines
Thanks,
ZorroHey Zorro, I agree with your points that version might not be the best indicator esp for downstream software, but at this point I'm unsure what's the workaround.
Quoted 5 lines
Hi Ojaswin, I don't have better workaround than
require_fio_versionfor now. I mean:
1) name_require_fio_versionas__require_fio_version, to mark it's an internal function of another common function.
2) only call__require_fio_versionin_require_fio_atomic_writesfor now, don't use it in any test cases directly.
Got it, I'll make this change, thanks
Quoted 13 lines
Quoted 13 lines
Quoted 13 lines
Quoted 12 lines
One thing that comes to mind is to let fio do the atomic write and use the tracepoints to confirm if
RWF_ATOMICwas passed, but that adds a lot of dependency on tracing framework being present (im unsure if something like this is used somewhere in xfstests before). Further it's messy to figure out that out of all the IO fio command will do, which one to check forRWF_ATOMIC.It can be done I suppose but is this sort of complexity something we want to add is the question. Or do we just go ahead with the version check.
Quoted 7 lines
Quoted 5 lines
Quoted 3 lines
I think that just checking the version is fine for this specific feature. But I still also think that versioning should be hidden from the end user, i.e. we should provide a helper like
_require_fio_atomic_writesSure, I'm okay. @Zorro, does that sound okay to you?
Sure, that's what I tried to say as above, sorry if I made you misunderstand :)
Right I was just confirming. Thanks for the review :)
Regards,
ojaswin
Quoted 9 lines
Thanks,
ZorroQuoted 4 lines
Quoted 3 lines
thanks,
John