Skip LSan leak check in 02435_rollback_cancelled_queries by groeneai · Pull Request #105474 · ClickHouse/ClickHouse · GitHub
Skip to content

Skip LSan leak check in 02435_rollback_cancelled_queries#105474

Open
groeneai wants to merge 5 commits into
ClickHouse:masterfrom
groeneai:groeneai/fix-02435-lsan-ptrace-spurious-stderr
Open

Skip LSan leak check in 02435_rollback_cancelled_queries#105474
groeneai wants to merge 5 commits into
ClickHouse:masterfrom
groeneai:groeneai/fix-02435-lsan-ptrace-spurious-stderr

Conversation

@groeneai

@groeneai groeneai commented May 20, 2026

Copy link
Copy Markdown
Contributor

Changelog category (leave one):

  • CI Fix or Improvement (changelog entry is not required)

Changelog entry (a user-readable short description of the changes that goes into CHANGELOG.md):

Fix spurious Stateless tests (arm_asan_ubsan, targeted) failures of 02435_rollback_cancelled_queries caused by LeakSanitizer at-exit ptrace warnings.

Description

02435_rollback_cancelled_queries intermittently fails the Stateless tests (arm_asan_ubsan, targeted) 50x flaky runner with a fatal-looking stderr line even though stdout is correct (1000000, 0):

==NNNN==WARNING: ptrace appears to be blocked (is seccomp enabled?). LeakSanitizer may hang.
==NNNN==Child exited with signal 36.

The "is seccomp enabled?" text is LSan's own guess and is misleading here. CIDB shows the warning fires for this one test only out of ~7000 tests on the same LSan-enabled runner, so a global ptrace/seccomp block is ruled out. The cause is self-inflicted by the test:

  • LSan's at-exit leak scan (sanitizer_stoptheworld_linux_libcdep.cpp, TestPTrace) forks a probe child that internal_fork()s from clickhouse-client and inherits its argv, so the probe's /proc/<pid>/cmdline still contains $TEST_MARK.
  • thread_cancel greps /proc/*/cmdline for $TEST_MARK and sends INT/KILL to every match, so it can hit the probe fork mid-ptrace.
  • The signal-killed probe makes LSan print the warning to the per-PID ASAN_OPTIONS log_path file, which clickhouse-test reports as a fatal stderr and fails the run.

Setting LSAN_OPTIONS=detect_leaks=0 at the top of the test skips the at-exit scan. The test exercises INSERT cancellation and transaction rollback, not leak detection, so this does not weaken what the test checks.

This is the second of three chronic flaky tests requested in #104648 . 01563_distributed_query_finish is addressed in #105457 ; 02345_implicit_transaction (Timeout exceeded while flushing system log) needs a server-side change and will land separately.

The test intentionally SIGKILLs the spawned `clickhouse-client` mid-INSERT
to exercise transaction rollback under query cancellation. When LSan's
at-exit leak check is enabled (e.g. `arm_asan_ubsan` runner), it forks a
ptrace tracer for stop-the-world. On runners where `ptrace` is sandboxed
by seccomp the tracer cannot attach, falls back to `SIGRTMIN+2`, and
prints `ptrace appears to be blocked ... Child exited with signal 36` to
stderr. The CI framework's `stderr-fatal` watch then fails the test even
though stdout is correct (`1000000`, `0`).

Set `LSAN_OPTIONS=detect_leaks=0` at the top of the test so the spawned
clients skip the at-exit leak scan. Doing leak detection on a process we
are killing on purpose is not useful.

Tracking issue: rschu1ze directive on
ClickHouse#104648 (one of three chronic
flaky tests). The other two (`01563_distributed_query_finish` and
`02345_implicit_transaction`) are addressed elsewhere; see PR body.
@groeneai

Copy link
Copy Markdown
Contributor Author

@groeneai

Copy link
Copy Markdown
Contributor Author

cc @rschu1ze (you asked for this in #104648) and @Algunenano (sibling PR #105457 for 01563 in the same bundle) — could you review? It is a one-line test-only fix to skip LSan's at-exit leak scan in a test that intentionally SIGKILLs the spawned client mid-INSERT, which on the targeted arm_asan_ubsan runner trips the seccomp ptrace block.

@alexey-milovidov alexey-milovidov added the can be tested Allows running workflows for external contributors label May 21, 2026
@clickhouse-gh

clickhouse-gh Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor

Workflow [PR], commit [c22aca1]

Summary:


AI Review

Summary

This PR narrows the LSAN_OPTIONS=detect_leaks=0 workaround to the thread_insert subshell in 02435_rollback_cancelled_queries. That matches the only clients whose query_id contains $TEST_MARK while thread_cancel is sweeping /proc/*/cmdline, so the earlier over-broad leak suppression is fixed without reintroducing the original race. I do not see a remaining correctness or coverage issue in the current shell logic.

Final Verdict
  • Status: ✅ Approve

@clickhouse-gh clickhouse-gh Bot added the pr-ci label May 21, 2026
@alexey-milovidov

Copy link
Copy Markdown
Member

What is "ptrace tracer for stop-the-world" and how can it detect KILL?

@groeneai

Copy link
Copy Markdown
Contributor Author

1. What is "ptrace tracer for stop-the-world"?

At process exit, the LSan runtime forks a helper child. The child calls ptrace(PTRACE_ATTACH, ...) on the parent, suspends every thread, walks the heap and live roots to find unreachable allocations, then detaches. The scan must observe a frozen address space, so the design is a GC-style stop-the-world phase. Implementation: compiler-rt/lib/lsan/lsan_thread.cpp plus sanitizer_common/sanitizer_stoptheworld_linux_libcdep.cpp.

2. How can it detect KILL?

It cannot, and the fix is not aimed at the SIGKILLed client. That process is reaped by the kernel instantly, its at-exit handler never runs, so LSan never spins up on it.

The spurious failure is on the OTHER client subprocesses the test spawns: the post-kill verification queries (SELECT count() FROM ... that confirm rollback). Those exit cleanly, their at-exit __lsan::DoLeakCheck() runs, LSan forks the tracer, ptrace(PTRACE_ATTACH) returns EPERM because seccomp on the arm_asan_ubsan, targeted runner blocks it, LSan falls back to SIGRTMIN+2 to suspend threads, that path is also blocked, and the tracer child exits with signal 36 (SIGRTMIN+2) after printing ptrace appears to be blocked (is seccomp enabled?). Praktika's stderr-fatal watcher then fails the test even though every stdout is correct.

So the description's "is racy here" wording is misleading. The LSan output we silence comes from cleanly-exiting verification clients, not from the killed one. LSAN_OPTIONS=detect_leaks=0 skips the at-exit scan on every client spawned by this test, which is acceptable because the test does not check for leaks. I will tighten the code comment in the next push to reflect this.

Reflect the clarification from the PR thread: the spurious stderr comes
from the cleanly-exiting verification clients running at-exit
DoLeakCheck, not from the SIGKILLed insert client whose at-exit handler
never runs.
@alexey-milovidov

Copy link
Copy Markdown
Member

@groeneai Are you sure we use seccomp in the CI? I don't remember that.

@alexey-milovidov

Copy link
Copy Markdown
Member

And even if we do, how then is LSan supposed to work in other tests?

@groeneai

Copy link
Copy Markdown
Contributor Author

@alexey-milovidov fair pushback, more careful answer:

On "seccomp". It was my best guess from LSan's own diagnostic text. Source path: contrib/llvm-project/compiler-rt/lib/sanitizer_common/sanitizer_stoptheworld_linux_libcdep.cpp:411-470, TestPTrace:

int pid = internal_fork();
if (pid == 0) {
  internal_ptrace(PTRACE_ATTACH, 0, nullptr, nullptr);
  internal__exit(0);
} else {
  internal_waitpid(pid, &wstatus, 0);
  if (WIFSIGNALED(wstatus)) {
    VReport(0, "WARNING: ptrace appears to be blocked (is seccomp enabled?). LeakSanitizer may hang.\n");
    VReport(0, "Child exited with signal %d.\n", WTERMSIG(wstatus));
  }
}

The message fires whenever the fork is signal-killed during the ptrace syscall. SCMP_ACT_KILL_PROCESS is the canonical trigger and the message text bakes that in, but any LSM, signal handler, or process that signal-kills the child during ptrace produces the same output. LSan never inspects the actual seccomp filter.

On "how does LSan work in other tests". CIDB hits for this exact warning across all CI over the last 30 days:

test_name                              check_name                                  count
02435_rollback_cancelled_queries       Stateless tests (arm_asan_ubsan, targeted)  18

That is the full list. The runner did ~11.9M test invocations across 7,133 distinct tests in the same window, all ASan+LSan. So whatever the runner does to ptrace surfaces only through this one test.

The 02435-specific route through TestPTrace is the signal-handler exit path. thread_cancel sends SIGINT to running clickhouse-client subprocesses mid-insert. interruptSignalHandler -> safeExit calls __lsan_do_leak_check() early under ASan (base/base/safeExit.cpp:27), which runs LockStuffAndStopTheWorld -> StopTheWorld -> TestPTrace from signal context. Cleanly-exiting clients reach DoLeakCheck from atexit too, but in a consistent address-space state. The signal-handler path is what differs.

There is also a mechanical wrinkle. TestPTrace's fork inherits the parent's argv, so /proc/<fork-pid>/cmdline still matches $TEST_MARK. thread_cancel's grep $TEST_MARK /proc/*/cmdline | kill -s INT/KILL ... will pick it up. So "the test kills its own probe fork mid-ptrace" is the most likely explanation for the signal-kill rather than seccomp. The failure log reports signal 36 = SIGRTMIN+2, which is not what thread_cancel sends directly, so the exact signal source on the fork is still uncertain (possibly NPTL-internal cleanup from the inherited interruptSignalHandler running on the fork before it can return cleanly).

The warning lands in <stderr-fatal>.<PID> because ASAN_OPTIONS=log_path=... is inherited by LSan on Linux, and clickhouse-test reports any such per-PID file as having stderror: .... The 4.2% test failure rate on this runner (18 / 433 runs) is consistent with a race inside TestPTrace rather than a deterministic global ptrace block.

The LSAN_OPTIONS=detect_leaks=0 skip is scoped per-test via export inside the script body, so other LSan-enabled tests on this runner keep full leak detection. The test design intentionally kills its own clients mid-query, so DoLeakCheck on the half-torn-down state never produces useful output anyway.

The earlier comment attributed the LSan "ptrace appears to be blocked
(is seccomp enabled?)" stderr warning to a seccomp sandbox on the
arm_asan_ubsan runner. As discussed on the PR, that was a wrong guess:
LSan prints that text whenever its ptrace probe fork is signal-killed
during the PTRACE_ATTACH syscall, and the message bakes in the seccomp
hypothesis regardless of the real cause.

CIDB shows the warning fires for this one test only, out of ~7000 tests
on the same LSan-enabled runner, so a global ptrace/seccomp block is
ruled out. The actual mechanism is self-inflicted by the test:

  - LSan's at-exit leak scan (sanitizer_stoptheworld_linux_libcdep.cpp,
    TestPTrace) forks a probe child that internal_fork()s from the
    clickhouse-client and therefore inherits the client's argv. Its
    /proc/<pid>/cmdline still contains $TEST_MARK.
  - thread_cancel greps /proc/*/cmdline for $TEST_MARK and sends
    INT/KILL to every match, so it can hit the probe fork mid-ptrace.
  - The signal-killed probe makes LSan print the warning to the
    per-PID ASAN_OPTIONS log_path file, which clickhouse-test reports
    as a fatal stderr and fails the run.

The fix (LSAN_OPTIONS=detect_leaks=0) is unchanged and already
approved; this only rewrites the comment to match the real cause so it
does not mislead the next reader. The test does not check for leaks.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@groeneai

Copy link
Copy Markdown
Contributor Author

Pushed 2bdf4bd: the code comment and PR description now state the real cause instead of the seccomp guess. As established above, this test is the only one of ~7000 on the LSan-enabled runner that hits the warning, so it is not a global ptrace block. The mechanism is self-inflicted: LSan's TestPTrace probe fork inherits the client's argv (so its cmdline still matches $TEST_MARK), and thread_cancel's grep $TEST_MARK /proc/*/cmdline | kill loop catches that probe mid-ptrace, which makes LSan print the fatal-looking stderr line. The fix (LSAN_OPTIONS=detect_leaks=0) is unchanged; the test does not check for leaks.

@alexey-milovidov

Copy link
Copy Markdown
Member

Updated the branch to current master (it was ~14k commits / ~3 weeks behind) with a clean merge — the net diff is still exactly the one 8-line change to 02435_rollback_cancelled_queries.sh. This re-triggers a fresh CI run on b7a18c7, which is the AI review's minimum required action (a fresh run, including the targeted Stateless tests (arm_asan_ubsan, targeted) flaky check for the modified test).

The fix is not redundant after master's recent 02435 deflaking: those commits added the long tag and adjusted timeouts, but master still does not set LSAN_OPTIONS, and thread_cancel still greps /proc/*/cmdline for $TEST_MARK and kills every match — so the inherited-argv ptrace probe race the PR describes still applies.

The reds on the previous commit 2bdf4bd6 were all chronic master flakiness / infra, not caused by this test-only change (setting LSAN_OPTIONS=detect_leaks=0 in one test's subshell cannot affect the server or other tests). Over the last 30 days on master (pull_request_number = 0), the same arm_asan_ubsan, azure runners show Server died (26×), many Segmentation fault (STID: …) / AddressSanitizer (STID: …) reports in the ASan quarantine allocator, and the Stress Hung check failed / Connection refused (463×) — all independent of this change.

Answers to the earlier questions are in the thread above: the "seccomp" wording was retracted, and the real cause (the test signalling its own inherited-argv ptrace probe fork mid-scan) is documented in both the code comment and the PR description.

Comment thread tests/queries/0_stateless/02435_rollback_cancelled_queries.sh Outdated
Only the clients started in thread_insert carry $TEST_MARK in their argv
and run while thread_cancel kills every /proc/*/cmdline match, so only they
can hit the inherited-argv ptrace probe race that produces the spurious
"ptrace appears to be blocked" stderr. The setup queries, thread_select
clients and the post-wait verification clients never match that kill loop,
so scoping the export to thread_insert keeps full LSan coverage on them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@groeneai

groeneai commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author
Pre-PR validation gate (click to expand)
# Question Answer
a Deterministic repro? Yes for the mechanism: thread_cancel greps /proc/*/cmdline for $TEST_MARK and kills every match; LSan's at-exit TestPTrace forks a probe child that inherits the client argv (so its cmdline matches $TEST_MARK), the kill loop catches it mid-ptrace, LSan prints "ptrace appears to be blocked" to the per-PID ASan log_path, and clickhouse-test reports it as fatal stderr. 18/18 CIDB hits are on this one test on arm_asan_ubsan, targeted.
b Root cause explained? The spurious stderr comes only from clients whose argv contains $TEST_MARK AND that run concurrently with thread_cancel. Those are exactly the thread_insert clients. detect_leaks=0 on just those clients removes the at-exit scan that races the kill loop.
c Fix matches root cause? Yes. Scopes detect_leaks=0 to the thread_insert subshell only, matching the set of clients that can hit the race.
d Test intent preserved / new tests added? Yes. No queries, settings, or reference output changed; stdout contract (1000000 / 0) is untouched. Setup, thread_select and final verification clients keep full LSan leak detection, so unrelated client-side leaks are still caught.
e Both directions demonstrated? The prior script-wide fix already stopped the CI failures; this change narrows scope without altering behavior on the affected clients. Verified via a shell model of the exact function/subshell layout: only thread_insert clients see detect_leaks=0; setup, thread_select, thread_cancel, and the post-wait verification all see it unset.
f Fix is general across code paths? N/A (test-only stderr scoping, not a code bug).
g Fix generalizes across inputs? N/A (test-only stderr scoping, not a code bug).
h Backward compatible? N/A (test-only stderr scoping, not a code bug).
i Invariants and contracts preserved? N/A (test-only stderr scoping, not a code bug).

Session id: cron:clickhouse-worker-slot-1:20260702-233600

@clickhouse-gh

clickhouse-gh Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

LLVM Coverage Report

Metric Baseline Current Δ
Lines 85.82% 85.83% +0.01%
Functions 91.47% 91.47% +0.00%
Branches 78.36% 78.39% +0.03%

Changed lines: No C/C++ source files changed — skipping uncovered code analysis.

Newly covered by added/modified tests: 115 line(s), 6 function(s) across 42 file(s) · Details

Top files
  • src/Processors/QueryPlan/Optimizations/optimizeJoinByShards.cpp: 15 line(s)
  • src/Functions/array/arrayUniq.cpp: 6 line(s)
  • src/Functions/h3UnidirectionalEdgeIsValid.cpp: 6 line(s)
  • src/Columns/ColumnDecimal.cpp: 4 line(s)
  • src/Disks/IO/AsynchronousBoundedReadBuffer.cpp: 4 line(s)

Full report

@groeneai

groeneai commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

CI finish ledger — c22aca1

Every failure below has an owner: a fixing PR (ours or external), or a full-effort fix task whose fixing-PR link will be posted here when it opens. Only CH Inc sync is exempt.

Check / test Reason Owner / fixing PR
(none) No failed tests. The load-bearing Stateless tests (arm_asan_ubsan, targeted) and all 4 flaky check sanitizer runs (which re-run the modified 02435_rollback_cancelled_queries 50x) passed.
Sync CH Inc sync (private, not actionable)

Session id: cron:our-pr-ci-monitor:20260703-033000

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

can be tested Allows running workflows for external contributors pr-ci

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants