#!/bin/bash
# TBR-2088 reproducer:
#   mariadbd: buf0buf.cc: buf_page_get_gen: Assertion `mode == 16 || mode == 12' failed
#   during ROLLBACK, reached from row_undo_mod_sec_is_unsafe() -> row_build() ->
#   row_ext_create() -> btr_copy_blob_prefix() on an already-freed off-page BLOB page.
#
# usage: run.sh <basedir> [threads] [seconds] [workdir] [schema.sql]
#   RR="rr record --wait --chaos" ./run.sh ...   records the server under rr;
#   the trace lands in <workdir>/rr (replay with _RR_TRACE_DIR=<workdir>/rr rr replay)
# Needs a DEBUG (UNIV_DEBUG) build - the failure is a ut_ad().
set -u
B=${1:?basedir}; TH=${2:-9}; DUR=${3:-1200}; WD=${4:-/dev/shm/tbr2088}
HERE=$(cd "$(dirname "$0")" && pwd)
SCHEMA=${5:-$HERE/setup.sql}
NT=$(grep -ci "^CREATE TABLE" "$SCHEMA")
SOCK=/tmp/$(basename "$WD").sock
RR=${RR:-}
EXTRA=${EXTRA:-}   # extra mariadbd options, e.g. EXTRA="--debug-dbug=+d,keyword"
rm -rf "$WD"; mkdir -p "$WD/tmp" "$WD/sql"
if [ -n "$RR" ]; then mkdir -p "$WD/rr"; export _RR_TRACE_DIR="$WD/rr"; fi
python3 "$HERE/gen_dml.py" "$WD/sql" "$NT" || exit 1
"$B"/scripts/mariadb-install-db --no-defaults --basedir="$B" --datadir="$WD/dd" \
  --innodb-page-size=32K --auth-root-authentication-method=normal >/dev/null 2>&1 || { echo INSTALL_FAILED; exit 1; }

# Options that matter: small buffer pool + large page size force the BLOB pages out
# of the pool, and one serialised read IO thread widens the window in which purge
# frees a BLOB page while the rollback is waiting for its S-latch.
$RR "$B"/bin/mariadbd --no-defaults --basedir="$B" --datadir="$WD/dd" --socket=$SOCK --port=0 \
  --skip-networking --tmpdir="$WD/tmp" --lc-messages-dir="$B"/share --log_output=none --skip-log-bin \
  --innodb_page_size=32K --innodb-buffer-pool-size=24M --innodb_file_per_table=1 \
  --innodb_undo_tablespaces=0 --innodb_undo_log_truncate=ON \
  --innodb_adaptive_hash_index=off --innodb_random_read_ahead=ON --innodb_stats_persistent=on \
  --innodb_rollback_on_timeout=OFF --innodb-lock-wait-timeout=50 --lock-wait-timeout=86400 \
  --innodb-use-native-aio=0 --loose-innodb-read-io-threads=1 --loose-innodb-write-io-threads=2 \
  --innodb_flush_method=fsync --loose-innodb_evict_tables_on_commit_debug=on \
  --loose-max-statement-time=30 --max-connections=500 \
  --sql_mode=STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION \
  $EXTRA > "$WD/err.log" 2>&1 &
for i in $(seq 1 300); do "$B"/bin/mariadb --socket=$SOCK -uroot -e "SELECT 1" >/dev/null 2>&1 && break; sleep 1; done
"$B"/bin/mariadb --socket=$SOCK -uroot -e "SELECT 1" >/dev/null 2>&1 || { echo NO_START; tail -6 "$WD/err.log"; exit 1; }
"$B"/bin/mariadb --socket=$SOCK -uroot test < "$SCHEMA" || exit 1

END=$(( $(date +%s) + DUR ))
for t in $(seq 1 "$TH"); do
  ( while [ "$(date +%s)" -lt $END ]; do
      "$B"/bin/mariadb --socket=$SOCK -uroot test --force < "$WD/sql/dml_$(( (t % 8) + 1 )).sql" >/dev/null 2>&1 || break
    done ) &
done
while [ "$(date +%s)" -lt $END ]; do
  grep -qaE "Assertion .* failed" "$WD/err.log" && break
  "$B"/bin/mariadb --socket=$SOCK -uroot -e "SELECT 1" >/dev/null 2>&1 || break
  sleep 3
done
sleep 2
if grep -qaE "Assertion .* failed" "$WD/err.log"; then
  echo "REPRODUCED after $(( DUR - (END - $(date +%s)) ))s"
  grep -aE "Assertion .* failed" "$WD/err.log" | head -1
  echo "core: $WD/dd/core     error log: $WD/err.log"
  [ -n "$RR" ] && echo "  rr trace: $WD/rr   replay: _RR_TRACE_DIR=$WD/rr rr replay"
  echo "  gdb -batch -ex 'bt 30' $B/bin/mariadbd $WD/dd/core"
else
  echo "NOT REPRODUCED in ${DUR}s"
fi
pkill -P $$ 2>/dev/null
"$B"/bin/mariadb-admin --socket=$SOCK -uroot shutdown >/dev/null 2>&1
