bench: add SIGABRT handler to show failed bench name #34180

pull l0rinc wants to merge 1 commits into bitcoin:master from l0rinc:l0rinc/bench-sigabort-handler changing 1 files +18 −0
  1. l0rinc commented at 11:11 am on December 30, 2025: contributor

    Problem

    When a benchmark triggers an assertion failure or abort deeper down the stack, the output provides no indication of which benchmark failed.

    Reproducer

    To reproduce, inject a failure outside the benchmarks:

     0diff --git a/src/streams.h b/src/streams.h
     1--- a/src/streams.h	(revision aa2eebdb1e8182394d44fd9719e316ea52926cab)
     2+++ b/src/streams.h	(date 1767091264182)
     3@@ -179,6 +179,7 @@
     4
     5     bool Rewind(std::optional<size_type> n = std::nullopt)
     6     {
     7+        Assert(false);
     8         // Total rewind if no size is passed
     9         if (!n) {
    10             m_read_pos = 0;
    

    and run with a filter matching multiple benchmarks:

    0rm -rfd build && cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_BENCH=ON && cmake --build build -j$(nproc) && ./build/bin/bench_bitcoin -filter='DeserializeAndCheckBlockTest|DeserializeBlockTest|PrevectorDeserialize' -sanity-check
    

    Before - the outputs provide no indication which benchmark failed:

    0streams.h:182 bool DataStream::Rewind(std::optional<size_type>): Assertion `false' failed.
    1zsh: abort      ./build/bin/bench_bitcoin  -sanity-check
    

    Fix

    This PR adds a SIGABRT signal handler that prints the currently running benchmark’s name before re-raising the signal. The name only appears on failure, not before each benchmark starts, keeping normal output clean.

    After - the benchmark name is logged, and the signal re-raised:

    0streams.h:182 bool DataStream::Rewind(std::optional<size_type>): Assertion `false' failed.
    1Benchmark failed: DeserializeAndCheckBlockTest.
    2zsh: abort      ./build/bin/bench_bitcoin  -sanity-check
    

    Considerations

    Works in both debug and release builds since it doesn’t rely on stack traces (which would require debug symbols).

  2. bench: add signal handler for SIGABRT to improve error reporting
    When a benchmark triggers an assertion failure or abort deeper down the stack, the output provides no indication of which benchmark failed.
    
    To reproduce, inject a failure outside the benchmarks:
    ```patch
    diff --git a/src/streams.h b/src/streams.h
    --- a/src/streams.h	(revision aa2eebdb1e8182394d44fd9719e316ea52926cab)
    +++ b/src/streams.h	(date 1767091264182)
    @@ -179,6 +179,7 @@
    
         bool Rewind(std::optional<size_type> n = std::nullopt)
         {
    +        Assert(false);
             // Total rewind if no size is passed
             if (!n) {
                 m_read_pos = 0;
    ```
    
    and run with a filter matching multiple benchmarks:
    ```
    rm -rfd build && cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_BENCH=ON && cmake --build build -j$(nproc) && ./build/bin/bench_bitcoin -filter='DeserializeAndCheckBlockTest|DeserializeBlockTest|PrevectorDeserialize' -sanity-check
    ```
    
    Before - the outputs provide no indication which benchmark failed:
    ```
    streams.h:182 bool DataStream::Rewind(std::optional<size_type>): Assertion `false' failed.
    zsh: abort      ./build/bin/bench_bitcoin  -sanity-check
    ```
    
    This PR adds a `SIGABRT` signal handler that prints the currently running benchmark's name before re-raising the signal.
    The name only appears on failure, not before each benchmark starts, keeping normal output clean.
    
    After - the benchmark name is logged, and the signal re-raised:
    ```
    streams.h:182 bool DataStream::Rewind(std::optional<size_type>): Assertion `false' failed.
    Benchmark failed: DeserializeAndCheckBlockTest.
    zsh: abort      ./build/bin/bench_bitcoin  -sanity-check
    ```
    
    Works in both debug and release builds since it doesn't rely on stack traces (which would require debug symbols).
    4e6d216b93
  3. DrahtBot added the label Tests on Dec 30, 2025
  4. DrahtBot commented at 11:11 am on December 30, 2025: contributor

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/34180.

    Reviews

    See the guideline for information on the review process. A summary of reviews will appear here.

  5. maflcko commented at 11:56 am on December 30, 2025: member

    This PR adds a SIGABRT signal handler that prints the currently running benchmark’s name before re-raising the signal. The name only appears on failure, not before each benchmark starts, keeping normal output clean.

    Not sure. This seems to go in the wrong direction. Ideally, each benchmark is run individually and then the test framework just responds with the name of the failing test. This is possible via #33142. I don’t care if that is written in python of cmake script, but I think we should just pick one solution instead of keeping to write verbose workarounds that aren’t really needed.

    Before - the outputs provide no indication which benchmark failed:

    This is common in all other executables that are built? Seems odd to try to fix this in the source code one-by-one. I think the normal way to approach this is to just use a debugger, such as gdb, or even just valgrind --tool=none, or link a sanitizer, etc. I don’t think there is a need to come up with a hand-rolled way for this?

  6. l0rinc commented at 9:27 pm on December 30, 2025: contributor

    The use cases differ: #33142 runs -sanity-check to catch crashes, typically in Debug or sanitizer builds where debugging tools are available, running every single benchmark:

    0...
    1tool_bench_sanity_check.py --bench=WalletLoadingDescriptors                      |  Passed  | 1 s                                                                                               
    2tool_bench_sanity_check.py --bench=WalletMigration                               |  Passed  | 2 s                                                                                               
    3tool_bench_sanity_check.py --bench=WriteBlockBench                               |  Passed  | 0 s                                                                                               
    4tool_bench_sanity_check.py --bench=DeserializeAndCheckBlockTest                  |  Failed  | 0 s                                                                                               
    5tool_bench_sanity_check.py --bench=DeserializeBlockTest                          |  Failed  | 0 s                                                                                               
    6tool_bench_sanity_check.py --bench=PrevectorDeserializeNontrivial                |  Failed  | 0 s                                                                                               
    7tool_bench_sanity_check.py --bench=PrevectorDeserializeTrivial                   |  Failed  | 0 s                                                                                               
    

    But actual benchmarking requires Release build for meaningful results, and that’s where a crash becomes harder to diagnose - no debug symbols, aggressive inlining obscures stack traces, and re-running with sanitizers changes timing characteristics enough that some bugs may not reproduce.

    When bench_bitcoin -filter='Foo|Bar|Baz' crashes in Release, the signal handler gives you immediate triage: “it was Bar” - so you know which benchmark to isolate and re-run in Debug.

    We can make it work without this change as well, I just bumped into this a few times, though I’ll fix it …

  7. bensig commented at 9:59 pm on December 30, 2025: contributor

    Tested on macOS - builds and works as described. Sent SIGABRT during a running benchmark and it correctly printed “Benchmark failed: AddrManAdd.”

    I’ll leave the concept discussion to more experienced reviewers.

  8. maflcko commented at 10:18 am on December 31, 2025: member

    But actual benchmarking requires Release build for meaningful results, and that’s where a crash becomes harder to diagnose - no debug symbols, aggressive inlining obscures stack traces,

    I don’t think this is true. The default build type is -O2 -g (with debug symbols) and I haven’t seen inlining remove the name of the benchmark. Also, gdb shouldn’t slow down typical benchmark code, when no breakpoints are present.

    Even with Release -O2, I am locally seeing no slowdown and the name of the benchmark in the bt in gdb.

  9. l0rinc closed this on Dec 31, 2025


github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-01-07 03:13 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me