test(sharding): add daemon shutdown deadlock test#4619
Conversation
clt❌ CLT tests in Failed tests:🔧 Edit failed tests in UI:
test/clt-tests/sharding/rollback/sequential-node-failures-high-rf.rec––– input –––
export INSTANCE=1
––– output –––
OK
––– input –––
mkdir -p /var/{run,lib,log}/manticore-${INSTANCE}
––– output –––
OK
––– input –––
stdbuf -oL searchd -c test/clt-tests/base/searchd-with-flexible-ports.conf > /dev/null
––– output –––
OK
––– input –––
if timeout 30 grep -qm1 '\[BUDDY\] started' <(tail -n 1000 -f /var/log/manticore-${INSTANCE}/searchd.log); then echo 'Buddy started!'; else echo 'Timeout or failed!'; cat /var/log/manticore-${INSTANCE}/searchd.log; fi
––– output –––
OK
––– input –––
export INSTANCE=2
––– output –––
OK
––– input –––
mkdir -p /var/{run,lib,log}/manticore-${INSTANCE}
––– output –––
OK
––– input –––
stdbuf -oL searchd -c test/clt-tests/base/searchd-with-flexible-ports.conf > /dev/null
––– output –––
OK
––– input –––
if timeout 30 grep -qm1 '\[BUDDY\] started' <(tail -n 1000 -f /var/log/manticore-${INSTANCE}/searchd.log); then echo 'Buddy started!'; else echo 'Timeout or failed!'; cat /var/log/manticore-${INSTANCE}/searchd.log; fi
––– output –––
OK
––– input –––
export INSTANCE=3
––– output –––
OK
––– input –––
mkdir -p /var/{run,lib,log}/manticore-${INSTANCE}
––– output –––
OK
––– input –––
stdbuf -oL searchd -c test/clt-tests/base/searchd-with-flexible-ports.conf > /dev/null
––– output –––
OK
––– input –––
if timeout 30 grep -qm1 '\[BUDDY\] started' <(tail -n 1000 -f /var/log/manticore-${INSTANCE}/searchd.log); then echo 'Buddy started!'; else echo 'Timeout or failed!'; cat /var/log/manticore-${INSTANCE}/searchd.log; fi
––– output –––
OK
––– input –––
export INSTANCE=4
––– output –––
OK
––– input –––
mkdir -p /var/{run,lib,log}/manticore-${INSTANCE}
––– output –––
OK
––– input –––
stdbuf -oL searchd -c test/clt-tests/base/searchd-with-flexible-ports.conf > /dev/null
––– output –––
OK
––– input –––
if timeout 30 grep -qm1 '\[BUDDY\] started' <(tail -n 1000 -f /var/log/manticore-${INSTANCE}/searchd.log); then echo 'Buddy started!'; else echo 'Timeout or failed!'; cat /var/log/manticore-${INSTANCE}/searchd.log; fi
––– output –––
OK
––– input –––
export CLUSTER_NAME=c TABLE_NAME=t
––– output –––
OK
––– input –––
mysql -h0 -P1306 -e "create cluster ${CLUSTER_NAME}"
––– output –––
OK
––– input –––
mysql -h0 -P1306 -e "show status like 'cluster_${CLUSTER_NAME}_status'\G"
––– output –––
OK
––– input –––
for n in `seq 2 $INSTANCE`; do mysql -h0 -P${n}306 -e "join cluster ${CLUSTER_NAME} at '127.0.0.1:1312'"; done;
––– output –––
OK
––– input –––
mysql -h0 -P${INSTANCE}306 -e "show status like 'cluster_${CLUSTER_NAME}_status'\G"
––– output –––
OK
––– input –––
show_shards() {
# Capture the whole SHOW CREATE TABLE output: the render is multi-line, so the agent=
# clauses live on a later line than "Create Table:" — filtering to that line drops them.
shards=$(mysql -h0 -P$1 -e "show create table $2 option force=1\G")
(
# Handle local shards
echo "$shards" | grep -oP "local='[^']*'" | sed "s/local='//g; s/'//g" | tr ',' '\n' | while read -r tbl; do
[ -n "$tbl" ] && echo "$tbl: local"
done
# Handle agent shards. Strip the [retry_count=...,ha_strategy=...] options suffix so all
# mirrors of a shard group under the same table name instead of a polluted awk key.
echo "$shards" | grep -oP "agent='[^']*'" | sed "s/agent='//g; s/'//g; s/\[[^]]*\]//g" | tr '|' '\n' | awk -F: '{agents[$3] = agents[$3] ? agents[$3] " " $1 ":" $2 : $1 ":" $2} END {for (t in agents) print t ": " agents[t]}'
) | sort
}
––– output –––
OK
––– input –––
check_cluster_status() {
mysql -h0 -P$1 -e "SHOW STATUS LIKE 'cluster_${2}_status'\\G" | grep -q "Value: primary" && echo "OK" || echo "FAIL"
}
––– output –––
OK
––– input –––
mysql -h0 -P1306 -e "CREATE TABLE ${CLUSTER_NAME}:${TABLE_NAME} (id bigint, value string) shards='3' rf='3'"; echo $?
––– output –––
- 0
+ ERROR 1064 (42000) at line 1: Waiting timeout exceeded.
+ 1
––– input –––
mysql -h0 -P1306 -e "INSERT INTO ${TABLE_NAME} (id, value) VALUES (1, 'Widget A'), (2, 'Widget B'), (3, 'Widget C')"; echo $?
––– output –––
OK
––– input –––
show_shards 1306 "$TABLE_NAME"
––– output –––
OK
––– input –––
export INSTANCE=1; stdbuf -oL searchd --stopwait -c test/clt-tests/base/searchd-with-flexible-ports.conf > /dev/null; echo "Node 1 killed"
––– output –––
OK
––– input –––
timeout 30 bash -c 'while lsof -i :${INSTANCE}306 &>/dev/null; do sleep 1; done'
––– output –––
OK
––– input –––
timeout 60 grep -qm1 'Rebalancing completed' <(tail -n 100 -f /var/log/manticore-2/searchd.log 2>/dev/null); echo $?
––– output –––
OK
––– input –––
for i in 2 3 4; do check_cluster_status ${i}306 "$CLUSTER_NAME"; done | uniq
––– output –––
OK
––– input –––
show_shards 2306 "$TABLE_NAME"
––– output –––
OK
––– input –––
mysql --no-defaults -h0 -P2306 -sN -e "SELECT COUNT(*) FROM ${TABLE_NAME}"
––– output –––
OK
––– input –––
export INSTANCE=2; stdbuf -oL searchd --stopwait -c test/clt-tests/base/searchd-with-flexible-ports.conf > /dev/null; echo "Node 2 killed"
––– output –––
OK
––– input –––
timeout 30 bash -c 'while lsof -i :${INSTANCE}306 &>/dev/null; do sleep 1; done'
––– output –––
OK
––– input –––
timeout 20 grep -qm1 'Skipping rebalance for table' <(tail -n 100 -f /var/log/manticore-{3,4}/searchd.log 2>/dev/null); echo $?
––– output –––
OK
––– input –––
for i in 3 4; do check_cluster_status ${i}306 "$CLUSTER_NAME"; done | uniq
––– output –––
OK
––– input –––
mysql --no-defaults -h0 -P3306 -sN -e "SELECT COUNT(*) FROM ${TABLE_NAME}"
––– output –––
OK
––– input –––
truncate -s 0 /var/log/manticore-3/searchd.log
––– output –––
OK
––– input –––
export INSTANCE=1
––– output –––
OK
––– input –––
mkdir -p /var/{run,lib,log}/manticore-${INSTANCE}
––– output –––
OK
––– input –––
stdbuf -oL searchd -c test/clt-tests/base/searchd-with-flexible-ports.conf > /dev/null
––– output –––
OK
––– input –––
if timeout 30 grep -qm1 '\[BUDDY\] started' <(tail -n 1000 -f /var/log/manticore-${INSTANCE}/searchd.log); then echo 'Buddy started!'; else echo 'Timeout or failed!'; cat /var/log/manticore-${INSTANCE}/searchd.log; fi
––– output –––
OK
––– input –––
export INSTANCE=2
––– output –––
OK
––– input –––
mkdir -p /var/{run,lib,log}/manticore-${INSTANCE}
––– output –––
OK
––– input –––
stdbuf -oL searchd -c test/clt-tests/base/searchd-with-flexible-ports.conf > /dev/null
––– output –––
OK
––– input –––
if timeout 30 grep -qm1 '\[BUDDY\] started' <(tail -n 1000 -f /var/log/manticore-${INSTANCE}/searchd.log); then echo 'Buddy started!'; else echo 'Timeout or failed!'; cat /var/log/manticore-${INSTANCE}/searchd.log; fi
––– output –––
OK
––– input –––
timeout 60 grep -qm1 'Rebalancing completed' <(tail -n 100 -F /var/log/manticore-3/searchd.log 2>/dev/null); echo $?
––– output –––
OK
––– input –––
for i in 1 2 3 4; do check_cluster_status ${i}306 "$CLUSTER_NAME"; done | uniq
––– output –––
OK
––– input –––
show_shards 1306 "$TABLE_NAME"
––– output –––
OK
––– input –––
mysql --no-defaults -h0 -P1306 -sN -e "SELECT COUNT(*) FROM ${TABLE_NAME}"
––– output –––
OKtest/clt-tests/sharding/rollback/daemon-shutdown-deadlock.rec––– input –––
export INSTANCE=1
––– output –––
OK
––– input –––
mkdir -p /var/{run,lib,log}/manticore-${INSTANCE}
––– output –––
OK
––– input –––
stdbuf -oL searchd -c test/clt-tests/base/searchd-with-flexible-ports.conf > /dev/null
––– output –––
OK
––– input –––
if timeout 30 grep -qm1 '\[BUDDY\] started' <(tail -n 1000 -f /var/log/manticore-${INSTANCE}/searchd.log); then echo 'Buddy started!'; else echo 'Timeout or failed!'; cat /var/log/manticore-${INSTANCE}/searchd.log; fi
––– output –––
OK
––– input –––
export INSTANCE=2
––– output –––
OK
––– input –––
mkdir -p /var/{run,lib,log}/manticore-${INSTANCE}
––– output –––
OK
––– input –––
stdbuf -oL searchd -c test/clt-tests/base/searchd-with-flexible-ports.conf > /dev/null
––– output –––
OK
––– input –––
if timeout 30 grep -qm1 '\[BUDDY\] started' <(tail -n 1000 -f /var/log/manticore-${INSTANCE}/searchd.log); then echo 'Buddy started!'; else echo 'Timeout or failed!'; cat /var/log/manticore-${INSTANCE}/searchd.log; fi
––– output –––
OK
––– input –––
export INSTANCE=3
––– output –––
OK
––– input –––
mkdir -p /var/{run,lib,log}/manticore-${INSTANCE}
––– output –––
OK
––– input –––
stdbuf -oL searchd -c test/clt-tests/base/searchd-with-flexible-ports.conf > /dev/null
––– output –––
OK
––– input –––
if timeout 30 grep -qm1 '\[BUDDY\] started' <(tail -n 1000 -f /var/log/manticore-${INSTANCE}/searchd.log); then echo 'Buddy started!'; else echo 'Timeout or failed!'; cat /var/log/manticore-${INSTANCE}/searchd.log; fi
––– output –––
OK
––– input –––
export CLUSTER_NAME=c TABLE_NAME=t
––– output –––
OK
––– input –––
mysql -h0 -P1306 -e "create cluster ${CLUSTER_NAME}"
––– output –––
OK
––– input –––
mysql -h0 -P1306 -e "show status like 'cluster_${CLUSTER_NAME}_status'\G"
––– output –––
OK
––– input –––
for n in `seq 2 $INSTANCE`; do mysql -h0 -P${n}306 -e "join cluster ${CLUSTER_NAME} at '127.0.0.1:1312'"; done;
––– output –––
OK
––– input –––
mysql -h0 -P${INSTANCE}306 -e "show status like 'cluster_${CLUSTER_NAME}_status'\G"
––– output –––
OK
––– input –––
show_shards() {
# Capture the whole SHOW CREATE TABLE output: the render is multi-line, so the agent=
# clauses live on a later line than "Create Table:" — filtering to that line drops them.
shards=$(mysql -h0 -P$1 -e "show create table $2 option force=1\G")
(
# Handle local shards
echo "$shards" | grep -oP "local='[^']*'" | sed "s/local='//g; s/'//g" | tr ',' '\n' | while read -r tbl; do
[ -n "$tbl" ] && echo "$tbl: local"
done
# Handle agent shards. Strip the [retry_count=...,ha_strategy=...] options suffix so all
# mirrors of a shard group under the same table name instead of a polluted awk key.
echo "$shards" | grep -oP "agent='[^']*'" | sed "s/agent='//g; s/'//g; s/\[[^]]*\]//g" | tr '|' '\n' | awk -F: '{agents[$3] = agents[$3] ? agents[$3] " " $1 ":" $2 : $1 ":" $2} END {for (t in agents) print t ": " agents[t]}'
) | sort
}
––– output –––
OK
––– input –––
check_cluster_status() {
mysql -h0 -P$1 -e "SHOW STATUS LIKE 'cluster_${2}_status'\\G" | grep -q "Value: primary" && echo "OK" || echo "FAIL"
}
––– output –––
OK
––– input –––
mysql -h0 -P1306 -e "CREATE TABLE ${CLUSTER_NAME}:${TABLE_NAME} (id bigint, account string, amount float, ts int) shards='2' rf='2'"; echo $?
––– output –––
OK
––– input –––
mysql -h0 -P1306 -e "INSERT INTO ${TABLE_NAME} (id, account, amount, ts) VALUES (1, 'ACC001', 100.50, 1000), (2, 'ACC002', 200.75, 2000), (3, 'ACC003', 150.25, 3000), (4, 'ACC001', 300.00, 4000), (5, 'ACC002', 250.50, 5000)"; echo $?
––– output –––
OK
––– input –––
export INSTANCE=1; stdbuf -oL searchd --stopwait -c test/clt-tests/base/searchd-with-flexible-ports.conf > /dev/null; echo "Node 1 killed"
––– output –––
OK
––– input –––
timeout 30 bash -c 'while lsof -i :${INSTANCE}306 &>/dev/null; do sleep 1; done'
––– output –––
OK
––– input –––
timeout 10 grep -qm1 'becoming master' <(tail -n 1000 -f /var/log/manticore-{2,3}/searchd.log 2>/dev/null); echo $?
––– output –––
OK
––– input –––
mysql -h0 -P2306 -e "INSERT INTO ${TABLE_NAME} (id, account, amount, ts) VALUES (6, 'ACC003', 175.00, 6000), (7, 'ACC001', 225.75, 7000)"; echo $?
––– output –––
OK
––– input –––
export INSTANCE=1
––– output –––
OK
––– input –––
mkdir -p /var/{run,lib,log}/manticore-${INSTANCE}
––– output –––
OK
––– input –––
stdbuf -oL searchd -c test/clt-tests/base/searchd-with-flexible-ports.conf > /dev/null
––– output –––
OK
––– input –––
if timeout 30 grep -qm1 '\[BUDDY\] started' <(tail -n 1000 -f /var/log/manticore-${INSTANCE}/searchd.log); then echo 'Buddy started!'; else echo 'Timeout or failed!'; cat /var/log/manticore-${INSTANCE}/searchd.log; fi
––– output –––
OK
––– input –––
timeout 60 bash -c 'until for i in 1 2 3; do mysql -h0 -P${i}306 -e "SHOW STATUS LIKE '"'"'cluster_c_status'"'"'\G" 2>/dev/null | grep -q "Value: primary" || exit 1; done; do sleep 1; done'; echo "rejoined=$?"
––– output –––
OK
––– input –––
export INSTANCE=2; timeout 40 searchd --stopwait -c test/clt-tests/base/searchd-with-flexible-ports.conf >/dev/null 2>&1; echo "node2_stopwait_exit=$?"
––– output –––
- node2_stopwait_exit=0
+ node2_stopwait_exit=124
––– input –––
pkill -9 searchd 2>/dev/null; sleep 1; echo done
––– output –––
OK |
Linux release test results1 210 tests 1 158 ✅ 21m 4s ⏱️ For more details on these failures, see this check. Results for commit 00092e4. |
Linux release MCL test results1 207 tests 1 204 ✅ 22m 19s ⏱️ For more details on these failures, see this check. Results for commit 00092e4. |
Linux debug test results1 210 tests 1 158 ✅ 1h 55m 0s ⏱️ For more details on these failures, see this check. Results for commit 00092e4. |
|
Script to reproduce deadlock: script.sh#!/bin/bash
# ============================================================================
# Reproducer: searchd shutdown DEADLOCK on graceful `--stopwait` during a
# sharded-table rebalance on node rejoin.
#
# The daemon's main thread enters Shutdown() -> Threads::ThreadPool_c::StopAll()
# and blocks FOREVER joining a worker thread that was never signalled to exit.
# `searchd --stopwait` therefore never returns. 100% daemon-side (no Buddy).
#
# HOW TO RUN (on a host with docker):
# IMG=ghcr.io/manticoresoftware/manticoresearch:test-kit-bdf38ac
# docker run --rm -i --cap-add=SYS_PTRACE \
# -v /path/to/manticoresearch:/work -w /work \
# "$IMG" bash -s < reproduce_deadlock.sh
#
# (/work only needs test/clt-tests/base/searchd-with-flexible-ports.conf;
# any manticoresearch checkout works.)
#
# EXIT 1 + "DEADLOCK CONFIRMED" => bug reproduced (node2 --stopwait hung).
# EXIT 0 + "no deadlock" => daemon stopped gracefully (fixed/not repro).
# ============================================================================
set -u
CONF=test/clt-tests/base/searchd-with-flexible-ports.conf
start_node() { # $1 = instance number
local I=$1; export INSTANCE=$I
mkdir -p /var/run/manticore-$I /var/lib/manticore-$I /var/log/manticore-$I
# --logreplication only sharpens timing; the deadlock does not depend on it.
stdbuf -oL searchd --logreplication -c "$CONF" >/dev/null 2>&1
timeout 40 grep -qm1 '\[BUDDY\] started' \
<(tail -n 1000 -f /var/log/manticore-$I/searchd.log 2>/dev/null) \
|| { echo "FATAL: node$I failed to start"; exit 2; }
}
is_primary() { mysql -h0 -P"$1" -e "SHOW STATUS LIKE 'cluster_c_status'\G" 2>/dev/null | grep -q "Value: primary"; }
poll_primary() { local d=$((SECONDS+60)); while [ $SECONDS -lt $d ]; do
local ok=1; for n in "$@"; do is_primary "${n}306" || ok=0; done; [ $ok = 1 ] && return 0; sleep 0.5; done; return 1; }
echo "### 1) start 3-node cluster, create RF=2 sharded table, insert"
start_node 1; start_node 2; start_node 3
mysql -h0 -P1306 -e "create cluster c" >/dev/null 2>&1
mysql -h0 -P2306 -e "join cluster c at '127.0.0.1:1312'" >/dev/null 2>&1
mysql -h0 -P3306 -e "join cluster c at '127.0.0.1:1312'" >/dev/null 2>&1
poll_primary 1 2 3 || { echo "FATAL: cluster not primary after create"; exit 2; }
mysql -h0 -P1306 -e "CREATE TABLE c:t (id bigint, account string, amount float, ts int) shards='2' rf='2'" >/dev/null 2>&1
mysql -h0 -P1306 -e "INSERT INTO t (id,account,amount,ts) VALUES (1,'A',1,1),(2,'B',2,2),(3,'C',3,3),(4,'A',4,4),(5,'B',5,5)" >/dev/null 2>&1
echo "### 2) first failure: stop node1 (this graceful stop WORKS), restart it"
export INSTANCE=1; searchd --stopwait -c "$CONF" >/dev/null 2>&1
timeout 30 bash -c 'while lsof -i :1306 &>/dev/null; do sleep 0.2; done'
timeout 15 grep -qm1 'becoming master' <(tail -n 1000 -f /var/log/manticore-2/searchd.log /var/log/manticore-3/searchd.log 2>/dev/null)
mysql -h0 -P2306 -e "INSERT INTO t (id,account,amount,ts) VALUES (6,'C',6,6),(7,'A',7,7)" >/dev/null 2>&1
start_node 1
poll_primary 1 2 3 || echo "WARN: not all primary after node1 rejoin"
echo "### 3) second failure: graceful-stop node2 during the sharded rebalance"
echo "### healthy daemon returns in <few s; buggy daemon DEADLOCKS in StopAll()"
export INSTANCE=2
pid=$(cat /var/log/manticore-2/searchd.pid 2>/dev/null)
t0=$SECONDS
timeout 45 searchd --stopwait -c "$CONF" >/dev/null 2>&1
rc=$?
echo "node2 --stopwait exit=$rc (elapsed=$((SECONDS-t0))s) [124 = timed out = DEADLOCK]"
if [ "$rc" = 124 ]; then
echo
echo "==================== DEADLOCK CONFIRMED ===================="
echo "node2 searchd.log last line (where shutdown stalls):"
tail -1 /var/log/manticore-2/searchd.log 2>/dev/null
echo
echo "--- backtrace of the hung daemon (pid $pid) ---"
if ! command -v gdb >/dev/null; then
apt-get update -qq >/dev/null 2>&1 && DEBIAN_FRONTEND=noninteractive apt-get install -y -qq gdb >/dev/null 2>&1
fi
if command -v gdb >/dev/null && [ -n "$pid" ]; then
gdb -p "$pid" -batch -ex "set pagination off" \
-ex "thread apply all bt" 2>/dev/null \
| grep -E "^Thread |StopAll|Shutdown|clockjoin|pthread_join|do_run_one|ThreadPool_c::loop|ServiceThd|ServiceMain|TickHead|CheckSignals|^#4|^#5" \
| head -60
mkdir -p /cap 2>/dev/null && gdb -p "$pid" -batch -ex "set pagination off" -ex "thread apply all bt" 2>/dev/null > /cap/deadlock_bt.txt && chmod 777 /cap/deadlock_bt.txt 2>/dev/null
echo "(full backtrace saved to /cap/deadlock_bt.txt if /cap mounted)"
else
echo "(gdb unavailable; rerun with --cap-add=SYS_PTRACE and network for apt)"
fi
pkill -9 searchd 2>/dev/null
exit 1
else
echo "no deadlock: node2 stopped gracefully (exit $rc)"
pkill -9 searchd 2>/dev/null
exit 0
fi |
Windows test results1 181 tests 1 107 ✅ 1h 48m 35s ⏱️ For more details on these failures, see this check. Results for commit 00092e4. |
|
I see some unit test fails, while the fix fixing the CLT test. The script to reproduce attached. @klirichek can you look into it, is it valid fix? |
No description provided.