DF-2660 / trigger_df2660.sh
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | #!/bin/sh # DF-2660 baseline (stock kernel): hammer2_sync_insert() TOCTOU race. # # 3-master same-clid cluster: A/B (identical clones) hold f001..fN; C is the # pristine base (missing all of them). On merge, C's sync thread (idx=2, # quorum 2-of-3 reachable among A,B on stock) walks its insert backlog: # hammer2_sync_insert() synchro.c:741-758 # unlock parent(shared) -> lock EX -> re-lookup key -> KKASSERT(chain==NULL) # The scan ran with the inode unlocked and the parent shared, so a VOP create # that inserts the same dirent key on C between the scan and the re-lookup # makes the assert fire: panic on INVARIANTS kernels. # # Race choreography (attempt lessons): # a/e: m2/m3 subdirs hidden under the mounted $M -> M2/M3 failed with # mount(8) usage-error 64 (indistinguishable from EBUSY by rc) -> # NO cluster at all. Use separate top-level mountpoints + verify the # merge via the h2nod-<label> census. # a: full-speed hammer from M3-3s -- the sync thread's ipcluster collect # catches an A/B root-mtid skew -> EINPROGRESS -> 60s h2coll park; # pass never ran. # d: 0.1s/pair throttle -- still 80 txn/s on the root inode, pass never ran. # e: dmesg was broken (msgbuf overflow, "Cannot allocate memory") -- use # /var/log/messages for backlog detection. # => hammer WAITS (quiescent) until syslog shows the first 'insert rec' # (pass entered, backlog running), then unleashes 6 full-speed loops to # race the rest of the backlog. # # usage: trigger_df2660.sh <attempt-label> <vn-base> e.g. f 0 D=/root/poc/df2660 mkdir -p $D sysctl -w debug.debugger_on_panic=0 exec > $D/run_$1.log 2>&1 set -x LABEL=$1 VB=$2 V0=vn$VB V1=vn$((VB + 1)) V2=vn$((VB + 2)) N=200 M=/mnt/race_$1 M2=/mnt/racem2_$1 M3=/mnt/racem3_$1 mkdir -p $M $M2 $M3 # 0. clean slate; ABORT if a unit will not attach cleanly for v in $V0 $V1 $V2; do vnconfig -u $v 2>/dev/null; done : > $D/.probe_$1 for v in $V0 $V1 $V2; do vnconfig -c $v $D/.probe_$1 || { echo "VN_ATTACH_FAILED_$v"; touch $D/stop_$LABEL; exit 1; } vnconfig -u $v done rm -f $D/.probe_$1 # 1. base image + newfs dd if=/dev/zero of=$D/base_$1.img bs=1m count=512 status=none vnconfig -c $V0 $D/base_$1.img || { echo VN_ATTACH_FAILED_$V0; exit 1; } newfs_hammer2 -L $LABEL /dev/$V0 echo "NEWFS_RC=$?" vnconfig -u $V0 # 2. column A: f001..fN (empty files: dirent+inode chains, no data) cp $D/base_$1.img $D/A_$1.img vnconfig -c $V0 $D/A_$1.img || { echo VN_ATTACH_FAILED_$V0; exit 1; } mount -t hammer2 /dev/$V0@$LABEL $M echo "MOUNT_A_RC=$?" mount | grep -q " $M " || { echo "A_MOUNT_NOT_UP aborting"; touch $D/stop_$LABEL; exit 1; } i=1 while [ $i -le $N ]; do > $M/f$i i=$((i + 1)) done # dirent-hash order = readdir (RB key) order; the sync thread inserts in # exactly this order, so hash-order hammer sweeps CROSS the insertion # point every lap instead of hitting it 1/N-randomly. ls -f $M | grep -E '^f[0-9]+$' > $D/order_$1.txt wc -l $D/order_$1.txt sync sleep 2 umount $M echo "UMOUNT_A_RC=$?" vnconfig -u $V0 # 3. B = clone(A); C = pristine base cp $D/A_$1.img $D/B_$1.img cp $D/base_$1.img $D/C_$1.img md5 $D/A_$1.img $D/B_$1.img # 4. parked hammers: wait for GO, then full speed hloop() { while [ ! -f $D/go_$LABEL ] && [ ! -f $D/stop_$LABEL ]; do sleep 0.2; done # REVERSED sweep: the sync thread advances FORWARD through the same # hash order, so a backward-sweeping hammer meets the insertion # frontier head-on (trailing sweeps never collide -- attempt f/g/h). for j in $(tail -r $D/order_$LABEL.txt); do [ -f $D/stop_$LABEL ] && break rm -f $M/$j echo x > $M/$j done echo "HLOOP$1_SWEEP_DONE $(date +%s)" >&2 while [ ! -f $D/stop_$LABEL ]; do sleep 0.5; done } sysctl vfs.hammer2.debug=32768 vnconfig -c $V0 $D/A_$1.img vnconfig -c $V1 $D/B_$1.img vnconfig -c $V2 $D/C_$1.img mount -t hammer2 /dev/$V0@$LABEL $M echo "CLUSTER_M1_RC=$?" mount | grep -q " $M " || { echo "CLUSTER_M1_NOT_UP aborting"; touch $D/stop_$LABEL; exit 1; } mount -t hammer2 /dev/$V1@$LABEL $M2 echo "CLUSTER_M2_RC=$?" # EBUSY + merge mount -t hammer2 /dev/$V2@$LABEL $M3 echo "CLUSTER_M3_RC=$?" # EBUSY + merge; sync thread idx2 starts NOW sleep 2 NH=$(ps axlw | grep "h2nod-$LABEL" | grep -v grep | wc -l) echo "H2NOD_COUNT=$NH (need >=3 sync threads for a real 3-master cluster)" [ "$NH" -ge 3 ] || { echo "NO_SYNC_THREADS aborting"; touch $D/stop_$LABEL; exit 1; } ps axlw | grep "h2nod-$LABEL" | grep -v grep hloop 1 & hloop 2 & hloop 3 & hloop 4 & hloop 5 & hloop 6 & # 5. wait for the backlog to START (quiescent entry), then unleash. # /var/log/messages carries the kern prints (dmesg msgbuf is saturated). i=0 while [ $i -lt 150 ]; do if grep -q "sync_slaves pfs $LABEL" /var/log/messages 2>/dev/null; then echo "PASS_SEEN at $(date +%s) poll=$i -- UNLEASHING" touch $D/go_$LABEL break fi sleep 0.2 i=$((i + 1)) done [ -f $D/go_$LABEL ] || { echo "NO_PASS_SEEN"; touch $D/stop_$LABEL; exit 1; } # 6. race window: up to 15s of full-speed hammering against the backlog sleep 15 touch $D/stop_$LABEL sleep 2 n=$(grep -c 'insert rec par=' /var/log/messages 2>/dev/null) echo "POST inserts_seen=$n files=$(ls $M 2>/dev/null | grep -c '^f')" echo "=== DF-2660 ATTEMPT $1 COMPLETE (no panic if you can read this) ===" |