Skip to content

branch-4.0: [Enhancement](BE) Make print stack API more stable #64093#65283

Open
zclllyybb wants to merge 2 commits into
apache:branch-4.0from
zclllyybb:codex/pick-stack-api-4.0
Open

branch-4.0: [Enhancement](BE) Make print stack API more stable #64093#65283
zclllyybb wants to merge 2 commits into
apache:branch-4.0from
zclllyybb:codex/pick-stack-api-4.0

Conversation

@zclllyybb

Copy link
Copy Markdown
Contributor

Proposed changes

Backport the stack trace API stability enhancement to branch-4.0.

This PR includes:

  • #64454 / 0141aa2: add the BE stack trace HTTP API dependency
  • #64093 / dc70115: make the print stack API more stable

Validation

  • ./build.sh --be completed successfully in the branch-4.0 worktree
  • git diff --check upstream/branch-4.0..HEAD passed
  • Conflict-marker check passed

zclllyybb and others added 2 commits July 6, 2026 23:33
This PR adds a BE-side diagnostic HTTP API for collecting thread stack
traces from a live BE process:

```text
GET /api/stack_trace
GET /api/stack_trace?thread_id=<tid>[,<tid>...]
GET /api/stack_trace?mode=<DISABLED|FAST|FULL|FULL_WITH_INLINE>
GET /api/stack_trace?dwarf_location_info_mode=<DISABLED|FAST|FULL|FULL_WITH_INLINE>
GET /api/stack_trace?timeout_ms=<ms>
GET /api/stack_trace?skip_blocking_syscalls=<true|false>
```

`thread_id` is the preferred explicit selector. `tid` is kept only as a
legacy alias.

The default full-process collection samples all BE threads without a
signal-attempt cap and does not skip blocking syscalls. This is
intentional: during real incidents, blocked worker stacks are often the
useful part of the dump. `skip_blocking_syscalls=true` remains available
as an explicit conservative mode for isolating EINTR-sensitive paths.

The output is plain text and includes:

- BE pid, diagnostic signal number, thread count, timeout, DWARF mode,
and syscall-skip mode.
- One section per thread, with TID, thread name, capture status, capture
method, frame count, frame-pointer status, and stack bounds.
- A summary line with captured, skipped, timed-out, signal-error, and
exited counts.

Example header:

```text
BE thread stack traces
pid: 96543
service_signal: 40
thread_count: 1168
timeout_ms_per_thread: 3000
dwarf_location_info_mode: fast
skip_blocking_syscalls: false
signal_handler_unwinder: frame_pointer_with_coordinator_signal_context_libunwind_fallback
```

Example execution stack captured under load:

```text
----- thread 12438 (p_normal_simple) status=ok capture_method=frame_pointer frames=32 fp_status=end_of_chain stack_bounds=... -----
    doris::CastToStringFunction::execute_impl(...)
    doris::VectorizedFnCall::execute_column_impl(...)
    doris::AggSinkLocalState::_execute_without_key(...)
    doris::AggSinkOperatorX::sink_impl(...)
    doris::DataSinkOperatorXBase::sink(...)
    doris::PipelineTask::execute(bool*)
    doris::TaskScheduler::_do_work(int)
```

Implementation notes:

- The remote-thread capture path uses a signal handler to copy only
lightweight frame-pointer/context data from the target thread.
Symbolization and fallback unwinding run outside the handler.
- The handler publishes through one process-wide slot, so HTTP
collection is serialized. The coordinator waits until the previous
handler has fully released its latch before signaling the next TID;
otherwise back-to-back captures can drop the next signal.
- Stack bounds are collected before sending any signal, because the
handler must not open `/proc`, allocate memory, or take locks while
interrupting an arbitrary BE thread.
- Frame-pointer walking validates stack-range bounds and pointer
alignment before reading frame records.
- The same PCs can be rendered with different DWARF modes, so the
StackTrace cache key includes `dwarf_location_info_mode`. This prevents
a `DISABLED` rendering from poisoning a later `FULL` request and
prevents full file/line detail from leaking into a disabled request.
- Instruction addresses are compared via `uintptr_t` instead of pointer
arithmetic on non-object instruction addresses.
- Threads that block the diagnostic signal are reported as
`status=signal_blocked` instead of being treated as ambiguous timeouts.
- This is not a global stop-the-world snapshot. Each thread is sampled
independently as the coordinator walks the TID list, so stacks are close
in time but not guaranteed to be from the exact same instant.

EINTR handling:

- Sending a diagnostic signal can interrupt blocking syscalls even when
`SA_RESTART` is used, especially for poll/select/accept-style waits.
- Doris Thrift servers previously could treat `Interrupted system call`
from `TServerSocket::acceptImpl()` as a fatal server-loop exception.
- This PR wraps BE Thrift server sockets with `ImprovedServerSocket`,
which retries only the narrow EINTR/`Interrupted system call` case for
accept/read/peek. Other transport exceptions keep the original behavior.

(cherry picked from commit 0141aa2)
Issue Number: close apache#62497

follow up apache#64454

This PR adds a safer BE print stack API based on libunwind
signal-context unwinding.

Stack unwinding may call `dl_iterate_phdr()` to find DWARF/FDE metadata.
In signal-based stack capture, jemalloc profiling, or concurrent
`dlopen()` / `dlclose()`, re-entering glibc's loader-lock path may
deadlock or become unsafe.

Route GNU libunwind's PHDR lookup through Doris' lock-free PHDR cache.

The signal handler unwinds from the kernel-provided signal context,
while libunwind reads loaded-object metadata from Doris' PHDR snapshot
instead of calling glibc `dl_iterate_phdr()` directly.

Normal code paths still use the original live `dl_iterate_phdr()`
behavior.

The research project is at
https://github.com/JoverZhang/Doris-BE-Print-Stack-API-Research

Co-authored-by: zhaochangle <zhaochangle@selectdb.com>
(cherry picked from commit dc70115)
@zclllyybb zclllyybb requested a review from morningman as a code owner July 6, 2026 17:21
@hello-stephen

Copy link
Copy Markdown
Contributor

Thank you for your contribution to Apache Doris.
Don't know what should be done next? See How to process your PR.

Please clearly describe your PR:

  1. What problem was fixed (it's best to include specific error reporting information). How it was fixed.
  2. Which behaviors were modified. What was the previous behavior, what is it now, why was it modified, and what possible impacts might there be.
  3. What features were added. Why was this function added?
  4. Which code was refactored and why was this part of the code refactored?
  5. Which functions were optimized and what is the difference before and after the optimization?

@zclllyybb

Copy link
Copy Markdown
Contributor Author

run buildall

@zclllyybb zclllyybb changed the title [branch-4.0][pick] Make print stack API more stable branch-4.0: [Enhancement](BE) Make print stack API more stable #64093 Jul 7, 2026
@hello-stephen

Copy link
Copy Markdown
Contributor

BE UT Coverage Report

Increment line coverage 71.30% (718/1007) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 53.60% (19624/36612)
Line Coverage 36.65% (183743/501351)
Region Coverage 33.22% (142862/429988)
Branch Coverage 34.07% (61773/181309)

@hello-stephen

Copy link
Copy Markdown
Contributor

BE Regression && UT Coverage Report

Increment line coverage 100% (0/0) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 71.52% (25626/35831)
Line Coverage 54.40% (271882/499771)
Region Coverage 51.99% (225374/433488)
Branch Coverage 53.35% (96965/181748)

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants