Skip to content

[TSAN] Handling of nested pending signals by TSan results in blocked signals in user code. #134358

Open
@Michael387168

Description

@Michael387168

I have a multithreaded Linux application that heavily relies on signals. Unfortunately, ThreadSanitizer doesn't seem to support nested signals, which causes my application to hang.

I am using TSan provided with gcc and I built gcc (commit 3e0768d2ffde3f20c5baa92d33869f0c196245c4) myself in order to debug ThreadSanitizer. Unfortunately, I don't know where to find the TSan version used by gcc with this commit. This was the first time I built gcc, so I hope it's reasonably okay.

gcc -v

Using built-in specs.
COLLECT_GCC=./gcc
COLLECT_LTO_WRAPPER=/usr/local/gcc/libexec/gcc/x86_64-pc-linux-gnu/15.0.1/lto-wrapper
Target: x86_64-pc-linux-gnu
Configured with: ../configure --prefix=/usr/local/gcc --enable-languages=c,c++ --disable-bootstrap --with-dwarf2 --enable-checking=no CFLAGS='-g -O0' CXXFLAGS='-g -O0'
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 15.0.1 20250327 (experimental) (GCC)

Here's what happens:

  1. A thread receives a signal.
  2. TSan catches the signal with its sighandler() and increments TheadState::pending_signals.
  3. TSan intercepts a call made by the thread and handles the pending signal in ProcessPendingSignalsImpl().
  4. ProcessPendingSignalsImpl() decrements TheadState::pending_signals, stores the old signal mask/set in ThreadSignalContext::oldset of the thread and executes the application's signal handler for the pending signal.
  5. The application's signal calls sigsuspend(), a signal-safe function, to block the thread until it receives another signal.
  6. When the other signal sigsuspend() is waiting for was caught, TSan's sighandler() is executed again incrementing TheadState::pending_signals.
  7. Before leaving the sigsuspend() call made by the application's signal handler, TSan calls ProcessPendingSignalsImpl() to process the new pending signal.
  8. ProcessPendingSignalsImpl() decrements TheadState::pending_signals and stores again the old signal mask/set in ThreadSignalContext::oldset of the thread. Unfortunately, this overwrites the previous stored old set. The previous old set, which had most signals unblocked, is now overwritten with a set that is used by the first call to ProcessPendingSignalsImpl(). Now, when TSan returns from the nested call to ProcessPendingSignalsImpl(), it restores the old set with all signals blocked and when TSan returns from the first ProcessPendingSignalsImpl(), it again restores the same old set with all signals blocked.
  9. My application continues execution with all signals blocked, although they should be unblocked, resulting in the application hanging.

Is there a workaround for this issue or is this something that needs to be fixed in TSan in order to work?
Or am I doing something wrong?

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions