|
| 1 | +############################################################################ |
| 2 | +# PXB-3804 : --check-tables must not hang during B-tree descent. |
| 3 | +# |
| 4 | +# Theory (Gap 3): btr_validate_level()'s descent loop |
| 5 | +# while (level != btr_page_get_level(page)) { ... follow first node ptr ... } |
| 6 | +# has no iteration bound and no requirement that the level strictly DECREASES. |
| 7 | +# btr_descent_level_is_sane() only checks the level is in range (<= |
| 8 | +# BTR_MAX_NODE_LEVEL), not that descent makes progress. So a node pointer whose |
| 9 | +# child-page-number is corrupted to point to an in-bounds same/higher page |
| 10 | +# makes the descent loop forever (check-tables passes trx=nullptr, so |
| 11 | +# trx_is_interrupted() never breaks out). |
| 12 | +# |
| 13 | +# Repro (2-level tree): point the ROOT's leftmost node pointer's child-page |
| 14 | +# number back at the ROOT itself. Validating level 0 then descends |
| 15 | +# root -> (child = root) -> root -> ... forever. |
| 16 | +# Expectation BEFORE fix: timeout. AFTER fix: bounded descent reports the |
| 17 | +# cycle and exits non-zero. |
| 18 | +############################################################################ |
| 19 | + |
| 20 | +. inc/common.sh |
| 21 | + |
| 22 | +start_server --innodb_file_per_table |
| 23 | + |
| 24 | +mysql test <<'EOF' |
| 25 | +SET SESSION cte_max_recursion_depth = 20000; |
| 26 | +CREATE TABLE t1 (id INT PRIMARY KEY, pad VARCHAR(100)); |
| 27 | +INSERT INTO t1 |
| 28 | +WITH RECURSIVE seq(n) AS (SELECT 1 UNION ALL SELECT n + 1 FROM seq WHERE n < 5000) |
| 29 | +SELECT n, CONCAT('p', n) FROM seq; |
| 30 | +EOF |
| 31 | + |
| 32 | +xtrabackup --backup --target-dir=$topdir/backup |
| 33 | +xtrabackup --prepare --apply-log-only --target-dir=$topdir/backup |
| 34 | +IBD=$topdir/backup/test/t1.ibd |
| 35 | + |
| 36 | +PAGE_SIZE=$(get_page_size "$IBD") |
| 37 | + |
| 38 | +# ROOT page and the in-page byte offset of the leftmost node pointer's |
| 39 | +# child-page-number field. |
| 40 | +read ROOT OFF <<< "$(find_leftmost_node_ptr "$IBD")" |
| 41 | +[ "$ROOT" != "ERR" ] && [ -n "$OFF" ] || \ |
| 42 | + die "need a multi-level clustered index with node pointers ($ROOT $OFF)" |
| 43 | +vlog "root=$ROOT, leftmost node-ptr child-page field at in-page offset $OFF" |
| 44 | + |
| 45 | +# |
| 46 | +# Control: clean backup passes. |
| 47 | +# |
| 48 | +vlog "=== Control: clean --check-tables ===" |
| 49 | +cp -r $topdir/backup $topdir/backup_ctrl |
| 50 | +xtrabackup --prepare --check-tables --target-dir=$topdir/backup_ctrl 2>&1 \ |
| 51 | + | tee $topdir/ctrl.log |
| 52 | +grep -q "All table checks passed" $topdir/ctrl.log || \ |
| 53 | + die "Control: clean backup unexpectedly failed --check-tables" |
| 54 | +vlog "Control passed" |
| 55 | + |
| 56 | +# |
| 57 | +# Negative: point the root's first node pointer back at the root. |
| 58 | +# |
| 59 | +vlog "=== Negative: root node-ptr child -> root (descent self-cycle) ===" |
| 60 | +cp -r $topdir/backup $topdir/backup_bad |
| 61 | +CIBD=$topdir/backup_bad/test/t1.ibd |
| 62 | +ORIG=$(mach_read_n "$CIBD" "$ROOT" "$OFF" 4) |
| 63 | +mach_write_n "$CIBD" "$ROOT" "$OFF" "$ROOT" 4 |
| 64 | +vlog "child page $ORIG -> $ROOT" |
| 65 | + |
| 66 | +SIZE_BEFORE=$(stat -c %s "$CIBD") |
| 67 | +set +e |
| 68 | +timeout 90 $XB_BIN $XB_ARGS --prepare --check-tables \ |
| 69 | + --innodb-checksum-algorithm=none \ |
| 70 | + --target-dir=$topdir/backup_bad 2>&1 | tee $topdir/bad.log |
| 71 | +RC=${PIPESTATUS[0]} |
| 72 | +set -e |
| 73 | +SIZE_AFTER=$(stat -c %s "$CIBD") |
| 74 | +vlog "check-tables exit code: $RC" |
| 75 | + |
| 76 | +[ "$RC" -ne 124 ] || die "descent_nodeptr_cycle: --check-tables HUNG during descent" |
| 77 | +grep -qiE "Assertion failure|got signal|ut_error|ib::fatal triggered" $topdir/bad.log && \ |
| 78 | + die "descent_nodeptr_cycle: --check-tables ABORTED" |
| 79 | +[ "$SIZE_AFTER" = "$SIZE_BEFORE" ] || \ |
| 80 | + die "descent_nodeptr_cycle: .ibd grew during --check-tables ($SIZE_BEFORE -> $SIZE_AFTER)" |
| 81 | +[ "$RC" -ne 0 ] || die "descent_nodeptr_cycle: --check-tables passed a descent cycle" |
| 82 | +grep -qiE "descent|cycle|too deep|B-tree corruption|Table check failed" $topdir/bad.log || \ |
| 83 | + die "descent_nodeptr_cycle: corruption not reported" |
| 84 | + |
| 85 | +vlog "descent_nodeptr_cycle passed: descent cycle reported gracefully (rc=$RC)" |
0 commit comments