Skip to content

Commit 8a6eb33

Browse files
Attempt to catch linux hang
1 parent 56693c6 commit 8a6eb33

3 files changed

Lines changed: 122 additions & 6 deletions

File tree

.github/scripts/run_tests_with_backtrace.sh

Lines changed: 84 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
# Run a test binary under debug CI. On fatal signals, print post-mortem
33
# backtraces from core dumps when available. Linux also runs under catchsegv
44
# so a partial backtrace appears in the log even without a core file.
5+
#
6+
# When LIVEKIT_TEST_STALL_SECONDS is set to a positive integer, a watchdog
7+
# monitors test output and dumps live thread backtraces if the log goes silent
8+
# for that many seconds (integration-test hang diagnostics on linux-x64).
59
set -uo pipefail
610

711
usage() {
@@ -57,6 +61,36 @@ dump_macos_crash_reports() {
5761
fi
5862
}
5963

64+
dump_live_backtraces() {
65+
local test_pid=$1
66+
local reason=$2
67+
68+
echo "=== live backtrace diagnostics (${reason}, pid ${test_pid}) ==="
69+
70+
if [[ "$(uname -s)" == "Linux" ]]; then
71+
if command -v gdb >/dev/null 2>&1; then
72+
gdb -batch \
73+
-ex 'set pagination off' \
74+
-ex 'thread apply all bt full' \
75+
-p "${test_pid}" || true
76+
else
77+
echo "gdb not available; install gdb for live backtraces"
78+
fi
79+
return 0
80+
fi
81+
82+
if [[ "$(uname -s)" == "Darwin" ]]; then
83+
if command -v sample >/dev/null 2>&1; then
84+
sample "${test_pid}" 5 -mayDie 2>&1 || true
85+
fi
86+
if command -v lldb >/dev/null 2>&1; then
87+
lldb -p "${test_pid}" --batch -o 'thread backtrace all' -o 'detach' -o 'quit' 2>&1 || true
88+
else
89+
echo "lldb not available"
90+
fi
91+
fi
92+
}
93+
6094
dump_backtraces() {
6195
local test_pid=$1
6296
local status=$2
@@ -122,11 +156,57 @@ run_test() {
122156
fi
123157
}
124158

159+
start_stall_watchdog() {
160+
local test_pid=$1
161+
local log_file=$2
162+
local stall_limit=$3
163+
164+
(
165+
local last_size=-1
166+
local stall=0
167+
while kill -0 "${test_pid}" 2>/dev/null; do
168+
local size
169+
size=$(wc -c <"${log_file}" 2>/dev/null || echo 0)
170+
if [[ "${size}" == "${last_size}" ]]; then
171+
stall=$((stall + 5))
172+
else
173+
stall=0
174+
last_size=${size}
175+
fi
176+
if ((stall >= stall_limit)); then
177+
echo "=== TEST HANG DETECTED: no output for ${stall}s (pid ${test_pid}) ==="
178+
echo "--- last log lines ---"
179+
tail -n 40 "${log_file}" || true
180+
dump_live_backtraces "${test_pid}" "stall ${stall}s"
181+
kill -ABRT "${test_pid}" 2>/dev/null || kill -TERM "${test_pid}" 2>/dev/null || true
182+
break
183+
fi
184+
sleep 5
185+
done
186+
) &
187+
echo $!
188+
}
189+
190+
stall_limit=${LIVEKIT_TEST_STALL_SECONDS:-0}
191+
log_file="${RUNNER_TEMP:-/tmp}/livekit-test-output.log"
192+
125193
set +e
126-
run_test "$@" &
127-
test_pid=$!
128-
wait "${test_pid}"
129-
status=$?
194+
if ((stall_limit > 0)); then
195+
: >"${log_file}"
196+
run_test "$@" >"${log_file}" 2>&1 &
197+
test_pid=$!
198+
watchdog_pid=$(start_stall_watchdog "${test_pid}" "${log_file}" "${stall_limit}")
199+
wait "${test_pid}"
200+
status=$?
201+
kill "${watchdog_pid}" 2>/dev/null || true
202+
wait "${watchdog_pid}" 2>/dev/null || true
203+
cat "${log_file}"
204+
else
205+
run_test "$@" &
206+
test_pid=$!
207+
wait "${test_pid}"
208+
status=$?
209+
fi
130210
set -e
131211

132212
if ((status > 128)); then

.github/workflows/tests.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,8 @@ jobs:
191191
- os: ubuntu-latest
192192
name: linux-x64
193193
e2e-testing: true
194+
# Extra FFI logging on linux-x64 to diagnose timing-sensitive integration hangs.
195+
integration_rust_log: info,livekit_ffi::server=debug
194196
- os: ubuntu-24.04-arm
195197
name: linux-arm64
196198
e2e-testing: true
@@ -508,8 +510,11 @@ jobs:
508510
timeout-minutes: ${{ inputs.integration_timeout_minutes }}
509511
shell: bash
510512
env:
511-
RUST_LOG: ${{ inputs.integration_rust_log }}
513+
RUST_LOG: ${{ matrix.integration_rust_log || inputs.integration_rust_log }}
512514
RUST_BACKTRACE: full
515+
# linux-x64: verbose C++ logs + stall watchdog (see run_tests_with_backtrace.sh).
516+
LIVEKIT_TEST_LOG_LEVEL: ${{ matrix.name == 'linux-x64' && 'debug' || '' }}
517+
LIVEKIT_TEST_STALL_SECONDS: ${{ matrix.name == 'linux-x64' && '90' || '0' }}
513518
run: |
514519
set -euo pipefail
515520
source .token_helpers/set_data_track_test_tokens.bash

src/tests/common/test_common.h

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,37 @@ inline bool waitForParticipant(Room* room, const std::string& identity, std::chr
144144
/// once the room is torn down (or before connect). Dereferencing the result of
145145
/// lock() blindly is undefined behavior, so tests must go through this helper,
146146
/// which throws instead of crashing when the handle is expired.
147+
/// Resolve integration-test log verbosity from LIVEKIT_TEST_LOG_LEVEL (trace/debug/info/...).
148+
inline livekit::LogLevel testLogLevelFromEnv() {
149+
const char* level = std::getenv("LIVEKIT_TEST_LOG_LEVEL");
150+
if (level == nullptr || std::string(level).empty()) {
151+
return livekit::LogLevel::Info;
152+
}
153+
const std::string value(level);
154+
if (value == "trace") {
155+
return livekit::LogLevel::Trace;
156+
}
157+
if (value == "debug") {
158+
return livekit::LogLevel::Debug;
159+
}
160+
if (value == "info") {
161+
return livekit::LogLevel::Info;
162+
}
163+
if (value == "warn") {
164+
return livekit::LogLevel::Warn;
165+
}
166+
if (value == "error") {
167+
return livekit::LogLevel::Error;
168+
}
169+
if (value == "critical") {
170+
return livekit::LogLevel::Critical;
171+
}
172+
if (value == "off") {
173+
return livekit::LogLevel::Off;
174+
}
175+
return livekit::LogLevel::Info;
176+
}
177+
147178
inline std::shared_ptr<LocalParticipant> lockLocalParticipant(const Room& room) {
148179
if (auto participant = room.localParticipant().lock()) {
149180
return participant;
@@ -497,7 +528,7 @@ class StressTestStats {
497528
class LiveKitTestBase : public ::testing::Test {
498529
protected:
499530
void SetUp() override {
500-
livekit::initialize(livekit::LogLevel::Info);
531+
livekit::initialize(testLogLevelFromEnv());
501532
config_ = TestConfig::fromEnv();
502533

503534
// Tracing is controlled by compile-time macro LIVEKIT_TEST_ENABLE_TRACING

0 commit comments

Comments
 (0)