Skip to content

Commit 47c68af

Browse files
authored
rtcheck - real-time safety checking (#156)
* rtcheck: Added rtcheck on macOS and added this check to the allocation test * CI: Added codesign of librtcheck.dylib * rtcheck: Added rtcheck to all processBlock calls at level 10 * Added rtcheck options with disabled/enable/relaxed modes * Update macOS version in build workflow * rtcheck: Switched to using cpm * rtcheck: Fixed a warning * rtcheck: Fixed macro when rtcheck isn't enabled * rtcheck: Add rtcheck to juce plugin tests * rtcheck: Updated docs * rtcheck: codesign the rtcheck lib * rtcheck: Use relaxed mode for juce plugin tests * rtcheck: Disable rtcheck for now to see if signing works * rtcheck: Don't run spctl * rtcheck: Pre-allocate MIDI buffers * rtcheck: Test a debug build * rtcheck: Debug juce plugin builds * rtcheck: Avoid caching * rtcheck: Try getting a core dump * rtcheck: Fixed syntax * rtcheck: Run under gdb * rtcheck: Add gdb * rtcheck: Disable rt checking * rtcheck: Disable rtcheck for Linux * rtcheck: Switch back to release * rtcheck: Try ubuntu 24 * rtcheck: Avoid checking * rtcheck: Webkit update * rtcheck: Build plugins in release * rtcheck: Enable rtcheck on Linux again * rtcheck: Test MultiOutSynthPlugin first * Don't test the juce plugin demos with rtcheck * rtcheck: Disable on Linux for now * rtcheck: Re-enabled test plugin cache
1 parent 571d7af commit 47c68af

File tree

12 files changed

+1634
-52
lines changed

12 files changed

+1634
-52
lines changed

.github/workflows/build.yaml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
os: ubuntu-latest
2222
app: pluginval
2323
test-binary: ./pluginval
24-
container: ubuntu:22.04
24+
container: ubuntu:24.04
2525
- name: macOS
2626
os: macos-15
2727
app: pluginval.app
@@ -70,7 +70,7 @@ jobs:
7070
run: |
7171
sudo add-apt-repository ppa:ubuntu-toolchain-r/test
7272
sudo apt-get update
73-
sudo apt-get install -y freeglut3-dev g++ libasound2-dev libcurl4-openssl-dev libfreetype6-dev libjack-jackd2-dev libx11-dev libxcomposite-dev libxcursor-dev libxinerama-dev libxrandr-dev mesa-common-dev ladspa-sdk webkit2gtk-4.0 libgtk-3-dev xvfb
73+
sudo apt-get install -y freeglut3-dev g++ libasound2-dev libcurl4-openssl-dev libfreetype6-dev libjack-jackd2-dev libx11-dev libxcomposite-dev libxcursor-dev libxinerama-dev libxrandr-dev mesa-common-dev ladspa-sdk libwebkit2gtk-4.1-dev xvfb gdb
7474
sudo /usr/bin/Xvfb $DISPLAY &
7575
7676
- name: Make VST2 SDK available
@@ -147,7 +147,9 @@ jobs:
147147
148148
- name: Codesign (macOS)
149149
if: ${{ matrix.name == 'macOS' }}
150-
run: codesign --force -s "${{ secrets.DEVELOPER_ID_APPLICATION}}" -v ${{ env.APP_DIR }}/${{ matrix.app }} --entitlements ${{ env.BUILD_DIR }}/${{ env.BINARY_NAME }}_artefacts/JuceLibraryCode/${{ env.BINARY_NAME }}.entitlements --deep --strict --options=runtime --timestamp
150+
run: |
151+
codesign --force -s "${{ secrets.DEVELOPER_ID_APPLICATION}}" -v ${{ env.APP_DIR }}/${{ matrix.app }}/Contents/Libraries/librtcheck.dylib --entitlements ${{ env.BUILD_DIR }}/${{ env.BINARY_NAME }}_artefacts/JuceLibraryCode/${{ env.BINARY_NAME }}.entitlements --deep --strict --options=runtime --timestamp
152+
codesign --force -s "${{ secrets.DEVELOPER_ID_APPLICATION}}" -v ${{ env.APP_DIR }}/${{ matrix.app }} --entitlements ${{ env.BUILD_DIR }}/${{ env.BINARY_NAME }}_artefacts/JuceLibraryCode/${{ env.BINARY_NAME }}.entitlements --deep --strict --options=runtime --timestamp
151153
152154
- name: "Notarize and staple (macOS)"
153155
if: ${{ matrix.name == 'macOS' }}

CMakeLists.txt

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ if (APPLE)
99

1010
# Uncomment to produce a universal binary
1111
# set(CMAKE_OSX_ARCHITECTURES arm64 x86_64)
12+
set(PLUGINVAL_ENABLE_RTCHECK ON)
13+
elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux")
14+
# Disable rtcheck on Linux for now until further testing on real Linux systems has been done
15+
# set(PLUGINVAL_ENABLE_RTCHECK ON)
1216
endif()
1317

1418
# sanitizer options, from https://github.com/sudara/cmake-includes/blob/main/Sanitizers.cmake
@@ -41,7 +45,15 @@ endif ()
4145
set_property(GLOBAL PROPERTY USE_FOLDERS YES)
4246
option(JUCE_ENABLE_MODULE_SOURCE_GROUPS "Enable Module Source Groups" ON)
4347

44-
option(PLUGINVAL_FETCH_JUCE "Fetch JUCE along with PluginVal" ON)
48+
include(cmake/CPM.cmake)
49+
CPMAddPackage("gh:Neargye/magic_enum#v0.9.7")
50+
51+
if(PLUGINVAL_ENABLE_RTCHECK)
52+
CPMAddPackage("gh:Tracktion/rtcheck#main")
53+
endif()
54+
55+
56+
option(PLUGINVAL_FETCH_JUCE "Fetch JUCE along with pluginval" ON)
4557

4658
if(PLUGINVAL_FETCH_JUCE)
4759
add_subdirectory(modules/juce)
@@ -63,7 +75,7 @@ juce_add_gui_app(pluginval
6375

6476
juce_generate_juce_header(pluginval)
6577

66-
target_compile_features(pluginval PRIVATE cxx_std_17)
78+
target_compile_features(pluginval PRIVATE cxx_std_20)
6779

6880
set_target_properties(pluginval PROPERTIES
6981
C_VISIBILITY_PRESET hidden
@@ -105,6 +117,7 @@ target_compile_definitions(pluginval PRIVATE
105117
JUCE_WEB_BROWSER=0
106118
JUCE_MODAL_LOOPS_PERMITTED=1
107119
JUCE_GUI_BASICS_INCLUDE_XHEADERS=1
120+
$<$<BOOL:${PLUGINVAL_ENABLE_RTCHECK}>:PLUGINVAL_ENABLE_RTCHECK=1>
108121
VERSION="${CURRENT_VERSION}")
109122

110123
if(MSVC AND NOT CMAKE_MSVC_RUNTIME_LIBRARY)
@@ -116,13 +129,28 @@ target_link_libraries(pluginval PRIVATE
116129
juce::juce_audio_devices
117130
juce::juce_audio_processors
118131
juce::juce_audio_utils
119-
juce::juce_recommended_warning_flags)
132+
juce::juce_recommended_warning_flags
133+
magic_enum)
120134

121135
if (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
122136
target_link_libraries(pluginval PRIVATE
123137
-static-libstdc++)
124138
endif()
125139

140+
if (PLUGINVAL_ENABLE_RTCHECK)
141+
target_link_libraries(pluginval PRIVATE
142+
rtcheck)
143+
144+
if (APPLE)
145+
add_custom_command(TARGET pluginval POST_BUILD
146+
COMMAND ${CMAKE_COMMAND} -E echo "Executable path: $<TARGET_FILE_DIR:pluginval>")
147+
add_custom_command(TARGET pluginval POST_BUILD
148+
COMMAND ${CMAKE_COMMAND} -E copy
149+
${CMAKE_CURRENT_BINARY_DIR}/_deps/rtcheck-build/src/librtcheck.dylib
150+
$<TARGET_FILE_DIR:pluginval>/../Libraries/librtcheck.dylib)
151+
endif ()
152+
endif()
153+
126154
set (cmdline_docs_out "${CMAKE_CURRENT_LIST_DIR}/docs/Command line options.md")
127155

128156
add_custom_command (OUTPUT "${cmdline_docs_out}"

Source/CommandLine.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include <unistd.h>
2323
#endif
2424

25+
#include <magic_enum/magic_enum.hpp>
2526

2627
//==============================================================================
2728
static void exitWithError (const juce::String& error)
@@ -285,6 +286,7 @@ static Option possibleOptions[] =
285286
{ "--sample-rates", true },
286287
{ "--block-sizes", true },
287288
{ "--vst3validator", true },
289+
{ "--rtcheck", false },
288290
};
289291

290292
static juce::StringArray mergeEnvironmentVariables (juce::StringArray args, std::function<juce::String (const juce::String& name, const juce::String& defaultValue)> environmentVariableProvider = [] (const juce::String& name, const juce::String& defaultValue) { return juce::SystemStats::getEnvironmentVariable (name, defaultValue); })
@@ -362,6 +364,10 @@ static juce::String getHelpMessage()
362364
<< " Sets a timout which will stop validation with an error if no output from any" << newLine
363365
<< " test has happened for this number of ms." << newLine
364366
<< " By default this is 30s but can be set to \"-1\" (must be quoted) to never timeout." << newLine
367+
<< " --rtcheck [empty, disabled, enabled or relaxed]" << newLine
368+
<< " Turns on real-time saftey checks using rtcheck (macOS and Linux only)." << newLine
369+
<< " relaxed mode doesn't run the checks for the first processing block as a lot of plugins" << newLine
370+
<< " use this to allocate or initialise thread-locals (which can allocate)" << newLine
365371
<< newLine
366372
// repeating tests
367373
<< " --repeat [num repeats]" << newLine
@@ -552,6 +558,8 @@ std::pair<juce::String, PluginTests::Options> parseCommandLine (const juce::Argu
552558
options.sampleRates = getSampleRates (args);
553559
options.blockSizes = getBlockSizes (args);
554560
options.vst3Validator = getOptionValue (args, "--vst3validator", "", "Expected a path for the --vst3validator option");
561+
options.realtimeCheck = magic_enum::enum_cast<RealtimeCheck> (getOptionValue (args, "--rtcheck", "", "Expected one of [disabled, enabled, relaxed]").toString().toStdString())
562+
.value_or (RealtimeCheck::disabled);
555563

556564
return { fileOrID, options };
557565
}
@@ -622,6 +630,12 @@ juce::StringArray createCommandLine (juce::String fileOrID, PluginTests::Options
622630
if (options.vst3Validator != juce::File())
623631
args.addArray ({ "--vst3validator", options.vst3Validator.getFullPathName().quoted() });
624632

633+
if (auto rtCheckMode = options.realtimeCheck;
634+
rtCheckMode != RealtimeCheck::disabled)
635+
{
636+
args.addArray ({ "--rtcheck", std::string (magic_enum::enum_name (rtCheckMode)) });
637+
}
638+
625639
args.addArray ({ "--validate", fileOrID });
626640

627641
return args;

Source/MainComponent.cpp

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
==============================================================================*/
1414

1515
#include "MainComponent.h"
16+
17+
#include <magic_enum/magic_enum.hpp>
18+
1619
#include "PluginTests.h"
1720

1821
//==============================================================================
@@ -114,6 +117,18 @@ namespace
114117
return getAppPreferences().getValue ("vst3validator", juce::String());
115118
}
116119

120+
void setRealtimeCheckMode (RealtimeCheck rt)
121+
{
122+
getAppPreferences().setValue ("realtimeCheckMode", juce::String (std::string (magic_enum::enum_name (rt))));
123+
}
124+
125+
RealtimeCheck getRealtimeCheckMode()
126+
{
127+
auto modeString = getAppPreferences().getValue ("realtimeCheckMode", juce::String());
128+
return magic_enum::enum_cast<RealtimeCheck> (modeString.toStdString())
129+
.value_or (RealtimeCheck::disabled);
130+
}
131+
117132
PluginTests::Options getTestOptions()
118133
{
119134
PluginTests::Options options;
@@ -127,6 +142,7 @@ namespace
127142
options.sampleRates = getSampleRates();
128143
options.blockSizes = getBlockSizes();
129144
options.vst3Validator = getVST3Validator();
145+
options.realtimeCheck = getRealtimeCheckMode();
130146

131147
return options;
132148
}
@@ -336,10 +352,25 @@ MainComponent::MainComponent (Validator& v)
336352
randomise,
337353
chooseOutputDir,
338354
showVST3Validator,
339-
showSettingsDir
355+
showSettingsDir,
356+
rtCheck
340357
};
341358

342359
juce::PopupMenu m;
360+
361+
{
362+
juce::PopupMenu rtCheckMenu;
363+
364+
for (auto currentMode = getRealtimeCheckMode();
365+
auto mode : magic_enum::enum_values<RealtimeCheck>())
366+
{
367+
rtCheckMenu.addItem (getDisplayString (mode), true, mode == currentMode,
368+
[newMode = mode] { setRealtimeCheckMode (newMode); });
369+
}
370+
371+
m.addSubMenu ("Realtime check mode", rtCheckMenu);
372+
}
373+
343374
m.addItem (validateInProcess, TRANS("Validate in process"), true, getValidateInProcess());
344375
m.addItem (showRandomSeed, TRANS("Set random seed (123)").replace ("123", "0x" + juce::String::toHexString (getRandomSeed()) + "/" + juce::String (getRandomSeed())));
345376
m.addItem (showTimeout, TRANS("Set timeout (123ms)").replace ("123",juce::String (getTimeoutMs())));

Source/PluginTests.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,25 @@
1313
==============================================================================*/
1414

1515
#include "PluginTests.h"
16+
1617
#include "TestUtilities.h"
1718
#include <random>
19+
#include <cassert>
20+
21+
juce::String getDisplayString (RealtimeCheck rtc)
22+
{
23+
if (rtc == RealtimeCheck::disabled)
24+
return "Disabled (don't check for real-time safety)";
25+
26+
if (rtc == RealtimeCheck::enabled)
27+
return "Enabled (check for real-time safety in all process calls)";
28+
29+
if (rtc == RealtimeCheck::relaxed)
30+
return "Relaxed (check for real-time safety in all but the first process call)";
31+
32+
assert(false);
33+
return {};
34+
}
1835

1936
namespace
2037
{

Source/PluginTests.h

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,17 @@
1616

1717
#include "juce_audio_processors/juce_audio_processors.h"
1818

19+
/** Determines the type of real-time checking to perform. */
20+
enum class RealtimeCheck
21+
{
22+
disabled, ///< Doesn't check for realtime safety
23+
enabled, ///< Checks for realtime safety
24+
relaxed ///< Checks for realtime safety after the first audio callback (to allow initialisation)
25+
};
26+
27+
juce::String getDisplayString (RealtimeCheck);
28+
29+
1930
//==============================================================================
2031
/**
2132
The juce::UnitTest which will create the plugins and run each of the registered tests on them.
@@ -26,20 +37,21 @@ struct PluginTests : public juce::UnitTest
2637
/** A set of options to use when running tests. */
2738
struct Options
2839
{
29-
int strictnessLevel = 5; /**< Max test level to run. */
30-
juce::int64 randomSeed = 0; /**< The seed to use for the tests, 0 signifies a randomly generated seed. */
31-
juce::int64 timeoutMs = 30000; /**< Timeout after which to kill the test. */
32-
bool verbose = false; /**< Whether or not to log additional information. */
33-
int numRepeats = 1; /**< The number of times to repeat the tests. */
34-
bool randomiseTestOrder = false; /**< Whether to randomise the order of the tests in each repeat. */
35-
bool withGUI = true; /**< Whether or not avoid tests that instantiate a gui. */
36-
juce::File dataFile; /**< juce::File which tests can use to run user provided data. */
37-
juce::File outputDir; /**< Directory in which to write the log files for each test run. */
38-
juce::String outputFilename; /**< Filename to write logs into */
39-
juce::StringArray disabledTests; /**< List of disabled tests. */
40-
std::vector<double> sampleRates; /**< List of sample rates. */
41-
std::vector<int> blockSizes; /**< List of block sizes. */
42-
juce::File vst3Validator; /**< juce::File to use as the VST3 validator app. */
40+
int strictnessLevel = 5; /**< Max test level to run. */
41+
juce::int64 randomSeed = 0; /**< The seed to use for the tests, 0 signifies a randomly generated seed. */
42+
juce::int64 timeoutMs = 30000; /**< Timeout after which to kill the test. */
43+
bool verbose = false; /**< Whether or not to log additional information. */
44+
int numRepeats = 1; /**< The number of times to repeat the tests. */
45+
bool randomiseTestOrder = false; /**< Whether to randomise the order of the tests in each repeat. */
46+
bool withGUI = true; /**< Whether or not avoid tests that instantiate a gui. */
47+
juce::File dataFile; /**< juce::File which tests can use to run user provided data. */
48+
juce::File outputDir; /**< Directory in which to write the log files for each test run. */
49+
juce::String outputFilename; /**< Filename to write logs into */
50+
juce::StringArray disabledTests; /**< List of disabled tests. */
51+
std::vector<double> sampleRates; /**< List of sample rates. */
52+
std::vector<int> blockSizes; /**< List of block sizes. */
53+
juce::File vst3Validator; /**< juce::File to use as the VST3 validator app. */
54+
RealtimeCheck realtimeCheck = RealtimeCheck::disabled; /**< The type of real-time safety checking to perform. */
4355
};
4456

4557
/** Creates a set of tests for a fileOrIdentifier. */

Source/RTCheck.h

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*==============================================================================
2+
3+
Copyright 2018 by Tracktion Corporation.
4+
For more information visit www.tracktion.com
5+
6+
You may also use this code under the terms of the GPL v3 (see
7+
www.gnu.org/licenses).
8+
9+
pluginval IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
10+
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
11+
DISCLAIMED.
12+
13+
==============================================================================*/
14+
15+
#pragma once
16+
17+
#if PLUGINVAL_ENABLE_RTCHECK
18+
#include <rtcheck.h>
19+
#define RTC_REALTIME_CONTEXT rtc::realtime_context rc##__LINE__; 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));
20+
21+
#define RTC_REALTIME_CONTEXT_IF_LEVEL_10(level) \
22+
std::optional<rtc::realtime_context> rc; \
23+
\
24+
if (level >= 10) \
25+
{ \
26+
rc.emplace(); \
27+
rtc::disable_checks_for_thread (static_cast<uint64_t>(rtc::check_flags::pthread_mutex_lock) \
28+
| static_cast<uint64_t>(rtc::check_flags::pthread_mutex_unlock)); \
29+
}
30+
31+
#define RTC_REALTIME_CONTEXT_IF_ENABLED(realtimeCheckMode, blockNum) \
32+
std::optional<rtc::realtime_context> rc; \
33+
\
34+
if (realtimeCheckMode != RealtimeCheck::disabled) \
35+
{ \
36+
if (realtimeCheckMode != RealtimeCheck::relaxed || blockNum > 0) \
37+
{ \
38+
rc.emplace(); \
39+
rtc::disable_checks_for_thread (static_cast<uint64_t>(rtc::check_flags::pthread_mutex_lock) \
40+
| static_cast<uint64_t>(rtc::check_flags::pthread_mutex_unlock)); \
41+
} \
42+
}
43+
#else
44+
#define RTC_REALTIME_CONTEXT_IF_ENABLED(realtimeCheckMode, blockNum) \
45+
(void) realtimeCheckMode; \
46+
(void) blockNum;
47+
#endif

Source/Validator.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,8 @@ inline juce::Array<juce::UnitTestRunner::TestResult> runTests (PluginTests& test
203203

204204
updateFileNameIfPossible (test, testRunner);
205205
const int failures = getNumFailures (results);
206-
if (!failures)
206+
207+
if (! failures)
207208
testRunner.logMessage("SUCCESS");
208209
else
209210
testRunner.logMessage("FAILURE");

0 commit comments

Comments
 (0)