verification · part 03

RealtimeSanitizer checks the promise that a function never blocks

· english· audience: working-cpp· AI-generated, reviewed by Filip Sajdak

There is a class of C++ function that must never block. An audio callback has a few milliseconds to fill a buffer before the speaker underruns. A control loop has a deadline set by physics. An interrupt handler has one set by the hardware. In all of them a correct answer delivered late is still a failure, and the usual causes are invisible in the source: a std::vector that quietly reallocates, a std::mutex that waits, a log line that touches the filesystem.

AddressSanitizer and friends cannot help here, because nothing is wrong in the memory-safety sense. The code is correct. It is just not allowed to do what it is doing.

RealtimeSanitizer (Clang, -fsanitize=realtime) checks exactly that. You mark a function with [[clang::nonblocking]], which is a promise that it will not call anything that might block, and RTSan verifies the promise at runtime with interposed allocator and locking primitives. A violation is reported the way ASan reports a bad access: a named error and a full stack.

The demo’s audio_callback is marked [[clang::nonblocking]] and does two ordinary things: a push_back on an empty vector and a lock_guard. Both are reported. The first is the interesting one, because nothing in the line buf.push_back(1.0f) looks like an allocation:

==1==ERROR: RealtimeSanitizer: unsafe-library-call
Intercepted call to real-time unsafe function `malloc` in real-time context!
    #0 ... in malloc
    #1 ... in operator new(unsigned long)
    #2 ... in std::__new_allocator<float>::allocate(unsigned long, void const*)
    ...
    #6 ... in std::vector<float>::_M_realloc_append<float>(float&&)

The stack walks from the intercepted malloc straight back to the push_back that caused it. That is the whole value proposition: the tool finds the allocation through six layers of standard-library inlining, in code where a human reviewer sees only a container append.

What it catches, and what it costs

RTSan interposes the things that block in practice: malloc and free and everything built on them, mutex operations, thread creation and joining, file and socket syscalls, and sleep. The annotation is part of Clang’s function-effects analysis, and it comes in two strengths: [[clang::nonblocking]] for “must not block” and [[clang::nonallocating]] for the narrower “must not allocate.”

Two practical notes. It is a Clang feature (the demo above runs on clang trunk, not GCC), and it is a runtime check, so it only reports the violations your test actually executes. That makes it a natural fit for the test suite from episode 1: run your realtime paths under RTSan in CI, and a newly introduced allocation fails a test instead of surfacing as an intermittent glitch a user reports months later.

The demo sets halt_on_error=false so it can keep running and exit cleanly in the browser. In a real build you want the default, which stops at the first violation.

The pattern so far

Each layer of a verification setup checks a different kind of promise. Unit tests check the cases you thought of. The sanitizers check that the language’s rules were not broken. RTSan checks a promise the type system has no way to express: not what this function computes, but what it is allowed to do on the way there.

Next in the series: what the compiler already tells you about performance, without running anything at all.


Sources: Clang RealtimeSanitizer documentation · Clang function effect analysis (nonblocking/nonallocating).