Salta ai contenuti

0019. Real-time safety checked by the compiler and in CI

Questa pagina è in inglese: in italiano ci sono per ora i tutorial e il glossario; menu, ricerca e navigazione sono in italiano.

  • Status: accepted
  • Date: 2026-10-07

Every function that runs on the audio thread must not allocate, lock, throw, do I/O or call anything that might. Until now that rule lived in comments (“noexcept, audio thread”) and in tests that count allocations on chosen paths. Comments drift, and a counting test only covers the paths it drives: a call added deep inside a helper, a std::function, a std::stable_sort that takes a temporary buffer, or a shared_ptr released on the wrong thread passes review and every test until a show glitches.

Clang 20 checks function effects at compile time: a function declared [[clang::nonblocking]] may only call functions that are themselves nonblocking, either declared so or inferred from a body the compiler can see. RealtimeSanitizer (-fsanitize=realtime, Clang 20, Linux and macOS) checks the same functions at run time and reports any allocation, lock or system call made while one of them is on the stack.

  • shuffle/base/RealTime.h defines SHUFFLE_RT: [[clang::nonblocking]] on a Clang that has function effects, nothing elsewhere. Every function that runs on the audio thread carries it after noexcept, in cores and adapters, in ports (so every implementation is checked, test fakes included) and on out-of-line definitions.
  • A call that is safe for a reason the compiler cannot see (a spin that yields while another thread is preempted, a non-blocking socket call, a shared_ptr that only moves between owners, a JUCE call that only touches the FPU) sits between SHUFFLE_RT_ALLOW_BEGIN ("reason") and SHUFFLE_RT_ALLOW_END. The reason is mandatory and the region is exempt from both the compiler and the sanitizer. A function holding such a region carries SHUFFLE_RT itself.
  • The CMake option SHUFFLE_ENGINE_RT_CHECKS turns a violation into a build error with Clang 20 or later on libstdc++ or libc++. clang-cl is left out: with the Microsoft standard library the analysis flags calls inside the library itself (algorithms compiled into its DLL) that it cannot see.
  • The linux-clang-rtsan preset builds everything with Clang 20, the checks and RealtimeSanitizer; the CI job realtime builds it and runs the engine tests ([engine]) and the allocation-counting tests ([realtime]) under the sanitizer.
  • tools/check_repo.py fails when a function documented as running on the audio thread is not marked, and when a reviewed region has no reason.
  • Real-time safety is a property the build proves for every marked function, through every call it makes, instead of a promise in a comment.
  • The first pass found and fixed real violations: a std::stable_sort that could allocate a temporary buffer on blocks with parameter changes, a MIDI port handle (and possibly a device destructor) released on the audio thread when a retirement queue was full, std::swap of structures that destroy a temporary owner on the audio thread, std::string_view calls with an exception path, an indirect call through a function pointer, and an example whose real-time MIDI consumer grew a std::vector.
  • clang-cl remains usable for the attribute but not for the checks: what it reports there lies inside the Microsoft standard library and SDK.
  • Test doubles of real-time ports are real-time code too: what they record is written inside reviewed regions.
  • MSVC and GCC builds are unchanged: the mark expands to nothing there.