# Pluginval: Real-time safety checking

**URL:** <https://forum.juce.com/t/pluginval-real-time-safety-checking/67439>\
**Category:** Audio Plugins\
**Created:** [October 29, 2025, 8:05pm UTC](https://forum.juce.com/t/pluginval-real-time-safety-checking/67439 "2025-10-29T20:05:17Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![dave96](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/dave96/32/8487_2.png) [@dave96](https://forum.juce.com/u/dave96)\
**Post date:** [October 29, 2025, 8:05pm UTC](https://forum.juce.com/t/pluginval-real-time-safety-checking/67439/1 "2025-10-29T20:05:17Z")

</div>

Hi pluginval users. I’m in the process of adding real-time safety checks to pluginval.  
macOS support is pretty much done but I’m still working on Linux (any support from proper Linux users welcome). You can see the progress and test it out for yourself on the [feature/rtcheck](https://github.com/Tracktion/pluginval/tree/refs/heads/feature/rtcheck) branch.

To use it, either select the new "Realtime check mode"s from the “Options” menu in the UI. Or from the CLI add the following: `--rtcheck enabled`.

* * *

Doing this work has brought up a couple of interesting points that are worth a bit of discussion here:

### 1. We can’t check for `mutex`es

```auto
    #define RTC_REALTIME_CONTEXT_IF_ENABLED(realtimeCheckMode, blockNum) \
      std::optional<rtc::realtime_context> rc; \
                                                                                                                \
      if (realtimeCheckMode != RealtimeCheck::disabled) \
      { \
          if (realtimeCheckMode != RealtimeCheck::relaxed || blockNum > 0) \
          { \
              rc.emplace(); \
              rtc::disable_checks_for_thread (static_cast<uint64_t>(rtc::check_flags::pthread_mutex_lock) \
                                              | static_cast<uint64_t>(rtc::check_flags::pthread_mutex_unlock)); \
          } \
      }

```

If you look at the code that turns on real-time checking, you’ll see that I have to explicitly disable checks for mutex lock/unlock. The reason for this is the `juce::CriticalSection` in `juce::AudioProcessor`.

This is kind of annoying because if this lock is used correctly and never contented, this won’t actually be a system call and would technically be real-time safe. I can’t however disable checks in juce code so have to pessimise here which unfortunately means I can’t check for locks in your own code (which you probably don’t want to do unless you really know what you’re doing).

### 2. “relaxed” mode

You’ll also notice there are three modes “disabled”/“enabled” and “relaxed”. The first two are obvious.

It’s actually quite common in audio processing for the first block to allocate resources. This could be calculating filter coefficients, queues or even initialising thread-local storage (which can allocate depending on the size and platform).

For this reason I’ve added the “relaxed” option which ignores checks for the first processing block. If you’re non-realtime safe more often than that it’s probably a good indication something might be up.

* * *

Feel free to ask any questions (or offer up any Linux help) in this thread whilst I work to get this production ready. And thanks for the support that made this happen!

---

<div class="post-metadata">

**Author:** ![dave96](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/dave96/32/8487_2.png) [@dave96](https://forum.juce.com/u/dave96)\
**Post date:** [October 29, 2025, 8:30pm UTC](https://forum.juce.com/t/pluginval-real-time-safety-checking/67439/2 "2025-10-29T20:30:14Z")

</div>

@juce\_team, one thing that did come up whilst I was trying to add this to my existing dogfood tests running the juce plugins is that they can do a fair bit of allocating. I just wanted to get your stance on if there’s any chance these might be tightened up or if you just consider them “demos” and I should remove them from my tests?

Here’s an example:

```auto
Real-time violation: intercepted call to real-time unsafe function realloc in real-time context! Stack trace:
0 librtcheck.dylib 0x0000000108292f5c _ZN3rtc14get_stacktraceEv + 80
 1 librtcheck.dylib 0x0000000108292bdc _ZN3rtc32log_function_if_realtime_contextEPKc + 300
  2 librtcheck.dylib 0x00000001082935b4 _Z44log_function_if_realtime_context_and_enabledN3rtc11check_flagsEPKc + 64
   3 librtcheck.dylib 0x0000000108293658 wrap_realloc + 36
    4 DSPModulePluginDemo 0x0000000123719ddc _ZZN4juce9HeapBlockIPNS_14MessageManager11MessageBaseELb0EE14reallocWrapperEPvmENKUlvE_clEv + 32
     5 DSPModulePluginDemo 0x0000000123719d94 _ZN4juce9HeapBlockIPNS_14MessageManager11MessageBaseELb0EE7wrapperIZNS4_14reallocWrapperEPvmEUlvE_EEPS3_mOT_ + 56
      6 DSPModulePluginDemo 0x0000000123719d50 _ZN4juce9HeapBlockIPNS_14MessageManager11MessageBaseELb0EE14reallocWrapperEPvm + 48
       7 DSPModulePluginDemo 0x0000000123719d0c _ZN4juce9HeapBlockIPNS_14MessageManager11MessageBaseELb0EE7reallocImEEvT_m + 52
        8 DSPModulePluginDemo 0x0000000123719c90 _ZN4juce9ArrayBaseIPNS_14MessageManager11MessageBaseENS_15CriticalSectionEE24setAllocatedSizeInternalEi + 40
         9 DSPModulePluginDemo 0x0000000123719c38 _ZN4juce9ArrayBaseIPNS_14MessageManager11MessageBaseENS_15CriticalSectionEE16setAllocatedSizeEi + 176
          10 DSPModulePluginDemo 0x000000012371a48c _ZN4juce9ArrayBaseIPNS_14MessageManager11MessageBaseENS_15CriticalSectionEE19ensureAllocatedSizeEi + 88
           11 DSPModulePluginDemo 0x000000012371a378 _ZN4juce9ArrayBaseIPNS_14MessageManager11MessageBaseENS_15CriticalSectionEE7addImplIJRKS3_EEEvDpOT_ + 52
            12 DSPModulePluginDemo 0x000000012371a338 _ZN4juce9ArrayBaseIPNS_14MessageManager11MessageBaseENS_15CriticalSectionEE3addERKS3_ + 32
             13 DSPModulePluginDemo 0x000000012371a2b4 _ZN4juce21ReferenceCountedArrayINS_14MessageManager11MessageBaseENS_15CriticalSectionEE3addEPS2_ + 64
              14 DSPModulePluginDemo 0x00000001236fc31c _ZN4juce12MessageQueue4postEPNS_14MessageManager11MessageBaseE + 36
               15 DSPModulePluginDemo 0x00000001236f217c _ZN4juce14MessageManager24postMessageToSystemQueueEPNS0_11MessageBaseE + 120
                16 DSPModulePluginDemo 0x00000001236f1c00 _ZN4juce14MessageManager11MessageBase4postEv + 92
                 17 DSPModulePluginDemo 0x00000001236f44fc _ZN4juce12AsyncUpdater18triggerAsyncUpdateEv + 156
                  18 DSPModulePluginDemo 0x0000000122d59924 _ZN4juce18ComponentRestarter7restartEi + 116
                   19 DSPModulePluginDemo 0x0000000122d5526c _ZN4juce22JuceVST3EditController21audioProcessorChangedEPNS_14AudioProcessorERKNS_22AudioProcessorListener13ChangeDetailsE + 776
                    20 DSPModulePluginDemo 0x00000001238445d4 _ZN4juce14AudioProcessor17updateHostDisplayERKNS_22AudioProcessorListener13ChangeDetailsE + 132
                     21 DSPModulePluginDemo 0x0000000123844540 _ZN4juce14AudioProcessor17setLatencySamplesEi + 116
                      22 DSPModulePluginDemo 0x0000000122db1b10 _ZN19DspModulePluginDemo12processBlockERN4juce11AudioBufferIfEERNS0_10MidiBufferE + 356
                       23 DSPModulePluginDemo 0x0000000122d5e5c4 _ZN4juce17JuceVST3Component12processAudioIfEEvRN9Steinberg3Vst11ProcessDataE + 532
                        24 DSPModulePluginDemo 0x0000000122d2b2b4 _ZN4juce17JuceVST3Component7processERN9Steinberg3Vst11ProcessDataE + 864
                         25 pluginval 0x0000000105293d9c _ZN4juce26VST3PluginInstanceHeadless12processAudioIfEEvRNS_11AudioBufferIT_EERNS_10MidiBufferEN9Steinberg3Vst19SymbolicSampleSizesEb + 480
                          26 pluginval 0x000000010526ebf4 _ZN4juce26VST3PluginInstanceHeadless12processBlockERNS_11AudioBufferIfEERNS_10MidiBufferE + 192
                           27 pluginval 0x0000000104f7c940 _ZN14AutomationTest7runTestER11PluginTestsRN4juce19AudioPluginInstanceE + 2028
                            28 pluginval 0x0000000104f6caa0 _ZN11PluginTests8testTypeERKN4juce17PluginDescriptionE + 2188
                             29 pluginval 0x0000000104f6c070 _ZN11PluginTests7runTestEv + 1328
                              30 pluginval 0x000000010513e040 _ZN4juce8UnitTest11performTestEPNS_14UnitTestRunnerE + 148
                               31 pluginval 0x000000010513ed44 _ZN4juce14UnitTestRunner8runTestsERKNS_5ArrayIPNS_8UnitTestENS_20DummyCriticalSectionELi0EEEx + 424
                                32 pluginval 0x0000000104f96c00 _Z8runTestsR11PluginTestsNSt3__18functionIFvRKN4juce6StringEEEE + 228
                                 33 pluginval 0x0000000104f95f48 _Z8validateRKN4juce6StringEN11PluginTests7OptionsENSt3__18functionIFvS2_EEE + 156
                                  34 pluginval 0x0000000104f95c0c _ZN14AsyncValidator3runEv + 156
                                   35 pluginval 0x0000000104f95b64 _ZZN14AsyncValidatorC1ERKN4juce6StringEN11PluginTests7OptionsENSt3__18functionIFvS1_EEENS7_IFvS1_jEEENS7_IFvS3_EEEENKUlvE_clEv + 28
                                    36 pluginval 0x0000000104f95b14 _ZNSt3 __18__ invokeB8ne180100IZN14AsyncValidatorC1ERKN4juce6StringEN11PluginTests7OptionsENS_8functionIFvS3_EEENS8_IFvS3_jEEENS8_IFvS5_EEEEUlvE_JEEEDTclclsr3stdE7declvalIT_EEspclsr3stdE7declvalIT0_EEEEOSG_DpOSH_ + 24
                                     37 pluginval 0x0000000104f95af0 _ZNSt3 __116__ thread_executeB8ne180100INS_10unique_ptrINS_15 __thread_structENS_14default_deleteIS2_EEEEZN14AsyncValidatorC1ERKN4juce6StringEN11PluginTests7OptionsENS_8functionIFvS8_EEENSD_IFvS8_jEEENSD_IFvSA_EEEEUlvE_JEJEEEvRNS_5tupleIJT_T0_DpT1_EEENS_15__ tuple_indicesIJXspT2_EEEE + 28
                                      38 pluginval 0x0000000104f95800 _ZNSt3 __114__ thread_proxyB8ne180100INS_5tupleIJNS_10unique_ptrINS_15__thread_structENS_14default_deleteIS3_EEEEZN14AsyncValidatorC1ERKN4juce6StringEN11PluginTests7OptionsENS_8functionIFvS9_EEENSE_IFvS9_jEEENSE_IFvSB_EEEEUlvE_EEEEEPvSN_ + 84
                                       39 libsystem_pthread.dylib 0x0000000183bbf2e4 _pthread_start + 136
                                        40 libsystem_pthread.dylib 0x0000000183bba0fc thread_start + 8

```

Here’s the relevant parts demangled:

```auto
                                juce::HeapBlock::realloc<…>(unsigned long, unsigned long) juce_HeapBlock.h:299
                              juce::ArrayBase::setAllocatedSizeInternal(int) juce_ArrayBase.h:432
                            juce::ArrayBase::setAllocatedSize(int) juce_ArrayBase.h:232
                          juce::ArrayBase::ensureAllocatedSize(int) juce_ArrayBase.h:243
                        juce::ArrayBase::addImpl<…>(juce::MessageManager::MessageBase *const &) juce_ArrayBase.h:561
                      juce::ArrayBase::add(juce::MessageManager::MessageBase *const &) juce_ArrayBase.h:273
                    juce::ReferenceCountedArray::add(juce::MessageManager::MessageBase *) juce_ReferenceCountedArray.h:369
                  juce::MessageQueue::post(juce::MessageManager::MessageBase *) juce_MessageQueue_mac.h:67
                juce::MessageManager::postMessageToSystemQueue(juce::MessageManager::MessageBase *) juce_MessageManager_mac.mm:436
              juce::MessageManager::MessageBase::post() juce_MessageManager.cpp:85
          juce::AsyncUpdater::triggerAsyncUpdate() juce_AsyncUpdater.cpp:81
juce::ComponentRestarter::restart(int) juce_VST3Common.h:1651
        juce::JuceVST3EditController::audioProcessorChanged(juce::AudioProcessor *, const juce::AudioProcessorListener::ChangeDetails &) juce_audio_plugin_client_VST3.cpp:1561
      juce::AudioProcessor::updateHostDisplay(const juce::AudioProcessorListener::ChangeDetails &) juce_AudioProcessor.cpp:435
    juce::AudioProcessor::setLatencySamples(int) juce_AudioProcessor.cpp:420
  DspModulePluginDemo::processBlock(juce::AudioBuffer<…> &, juce::MidiBuffer &) DSPModulePluginDemo.h:194

```

* * *

In the DSPModulePluginDemo specifically, the fact that updates are async and can lead to allocations is highlighted:

```auto
                                        wrap_malloc(size_t) rtcheck.cpp:287
                                      <lambda>::operator()() const juce_HeapBlock.h:356
                                    juce::HeapBlock::wrapper<…>(unsigned long, <lambda> &&) juce_HeapBlock.h:343
                                  juce::HeapBlock::mallocWrapper(unsigned long) juce_HeapBlock.h:356
                                juce::HeapBlock::allocate<…>(unsigned long, bool) juce_HeapBlock.h:288
                              juce::AudioBuffer::setSize(int, int, bool, bool, bool) juce_AudioSampleBuffer.h:442
                            juce::dsp::DelayLine::setMaximumDelayInSamples(int) juce_DelayLine.cpp:97
                          juce::dsp::DelayLine::DelayLine(int) juce_DelayLine.cpp:52
                        juce::dsp::DelayLine::DelayLine(int) juce_DelayLine.cpp:47
                      juce::dsp::Chorus::prepare(const juce::dsp::ProcessSpec &) juce_Chorus.cpp:103
<lambda>::operator()<…>(auto &, auto) const juce_ProcessorChain.h:91
                    juce::dsp::detail::forEachInTuple<…>(auto &&, auto &&, std::integer_sequence<…>) juce_ProcessorChain.h:48
                  juce::dsp::detail::forEachInTuple<…>(auto &&, auto &&) juce_ProcessorChain.h:57
                juce::dsp::ProcessorChain::prepare(const juce::dsp::ProcessSpec &) juce_ProcessorChain.h:91
              DspModulePluginDemo::prepareToPlay(double, int) DSPModulePluginDemo.h:166
            DspModulePluginDemo::update() DSPModulePluginDemo.h:833
          DspModulePluginDemo::processBlock(juce::AudioBuffer<…> &, juce::MidiBuffer &) DSPModulePluginDemo.h:187
        juce::JuceVST3Component::processAudio<…>(Steinberg::Vst::ProcessData &) juce_audio_plugin_client_VST3.cpp:3853
      juce::JuceVST3Component::process(Steinberg::Vst::ProcessData &) juce_audio_plugin_client_VST3.cpp:3731
    juce::VST3PluginInstanceHeadless::processAudio<…>(juce::AudioBuffer<…> &, juce::MidiBuffer &, Steinberg::Vst::SymbolicSampleSizes, bool) juce_VST3PluginFormatImpl.h:2500
  juce::VST3PluginInstanceHeadless::processBlock(juce::AudioBuffer<…> &, juce::MidiBuffer &) juce_VST3PluginFormatImpl.h:2415
AutomationTest::runTest(PluginTests &, juce::AudioPluginInstance &) BasicTests.cpp:440

```

Anyway, I thought you might find those findings interesting.

---

<div class="post-metadata">

**Author:** ![Rincewind](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/rincewind/32/10645_2.png) [@Rincewind](https://forum.juce.com/u/Rincewind)\
**Post date:** [October 30, 2025, 9:47am UTC](https://forum.juce.com/t/pluginval-real-time-safety-checking/67439/3 "2025-10-30T09:47:33Z")

</div>

Thank you for this amazing work!

Is there any way for the people, who are maintaining a fork of JUCE, to add a bypass around the `CriticalSection` in `AudioProcessor` so that everything else may still hit an assertion?  
If so, could this be supplied as a patch file for example?

---

<div class="post-metadata">

**Author:** ![reuk](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/reuk/32/21494_2.png) [@reuk](https://forum.juce.com/u/reuk)\
**Post date:** [October 30, 2025, 2:20pm UTC](https://forum.juce.com/t/pluginval-real-time-safety-checking/67439/4 "2025-10-30T14:20:16Z")

</div>

> [@dave96](#):
>
> any chance these might be tightened up or if you just consider them “demos” and I should remove them from my tests?

I think ideally we’d fix these up. Unfortunately, we can’t prioritise this work at the moment, as there’s a few other more serious issues that we need to fix, along with working on JUCE 9 in earnest. So, it might be best to remove these from your tests (or at least relax the tests in this area) for the time being, and revisit this once JUCE 9 is out.

---

<div class="post-metadata">

**Author:** ![benvining](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/benvining/32/9922_2.png) [@benvining](https://forum.juce.com/u/benvining)\
**Post date:** [October 30, 2025, 7:07pm UTC](https://forum.juce.com/t/pluginval-real-time-safety-checking/67439/5 "2025-10-30T19:07:38Z")

</div>

For sanitizers you can add attributes to functions to make them be ignored, I wonder if there might be a similar solution for this problem

---

<div class="post-metadata">

**Author:** ![dave96](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/dave96/32/8487_2.png) [@dave96](https://forum.juce.com/u/dave96)\
**Post date:** [October 30, 2025, 8:26pm UTC](https://forum.juce.com/t/pluginval-real-time-safety-checking/67439/6 "2025-10-30T20:26:30Z")

</div>

Unfortunately it’s not that straightforward for this as I’m using my own [rtcheck](https://github.com/Tracktion/rtcheck) library, not the RTSan with clang.  
It was quicker to do it this way and I have a few features (like being able to disable specific checks) that were required to get it validating juce plugins (with juce hosting).

But yes, if it used RTSan, JUCE could fairly safely mark those uncontested uses of the CriticalSection with attributes.

* * *

There is a way this _could_ be supported using rtcheck though and that would be for JUCE to do a

```auto
#if __has_include (<rtcheck.h>)
  #include <rtcheck.h>
  #define RTCHECK_NON_REALTIME rtc::non_realtime_context nrtc;
#else
  #define RTCHECK_NON_REALTIME 
#endif

```

But that would tie it strongly to the library.
