diff --git a/BREAKING-CHANGES.txt b/BREAKING-CHANGES.txt index 59c9de7bec..9c3525ae6c 100644 --- a/BREAKING-CHANGES.txt +++ b/BREAKING-CHANGES.txt @@ -1,9 +1,37 @@ JUCE breaking changes ===================== -develop +Develop ======= +Change +------ +The Thread::startThread (int) and Thread::setPriority (int) methods have been +removed. A new Thread priority API has been introduced. + +Possible Issues +--------------- +Code will fail to compile. + +Workaround +---------- +Rather than using an integer thread priority you must instead use a value from +the Thread::Priority enum. Thread::setPriority and Thread::getPriority should +only be called from the target thread. To start a Thread with a realtime +performance profile you must call startRealtimeThread. + +Rationale +--------- +Operating systems are moving away from a specific thread priority and towards +more granular control over which types of cores can be used and things like +power throttling options. In particular, it is no longer possible to map a 0-10 +integer to a meaningful performance range on macOS ARM using the pthread +interface. Using a more modern interface grants us access to more runtime +options, but also changes how we can work with threads. The two most +significant changes are that we cannot mix operations using the new and old +interfaces, and that changing a priority using the new interface can only be +done on the currently running thread. + Change ------ The constructor of WebBrowserComponent now requires passing in an instance of @@ -40,7 +68,6 @@ browser backend is more intuitive then requiring the user to derive from a special class, especially if additional browser backends are added in the future. - Change ------ The function AudioIODeviceCallback::audioDeviceIOCallback() was removed. diff --git a/examples/Audio/AudioPlaybackDemo.h b/examples/Audio/AudioPlaybackDemo.h index dd457ce5c1..54cbbcc42c 100644 --- a/examples/Audio/AudioPlaybackDemo.h +++ b/examples/Audio/AudioPlaybackDemo.h @@ -325,7 +325,7 @@ public: // audio setup formatManager.registerBasicFormats(); - thread.startThread (3); + thread.startThread (Thread::Priority::normal); #ifndef JUCE_DEMO_RUNNER RuntimePermissions::request (RuntimePermissions::recordAudio, diff --git a/examples/GUI/ImagesDemo.h b/examples/GUI/ImagesDemo.h index a03c68403e..6a112533ca 100644 --- a/examples/GUI/ImagesDemo.h +++ b/examples/GUI/ImagesDemo.h @@ -57,7 +57,7 @@ public: { setOpaque (true); imageList.setDirectory (File::getSpecialLocation (File::userPicturesDirectory), true, true); - directoryThread.startThread (1); + directoryThread.startThread (Thread::Priority::background); fileTree.setTitle ("Files"); fileTree.addListener (this); diff --git a/examples/GUI/VideoDemo.h b/examples/GUI/VideoDemo.h index 3f7d02ed05..086f5aa0a1 100644 --- a/examples/GUI/VideoDemo.h +++ b/examples/GUI/VideoDemo.h @@ -154,7 +154,7 @@ public: setOpaque (true); movieList.setDirectory (File::getSpecialLocation (File::userMoviesDirectory), true, true); - directoryThread.startThread (1); + directoryThread.startThread (Thread::Priority::background); fileTree.setTitle ("Files"); fileTree.addListener (this); diff --git a/examples/Plugins/ARAPluginDemo.h b/examples/Plugins/ARAPluginDemo.h index 9d59439a03..0ef52e80c9 100644 --- a/examples/Plugins/ARAPluginDemo.h +++ b/examples/Plugins/ARAPluginDemo.h @@ -63,7 +63,7 @@ public: SharedTimeSliceThread() : TimeSliceThread (String (JucePlugin_Name) + " ARA Sample Reading Thread") { - startThread (7); // Above default priority so playback is fluent, but below realtime + startThread (Priority::high); // Above default priority so playback is fluent, but below realtime } }; diff --git a/examples/Utilities/MultithreadingDemo.h b/examples/Utilities/MultithreadingDemo.h index e2363bad6f..9b329253ff 100644 --- a/examples/Utilities/MultithreadingDemo.h +++ b/examples/Utilities/MultithreadingDemo.h @@ -148,9 +148,7 @@ public: : BouncingBall (containerComp), Thread ("JUCE Demo Thread") { - // give the threads a random priority, so some will move more - // smoothly than others.. - startThread (Random::getSystemRandom().nextInt (3) + 3); + startThread(); } ~DemoThread() override diff --git a/extras/Projucer/Source/Application/jucer_AutoUpdater.cpp b/extras/Projucer/Source/Application/jucer_AutoUpdater.cpp index 3b6fed5ae0..204d649be5 100644 --- a/extras/Projucer/Source/Application/jucer_AutoUpdater.cpp +++ b/extras/Projucer/Source/Application/jucer_AutoUpdater.cpp @@ -44,7 +44,7 @@ void LatestVersionCheckerAndUpdater::checkForNewVersion (bool background) if (! isThreadRunning()) { backgroundCheck = background; - startThread (3); + startThread (Priority::low); } } @@ -373,7 +373,7 @@ public: : ThreadWithProgressWindow ("Downloading New Version", true, true), asset (a), targetFolder (t), completionCallback (std::move (cb)) { - launchThread (3); + launchThread (Priority::low); } private: diff --git a/modules/juce_audio_devices/midi_io/juce_MidiDevices.cpp b/modules/juce_audio_devices/midi_io/juce_MidiDevices.cpp index 51f0e5af94..47b7fae7c0 100644 --- a/modules/juce_audio_devices/midi_io/juce_MidiDevices.cpp +++ b/modules/juce_audio_devices/midi_io/juce_MidiDevices.cpp @@ -87,7 +87,7 @@ void MidiOutput::clearAllPendingMessages() void MidiOutput::startBackgroundThread() { - startThread (9); + startThread (Priority::high); } void MidiOutput::stopBackgroundThread() diff --git a/modules/juce_audio_devices/native/juce_android_Audio.cpp b/modules/juce_audio_devices/native/juce_android_Audio.cpp index 75607c6916..b654dae640 100644 --- a/modules/juce_audio_devices/native/juce_android_Audio.cpp +++ b/modules/juce_audio_devices/native/juce_android_Audio.cpp @@ -253,7 +253,7 @@ public: if (inputDevice != nullptr) env->CallVoidMethod (inputDevice, AudioRecord.startRecording); - startThread (8); + startThread (Priority::high); } else { diff --git a/modules/juce_audio_devices/native/juce_linux_ALSA.cpp b/modules/juce_audio_devices/native/juce_linux_ALSA.cpp index dafb34cf95..1be927ae90 100644 --- a/modules/juce_audio_devices/native/juce_linux_ALSA.cpp +++ b/modules/juce_audio_devices/native/juce_linux_ALSA.cpp @@ -618,7 +618,7 @@ public: if (outputDevice != nullptr && JUCE_ALSA_FAILED (snd_pcm_prepare (outputDevice->handle))) return; - startThread (9); + startThread (Priority::high); int count = 1000; diff --git a/modules/juce_audio_devices/native/juce_win32_DirectSound.cpp b/modules/juce_audio_devices/native/juce_win32_DirectSound.cpp index b0e5dcd1b5..239804012c 100644 --- a/modules/juce_audio_devices/native/juce_win32_DirectSound.cpp +++ b/modules/juce_audio_devices/native/juce_win32_DirectSound.cpp @@ -1196,7 +1196,7 @@ String DSoundAudioIODevice::openDevice (const BigInteger& inputChannels, for (int i = 0; i < inChans.size(); ++i) inChans.getUnchecked(i)->synchronisePosition(); - startThread (9); + startThread (Priority::highest); sleep (10); notify(); diff --git a/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp b/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp index f5484f5b84..d20b6e02c9 100644 --- a/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp +++ b/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp @@ -1387,7 +1387,7 @@ public: shouldShutdown = false; deviceSampleRateChanged = false; - startThread (8); + startThread (Priority::high); Thread::sleep (5); if (inputDevice != nullptr && inputDevice->client != nullptr) diff --git a/modules/juce_audio_formats/format/juce_BufferingAudioFormatReader.cpp b/modules/juce_audio_formats/format/juce_BufferingAudioFormatReader.cpp index 34a9de5c6f..95da52c920 100644 --- a/modules/juce_audio_formats/format/juce_BufferingAudioFormatReader.cpp +++ b/modules/juce_audio_formats/format/juce_BufferingAudioFormatReader.cpp @@ -254,7 +254,7 @@ public: void runTest() override { TimeSliceThread timeSlice ("TestBackgroundThread"); - timeSlice.startThread (5); + timeSlice.startThread (Thread::Priority::normal); beginTest ("Timeout"); { diff --git a/modules/juce_audio_plugin_client/utility/juce_LinuxMessageThread.h b/modules/juce_audio_plugin_client/utility/juce_LinuxMessageThread.h index 646d07285b..267a86fe11 100644 --- a/modules/juce_audio_plugin_client/utility/juce_LinuxMessageThread.h +++ b/modules/juce_audio_plugin_client/utility/juce_LinuxMessageThread.h @@ -25,8 +25,6 @@ #if JUCE_LINUX || JUCE_BSD -#include - namespace juce { @@ -34,15 +32,15 @@ namespace juce bool dispatchNextMessageOnSystemQueue (bool returnIfNoPendingMessages); /** @internal */ -class MessageThread +class MessageThread : public Thread { public: - MessageThread() + MessageThread() : Thread ("JUCE Plugin Message Thread") { start(); } - ~MessageThread() + ~MessageThread() override { MessageManager::getInstance()->stopDispatchLoop(); stop(); @@ -50,51 +48,37 @@ public: void start() { - if (isRunning()) - stop(); + startThread (Priority::high); - shouldExit = false; - - thread = std::thread { [this] - { - Thread::setCurrentThreadPriority (7); - Thread::setCurrentThreadName ("JUCE Plugin Message Thread"); - - MessageManager::getInstance()->setCurrentThreadAsMessageThread(); - XWindowSystem::getInstance(); - - threadInitialised.signal(); - - for (;;) - { - if (! dispatchNextMessageOnSystemQueue (true)) - Thread::sleep (1); - - if (shouldExit) - break; - } - } }; - - threadInitialised.wait(); + // Wait for setCurrentThreadAsMessageThread() and getInstance to be executed + // before leaving this method + threadInitialised.wait (10000); } void stop() { - if (! isRunning()) - return; - - shouldExit = true; - thread.join(); + signalThreadShouldExit(); + stopThread (-1); } - bool isRunning() const noexcept { return thread.joinable(); } + bool isRunning() const noexcept { return isThreadRunning(); } + + void run() override + { + MessageManager::getInstance()->setCurrentThreadAsMessageThread(); + XWindowSystem::getInstance(); + + threadInitialised.signal(); + + while (! threadShouldExit()) + { + if (! dispatchNextMessageOnSystemQueue (true)) + Thread::sleep (1); + } + } private: WaitableEvent threadInitialised; - std::thread thread; - - std::atomic shouldExit { false }; - JUCE_DECLARE_NON_MOVEABLE (MessageThread) JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MessageThread) }; diff --git a/modules/juce_audio_utils/gui/juce_AudioThumbnailCache.cpp b/modules/juce_audio_utils/gui/juce_AudioThumbnailCache.cpp index 4900cc4876..59800c31c9 100644 --- a/modules/juce_audio_utils/gui/juce_AudioThumbnailCache.cpp +++ b/modules/juce_audio_utils/gui/juce_AudioThumbnailCache.cpp @@ -64,7 +64,7 @@ AudioThumbnailCache::AudioThumbnailCache (const int maxNumThumbs) maxNumThumbsToStore (maxNumThumbs) { jassert (maxNumThumbsToStore > 0); - thread.startThread (2); + thread.startThread (Thread::Priority::low); } AudioThumbnailCache::~AudioThumbnailCache() diff --git a/modules/juce_core/juce_core.cpp b/modules/juce_core/juce_core.cpp index e79c1c91f4..99884b30bd 100644 --- a/modules/juce_core/juce_core.cpp +++ b/modules/juce_core/juce_core.cpp @@ -186,6 +186,7 @@ #include "zip/juce_ZipFile.cpp" #include "files/juce_FileFilter.cpp" #include "files/juce_WildcardFileFilter.cpp" +#include "native/juce_native_ThreadPriorities.h" //============================================================================== #if ! JUCE_WINDOWS diff --git a/modules/juce_core/native/juce_BasicNativeHeaders.h b/modules/juce_core/native/juce_BasicNativeHeaders.h index 3d8c715cba..f444f9c07f 100644 --- a/modules/juce_core/native/juce_BasicNativeHeaders.h +++ b/modules/juce_core/native/juce_BasicNativeHeaders.h @@ -203,6 +203,7 @@ #include #include #include + #include #include #include #include diff --git a/modules/juce_core/native/juce_android_Threads.cpp b/modules/juce_core/native/juce_android_Threads.cpp index 796328aa0c..e69b3bd9d2 100644 --- a/modules/juce_core/native/juce_android_Threads.cpp +++ b/modules/juce_core/native/juce_android_Threads.cpp @@ -344,36 +344,79 @@ LocalRef getMainActivity() noexcept } //============================================================================== -// sets the process to 0=low priority, 1=normal, 2=high, 3=realtime -JUCE_API void JUCE_CALLTYPE Process::setPriority (ProcessPriority prior) +#if JUCE_ANDROID && JUCE_MODULE_AVAILABLE_juce_audio_devices && (JUCE_USE_ANDROID_OPENSLES || JUCE_USE_ANDROID_OBOE) + #define JUCE_ANDROID_REALTIME_THREAD_AVAILABLE 1 +#endif + +#if JUCE_ANDROID_REALTIME_THREAD_AVAILABLE +pthread_t juce_createRealtimeAudioThread (void* (*entry) (void*), void* userPtr); +#endif + +extern JavaVM* androidJNIJavaVM; + +bool Thread::createNativeThread (Priority) { - // TODO + if (isRealtime()) + { + #if JUCE_ANDROID_REALTIME_THREAD_AVAILABLE + threadHandle = (void*) juce_createRealtimeAudioThread (threadEntryProc, this); + threadId = (ThreadID) threadHandle.get(); + return threadId != nullptr; + #else + jassertfalse; + #endif + } - struct sched_param param; - int policy, maxp, minp; + PosixThreadAttribute attr { threadStackSize }; + threadId = threadHandle = makeThreadHandle (attr, this, [] (void* userData) -> void* + { + auto* myself = static_cast (userData); - const int p = (int) prior; + juce_threadEntryPoint (myself); - if (p <= 1) - policy = SCHED_OTHER; - else - policy = SCHED_RR; + if (androidJNIJavaVM != nullptr) + { + void* env = nullptr; + androidJNIJavaVM->GetEnv (&env, JNI_VERSION_1_2); - minp = sched_get_priority_min (policy); - maxp = sched_get_priority_max (policy); + // only detach if we have actually been attached + if (env != nullptr) + androidJNIJavaVM->DetachCurrentThread(); + } - if (p < 2) - param.sched_priority = 0; - else if (p == 2 ) - // Set to middle of lower realtime priority range - param.sched_priority = minp + (maxp - minp) / 4; - else - // Set to middle of higher realtime priority range - param.sched_priority = minp + (3 * (maxp - minp) / 4); + return nullptr; + }); - pthread_setschedparam (pthread_self(), policy, ¶m); + return threadId != nullptr; } +void Thread::killThread() +{ + if (threadHandle != nullptr) + jassertfalse; // pthread_cancel not available! +} + +Thread::Priority Thread::getPriority() const +{ + jassert (Thread::getCurrentThreadId() == getThreadId()); + + const auto native = getpriority (PRIO_PROCESS, (id_t) gettid()); + return ThreadPriorities::getJucePriority (native); +} + +bool Thread::setPriority (Priority) +{ + jassert (Thread::getCurrentThreadId() == getThreadId()); + + if (isRealtime()) + return false; + + return setpriority (PRIO_PROCESS, (id_t) gettid(), ThreadPriorities::getNativePriority (priority)) == 0; +} + +//============================================================================== +JUCE_API void JUCE_CALLTYPE Process::setPriority (ProcessPriority) {} + JUCE_API bool JUCE_CALLTYPE juce_isRunningUnderDebugger() noexcept { StringArray lines; diff --git a/modules/juce_core/native/juce_linux_Threads.cpp b/modules/juce_core/native/juce_linux_Threads.cpp index 9f9c07b7b1..6abf658675 100644 --- a/modules/juce_core/native/juce_linux_Threads.cpp +++ b/modules/juce_core/native/juce_linux_Threads.cpp @@ -28,27 +28,49 @@ namespace juce live in juce_posix_SharedCode.h! */ -//============================================================================== -JUCE_API void JUCE_CALLTYPE Process::setPriority (const ProcessPriority prior) +bool Thread::createNativeThread (Priority) { - auto policy = (prior <= NormalPriority) ? SCHED_OTHER : SCHED_RR; - auto minp = sched_get_priority_min (policy); - auto maxp = sched_get_priority_max (policy); + PosixThreadAttribute attr { threadStackSize }; + PosixSchedulerPriority::getNativeSchedulerAndPriority (realtimeOptions, {}).apply (attr); - struct sched_param param; - - switch (prior) + threadId = threadHandle = makeThreadHandle (attr, this, [] (void* userData) -> void* { - case LowPriority: - case NormalPriority: param.sched_priority = 0; break; - case HighPriority: param.sched_priority = minp + (maxp - minp) / 4; break; - case RealtimePriority: param.sched_priority = minp + (3 * (maxp - minp) / 4); break; - default: jassertfalse; break; - } + auto* myself = static_cast (userData); - pthread_setschedparam (pthread_self(), policy, ¶m); + juce_threadEntryPoint (myself); + + return nullptr; + }); + + return threadId != nullptr; } +void Thread::killThread() +{ + if (threadHandle != nullptr) + pthread_cancel ((pthread_t) threadHandle.load()); +} + +// Until we implement Nice awareness, these don't do anything on Linux. +Thread::Priority Thread::getPriority() const +{ + jassert (Thread::getCurrentThreadId() == getThreadId()); + + return priority; +} + +bool Thread::setPriority (Priority newPriority) +{ + jassert (Thread::getCurrentThreadId() == getThreadId()); + + // Return true to make it compatible with other platforms. + priority = newPriority; + return true; +} + +//============================================================================== +JUCE_API void JUCE_CALLTYPE Process::setPriority (ProcessPriority) {} + static bool swapUserAndEffectiveUser() { auto result1 = setreuid (geteuid(), getuid()); diff --git a/modules/juce_core/native/juce_mac_Threads.mm b/modules/juce_core/native/juce_mac_Threads.mm index 9a88832a16..7c692be768 100644 --- a/modules/juce_core/native/juce_mac_Threads.mm +++ b/modules/juce_core/native/juce_mac_Threads.mm @@ -29,9 +29,139 @@ namespace juce */ #if JUCE_IOS -bool isIOSAppActive = true; + bool isIOSAppActive = true; #endif +API_AVAILABLE (macos (10.10)) +static auto getNativeQOS (Thread::Priority priority) +{ + switch (priority) + { + case Thread::Priority::highest: return QOS_CLASS_USER_INTERACTIVE; + case Thread::Priority::high: return QOS_CLASS_USER_INITIATED; + case Thread::Priority::low: return QOS_CLASS_UTILITY; + case Thread::Priority::background: return QOS_CLASS_BACKGROUND; + case Thread::Priority::normal: break; + } + + return QOS_CLASS_DEFAULT; +} + +API_AVAILABLE (macos (10.10)) +static auto getJucePriority (qos_class_t qos) +{ + switch (qos) + { + case QOS_CLASS_USER_INTERACTIVE: return Thread::Priority::highest; + case QOS_CLASS_USER_INITIATED: return Thread::Priority::high; + case QOS_CLASS_UTILITY: return Thread::Priority::low; + case QOS_CLASS_BACKGROUND: return Thread::Priority::background; + + case QOS_CLASS_UNSPECIFIED: + case QOS_CLASS_DEFAULT: break; + } + + return Thread::Priority::normal; +} + +bool Thread::createNativeThread (Priority priority) +{ + PosixThreadAttribute attr { threadStackSize }; + + if (@available (macos 10.10, *)) + pthread_attr_set_qos_class_np (attr.get(), getNativeQOS (priority), 0); + else + PosixSchedulerPriority::getNativeSchedulerAndPriority (realtimeOptions, priority).apply (attr); + + threadId = threadHandle = makeThreadHandle (attr, this, [] (void* userData) -> void* + { + auto* myself = static_cast (userData); + + JUCE_AUTORELEASEPOOL + { + juce_threadEntryPoint (myself); + } + + return nullptr; + }); + + return threadId != nullptr; +} + +void Thread::killThread() +{ + if (threadHandle != nullptr) + pthread_cancel ((pthread_t) threadHandle.load()); +} + +Thread::Priority Thread::getPriority() const +{ + jassert (Thread::getCurrentThreadId() == getThreadId()); + + if (! isRealtime()) + { + if (@available (macOS 10.10, *)) + return getJucePriority (qos_class_self()); + + // fallback for older versions of macOS + const auto min = jmax (0, sched_get_priority_min (SCHED_OTHER)); + const auto max = jmax (0, sched_get_priority_max (SCHED_OTHER)); + + if (min != 0 && max != 0) + { + const auto native = PosixSchedulerPriority::findCurrentSchedulerAndPriority().getPriority(); + const auto mapped = jmap (native, min, max, 0, 4); + return ThreadPriorities::getJucePriority (mapped); + } + } + + return {}; +} + +bool Thread::setPriority (Priority priority) +{ + jassert (Thread::getCurrentThreadId() == getThreadId()); + + if (isRealtime()) + { + // macOS/iOS needs to know how much time you need! + jassert (realtimeOptions->workDurationMs > 0); + + mach_timebase_info_data_t timebase; + mach_timebase_info (&timebase); + + const auto periodMs = realtimeOptions->workDurationMs; + const auto ticksPerMs = ((double) timebase.denom * 1000000.0) / (double) timebase.numer; + const auto periodTicks = (uint32_t) jmin ((double) std::numeric_limits::max(), periodMs * ticksPerMs); + + thread_time_constraint_policy_data_t policy; + policy.period = periodTicks; + policy.computation = jmin ((uint32_t) 50000, policy.period); + policy.constraint = policy.period; + policy.preemptible = true; + + return thread_policy_set (pthread_mach_thread_np (pthread_self()), + THREAD_TIME_CONSTRAINT_POLICY, + (thread_policy_t) &policy, + THREAD_TIME_CONSTRAINT_POLICY_COUNT) == KERN_SUCCESS; + } + + if (@available (macOS 10.10, *)) + return pthread_set_qos_class_self_np (getNativeQOS (priority), 0) == 0; + + #if JUCE_ARM + // M1 platforms should never reach this code!!!!!! + jassertfalse; + #endif + + // Just in case older versions of macOS support SCHED_OTHER priorities. + const auto psp = PosixSchedulerPriority::getNativeSchedulerAndPriority ({}, priority); + + struct sched_param param; + param.sched_priority = psp.getPriority(); + return pthread_setschedparam (pthread_self(), psp.getScheduler(), ¶m) == 0; +} + //============================================================================== JUCE_API bool JUCE_CALLTYPE Process::isForegroundProcess() { diff --git a/modules/juce_core/native/juce_native_ThreadPriorities.h b/modules/juce_core/native/juce_native_ThreadPriorities.h new file mode 100644 index 0000000000..5bc1d9e2c1 --- /dev/null +++ b/modules/juce_core/native/juce_native_ThreadPriorities.h @@ -0,0 +1,109 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +struct ThreadPriorities +{ + struct Entry + { + Thread::Priority priority; + int native; + }; + + #if JUCE_ANDROID + enum AndroidThreadPriority + { + THREAD_PRIORITY_AUDIO = -16, + THREAD_PRIORITY_FOREGROUND = -2, + THREAD_PRIORITY_MORE_FAVORABLE = -1, + THREAD_PRIORITY_DEFAULT = 0, + THREAD_PRIORITY_LESS_FAVORABLE = 1, + THREAD_PRIORITY_BACKGROUND = 10, + THREAD_PRIORITY_LOWEST = 19 + }; + #endif + + inline static constexpr Entry table[] + { + #if JUCE_ANDROID + { Thread::Priority::highest, AndroidThreadPriority::THREAD_PRIORITY_AUDIO }, + { Thread::Priority::high, AndroidThreadPriority::THREAD_PRIORITY_FOREGROUND }, + { Thread::Priority::normal, AndroidThreadPriority::THREAD_PRIORITY_DEFAULT }, + { Thread::Priority::low, AndroidThreadPriority::THREAD_PRIORITY_BACKGROUND - 5 }, + { Thread::Priority::background, AndroidThreadPriority::THREAD_PRIORITY_BACKGROUND }, + #endif + + #if JUCE_LINUX || JUCE_BSD + { Thread::Priority::highest, 0 }, + { Thread::Priority::high, 0 }, + { Thread::Priority::normal, 0 }, + { Thread::Priority::low, 0 }, + { Thread::Priority::background, 0 }, + #endif + + #if JUCE_MAC | JUCE_IOS + { Thread::Priority::highest, 4 }, + { Thread::Priority::high, 3 }, + { Thread::Priority::normal, 2 }, + { Thread::Priority::low, 1 }, + { Thread::Priority::background, 0 }, + #endif + + #if JUCE_WINDOWS + { Thread::Priority::highest, THREAD_PRIORITY_TIME_CRITICAL }, + { Thread::Priority::high, THREAD_PRIORITY_HIGHEST }, + { Thread::Priority::normal, THREAD_PRIORITY_NORMAL }, + { Thread::Priority::low, THREAD_PRIORITY_LOWEST }, + { Thread::Priority::background, THREAD_PRIORITY_IDLE }, + #endif + }; + + static_assert (std::size (table) == 5, + "The platform may be unsupported or there may be a priority entry missing."); + + static Thread::Priority getJucePriority (const int value) + { + const auto iter = std::min_element (std::begin (table), + std::end (table), + [value] (const auto& a, const auto& b) + { + return std::abs (a.native - value) < std::abs (b.native - value); + }); + + jassert (iter != std::end (table)); + return iter != std::end (table) ? iter->priority : Thread::Priority{}; + } + + static int getNativePriority (const Thread::Priority value) + { + const auto iter = std::find_if (std::begin (table), + std::end (table), + [value] (const auto& entry) { return entry.priority == value; }); + + jassert (iter != std::end (table)); + return iter != std::end (table) ? iter->native : 0; + } +}; + +} // namespace juce diff --git a/modules/juce_core/native/juce_posix_SharedCode.h b/modules/juce_core/native/juce_posix_SharedCode.h index 66ff9669b7..dee843cb57 100644 --- a/modules/juce_core/native/juce_posix_SharedCode.h +++ b/modules/juce_core/native/juce_posix_SharedCode.h @@ -851,98 +851,131 @@ void InterProcessLock::exit() pimpl.reset(); } -//============================================================================== -#if JUCE_ANDROID -extern JavaVM* androidJNIJavaVM; -#endif - -static void* threadEntryProc (void* userData) +class PosixThreadAttribute { - auto* myself = static_cast (userData); - - JUCE_AUTORELEASEPOOL +public: + explicit PosixThreadAttribute (size_t stackSize) { - juce_threadEntryPoint (myself); + if (valid) + pthread_attr_setstacksize (&attr, stackSize); } - #if JUCE_ANDROID - if (androidJNIJavaVM != nullptr) + ~PosixThreadAttribute() { - void* env = nullptr; - androidJNIJavaVM->GetEnv(&env, JNI_VERSION_1_2); - - // only detach if we have actually been attached - if (env != nullptr) - androidJNIJavaVM->DetachCurrentThread(); + if (valid) + pthread_attr_destroy (&attr); } - #endif - return nullptr; -} + auto* get() { return valid ? &attr : nullptr; } -#if JUCE_ANDROID && JUCE_MODULE_AVAILABLE_juce_audio_devices && (JUCE_USE_ANDROID_OPENSLES || JUCE_USE_ANDROID_OBOE) - #define JUCE_ANDROID_REALTIME_THREAD_AVAILABLE 1 -#endif - -#if JUCE_ANDROID_REALTIME_THREAD_AVAILABLE -extern pthread_t juce_createRealtimeAudioThread (void* (*entry) (void*), void* userPtr); -#endif - -void Thread::launchThread() -{ - #if JUCE_ANDROID - if (isAndroidRealtimeThread) - { - #if JUCE_ANDROID_REALTIME_THREAD_AVAILABLE - threadHandle = (void*) juce_createRealtimeAudioThread (threadEntryProc, this); - threadId = (ThreadID) threadHandle.get(); - - return; - #else - jassertfalse; - #endif - } - #endif - - threadHandle = {}; - pthread_t handle = {}; +private: pthread_attr_t attr; - pthread_attr_t* attrPtr = nullptr; + bool valid { pthread_attr_init (&attr) == 0 }; +}; - if (pthread_attr_init (&attr) == 0) +class PosixSchedulerPriority +{ +public: + static PosixSchedulerPriority findCurrentSchedulerAndPriority() { - attrPtr = &attr; - pthread_attr_setstacksize (attrPtr, threadStackSize); + int scheduler{}; + sched_param param{}; + pthread_getschedparam (pthread_self(), &scheduler, ¶m); + return { scheduler, param.sched_priority }; } - - if (pthread_create (&handle, attrPtr, threadEntryProc, this) == 0) + static PosixSchedulerPriority getNativeSchedulerAndPriority (const Optional& rt, + [[maybe_unused]] Thread::Priority prio) { - pthread_detach (handle); - threadHandle = (void*) handle; - threadId = (ThreadID) threadHandle.get(); + const auto isRealtime = rt.hasValue(); + + const auto priority = [&] + { + if (isRealtime) + { + const auto min = jmax (0, sched_get_priority_min (SCHED_RR)); + const auto max = jmax (1, sched_get_priority_max (SCHED_RR)); + + return jmap (rt->priority, 0, 10, min, max); + } + + // We only use this helper if we're on an old macos/ios platform that might + // still respect legacy pthread priorities for SCHED_OTHER. + #if JUCE_MAC || JUCE_IOS + const auto min = jmax (0, sched_get_priority_min (SCHED_OTHER)); + const auto max = jmax (0, sched_get_priority_max (SCHED_OTHER)); + + const auto p = [prio] + { + switch (prio) + { + case Thread::Priority::highest: return 4; + case Thread::Priority::high: return 3; + case Thread::Priority::normal: return 2; + case Thread::Priority::low: return 1; + case Thread::Priority::background: return 0; + } + + return 3; + }(); + + if (min != 0 && max != 0) + return jmap (p, 0, 4, min, max); + #endif + + return 0; + }(); + + #if JUCE_MAC || JUCE_IOS + const auto scheduler = SCHED_OTHER; + #elif JUCE_LINUX || JUCE_BSD + const auto backgroundSched = prio == Thread::Priority::background ? SCHED_IDLE + : SCHED_OTHER; + const auto scheduler = isRealtime ? SCHED_RR : backgroundSched; + #else + const auto scheduler = 0; + #endif + + return { scheduler, priority }; } - if (attrPtr != nullptr) - pthread_attr_destroy (attrPtr); + void apply ([[maybe_unused]] PosixThreadAttribute& attr) const + { + #if JUCE_LINUX || JUCE_BSD + const struct sched_param param { getPriority() }; + + pthread_attr_setinheritsched (attr.get(), PTHREAD_EXPLICIT_SCHED); + pthread_attr_setschedpolicy (attr.get(), getScheduler()); + pthread_attr_setschedparam (attr.get(), ¶m); + #endif + } + + constexpr int getScheduler() const { return scheduler; } + constexpr int getPriority() const { return priority; } + +private: + constexpr PosixSchedulerPriority (int schedulerIn, int priorityIn) + : scheduler (schedulerIn), priority (priorityIn) {} + + int scheduler; + int priority; +}; + +static void* makeThreadHandle (PosixThreadAttribute& attr, Thread* userData, void* (*threadEntryProc) (void*)) +{ + pthread_t handle = {}; + + if (pthread_create (&handle, attr.get(), threadEntryProc, userData) != 0) + return nullptr; + + pthread_detach (handle); + return (void*) handle; } void Thread::closeThreadHandle() { threadId = {}; - threadHandle = {}; -} - -void Thread::killThread() -{ - if (threadHandle.get() != nullptr) - { - #if JUCE_ANDROID - jassertfalse; // pthread_cancel not available! - #else - pthread_cancel ((pthread_t) threadHandle.get()); - #endif - } + threadHandle = nullptr; } void JUCE_CALLTYPE Thread::setCurrentThreadName (const String& name) @@ -963,41 +996,6 @@ void JUCE_CALLTYPE Thread::setCurrentThreadName (const String& name) #endif } -bool Thread::setThreadPriority (void* handle, int priority) -{ - constexpr auto maxInputPriority = 10; - - #if JUCE_LINUX || JUCE_BSD - constexpr auto lowestRrPriority = 8; - #else - constexpr auto lowestRrPriority = 0; - #endif - - struct sched_param param; - int policy; - - if (handle == nullptr) - handle = (void*) pthread_self(); - - if (pthread_getschedparam ((pthread_t) handle, &policy, ¶m) != 0) - return false; - - policy = priority < lowestRrPriority ? SCHED_OTHER : SCHED_RR; - - const auto minPriority = sched_get_priority_min (policy); - const auto maxPriority = sched_get_priority_max (policy); - - param.sched_priority = [&] - { - if (policy == SCHED_OTHER) - return 0; - - return jmap (priority, lowestRrPriority, maxInputPriority, minPriority, maxPriority); - }(); - - return pthread_setschedparam ((pthread_t) handle, policy, ¶m) == 0; -} - Thread::ThreadID JUCE_CALLTYPE Thread::getCurrentThreadId() { return (ThreadID) pthread_self(); @@ -1262,145 +1260,4 @@ bool ChildProcess::start (const StringArray& args, int streamFlags) #endif -//============================================================================== -struct HighResolutionTimer::Pimpl -{ - explicit Pimpl (HighResolutionTimer& t) - : owner (t) - {} - - ~Pimpl() - { - jassert (periodMs == 0); - stop(); - } - - void start (int newPeriod) - { - if (periodMs == newPeriod) - return; - - if (thread.get_id() == std::this_thread::get_id()) - { - periodMs = newPeriod; - return; - } - - stop(); - - periodMs = newPeriod; - - thread = std::thread ([this, newPeriod] - { - setThisThreadToRealtime ((uint64) newPeriod); - - auto lastPeriod = periodMs.load(); - Clock clock (lastPeriod); - - std::unique_lock unique_lock (timerMutex); - - while (periodMs != 0) - { - clock.next(); - while (periodMs != 0 && clock.wait (stopCond, unique_lock)); - - if (periodMs == 0) - break; - - owner.hiResTimerCallback(); - - auto nextPeriod = periodMs.load(); - - if (lastPeriod != nextPeriod) - { - lastPeriod = nextPeriod; - clock = Clock (lastPeriod); - } - } - - periodMs = 0; - }); - } - - void stop() - { - periodMs = 0; - - const auto thread_id = thread.get_id(); - - if (thread_id == std::thread::id() || thread_id == std::this_thread::get_id()) - return; - - { - std::unique_lock unique_lock (timerMutex); - stopCond.notify_one(); - } - - thread.join(); - } - - HighResolutionTimer& owner; - std::atomic periodMs { 0 }; - -private: - std::thread thread; - std::condition_variable stopCond; - std::mutex timerMutex; - - class Clock - { - public: - explicit Clock (std::chrono::steady_clock::rep millis) noexcept - : time (std::chrono::steady_clock::now()), - delta (std::chrono::milliseconds (millis)) - {} - - bool wait (std::condition_variable& cond, std::unique_lock& lock) noexcept - { - return cond.wait_until (lock, time) != std::cv_status::timeout; - } - - void next() noexcept - { - time += delta; - } - - private: - std::chrono::time_point time; - std::chrono::steady_clock::duration delta; - }; - - static bool setThisThreadToRealtime (uint64 periodMs) - { - const auto thread = pthread_self(); - - #if JUCE_MAC || JUCE_IOS - mach_timebase_info_data_t timebase; - mach_timebase_info (&timebase); - - const auto ticksPerMs = ((double) timebase.denom * 1000000.0) / (double) timebase.numer; - const auto periodTicks = (uint32_t) jmin ((double) std::numeric_limits::max(), periodMs * ticksPerMs); - - thread_time_constraint_policy_data_t policy; - policy.period = periodTicks; - policy.computation = jmin ((uint32_t) 50000, policy.period); - policy.constraint = policy.period; - policy.preemptible = true; - - return thread_policy_set (pthread_mach_thread_np (thread), - THREAD_TIME_CONSTRAINT_POLICY, - (thread_policy_t) &policy, - THREAD_TIME_CONSTRAINT_POLICY_COUNT) == KERN_SUCCESS; - - #else - ignoreUnused (periodMs); - struct sched_param param; - param.sched_priority = sched_get_priority_max (SCHED_RR); - return pthread_setschedparam (thread, SCHED_RR, ¶m) == 0; - #endif - } - - JUCE_DECLARE_NON_COPYABLE (Pimpl) -}; - } // namespace juce diff --git a/modules/juce_core/native/juce_win32_Threads.cpp b/modules/juce_core/native/juce_win32_Threads.cpp index 8d15042fe5..478978cbd3 100644 --- a/modules/juce_core/native/juce_win32_Threads.cpp +++ b/modules/juce_core/native/juce_win32_Threads.cpp @@ -51,7 +51,6 @@ void CriticalSection::enter() const noexcept { EnterCriticalSection ((CRI bool CriticalSection::tryEnter() const noexcept { return TryEnterCriticalSection ((CRITICAL_SECTION*) &lock) != FALSE; } void CriticalSection::exit() const noexcept { LeaveCriticalSection ((CRITICAL_SECTION*) &lock); } - //============================================================================== static unsigned int STDMETHODCALLTYPE threadEntryProc (void* userData) { @@ -65,31 +64,72 @@ static unsigned int STDMETHODCALLTYPE threadEntryProc (void* userData) return 0; } -void Thread::launchThread() +static bool setPriorityInternal (bool isRealtime, HANDLE handle, Thread::Priority priority) +{ + auto nativeThreadFlag = isRealtime ? THREAD_PRIORITY_TIME_CRITICAL + : ThreadPriorities::getNativePriority (priority); + + if (isRealtime) // This should probably be a fail state too? + Process::setPriority (Process::ProcessPriority::RealtimePriority); + + return SetThreadPriority (handle, nativeThreadFlag); +} + +bool Thread::createNativeThread (Priority priority) { unsigned int newThreadId; threadHandle = (void*) _beginthreadex (nullptr, (unsigned int) threadStackSize, - &threadEntryProc, this, 0, &newThreadId); - threadId = (ThreadID) (pointer_sized_int) newThreadId; + &threadEntryProc, this, CREATE_SUSPENDED, + &newThreadId); + + if (threadHandle != nullptr) + { + threadId = (ThreadID) (pointer_sized_int) newThreadId; + + if (setPriorityInternal (isRealtime(), threadHandle, priority)) + { + ResumeThread (threadHandle); + return true; + } + + killThread(); + closeThreadHandle(); + } + + return false; +} + +Thread::Priority Thread::getPriority() const +{ + jassert (Thread::getCurrentThreadId() == getThreadId()); + + const auto native = GetThreadPriority (threadHandle); + return ThreadPriorities::getJucePriority (native); +} + +bool Thread::setPriority (Priority priority) +{ + jassert (Thread::getCurrentThreadId() == getThreadId()); + return setPriorityInternal (isRealtime(), this, priority); } void Thread::closeThreadHandle() { - CloseHandle ((HANDLE) threadHandle.get()); + CloseHandle (threadHandle); threadId = nullptr; threadHandle = nullptr; } void Thread::killThread() { - if (threadHandle.get() != nullptr) + if (threadHandle != nullptr) { #if JUCE_DEBUG OutputDebugStringA ("** Warning - Forced thread termination **\n"); #endif JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6258) - TerminateThread (threadHandle.get(), 0); + TerminateThread (threadHandle, 0); JUCE_END_IGNORE_WARNINGS_MSVC } } @@ -129,23 +169,6 @@ Thread::ThreadID JUCE_CALLTYPE Thread::getCurrentThreadId() return (ThreadID) (pointer_sized_int) GetCurrentThreadId(); } -bool Thread::setThreadPriority (void* handle, int priority) -{ - int pri = THREAD_PRIORITY_TIME_CRITICAL; - - if (priority < 1) pri = THREAD_PRIORITY_IDLE; - else if (priority < 2) pri = THREAD_PRIORITY_LOWEST; - else if (priority < 5) pri = THREAD_PRIORITY_BELOW_NORMAL; - else if (priority < 7) pri = THREAD_PRIORITY_NORMAL; - else if (priority < 9) pri = THREAD_PRIORITY_ABOVE_NORMAL; - else if (priority < 10) pri = THREAD_PRIORITY_HIGHEST; - - if (handle == nullptr) - handle = GetCurrentThread(); - - return SetThreadPriority (handle, pri) != FALSE; -} - void JUCE_CALLTYPE Thread::setCurrentThreadAffinityMask (const uint32 affinityMask) { SetThreadAffinityMask (GetCurrentThread(), affinityMask); @@ -217,11 +240,11 @@ void juce_repeatLastProcessPriority() } } -void JUCE_CALLTYPE Process::setPriority (ProcessPriority prior) +void JUCE_CALLTYPE Process::setPriority (ProcessPriority newPriority) { - if (lastProcessPriority != (int) prior) + if (lastProcessPriority != (int) newPriority) { - lastProcessPriority = (int) prior; + lastProcessPriority = (int) newPriority; juce_repeatLastProcessPriority(); } } @@ -527,56 +550,4 @@ bool ChildProcess::start (const StringArray& args, int streamFlags) return start (escaped.trim(), streamFlags); } -//============================================================================== -struct HighResolutionTimer::Pimpl -{ - Pimpl (HighResolutionTimer& t) noexcept : owner (t) - { - } - - ~Pimpl() - { - jassert (periodMs == 0); - } - - void start (int newPeriod) - { - if (newPeriod != periodMs) - { - stop(); - periodMs = newPeriod; - - TIMECAPS tc; - if (timeGetDevCaps (&tc, sizeof (tc)) == TIMERR_NOERROR) - { - const int actualPeriod = jlimit ((int) tc.wPeriodMin, (int) tc.wPeriodMax, newPeriod); - - timerID = timeSetEvent ((UINT) actualPeriod, tc.wPeriodMin, callbackFunction, (DWORD_PTR) this, - TIME_PERIODIC | TIME_CALLBACK_FUNCTION | 0x100 /*TIME_KILL_SYNCHRONOUS*/); - } - } - } - - void stop() - { - periodMs = 0; - timeKillEvent (timerID); - } - - HighResolutionTimer& owner; - int periodMs = 0; - -private: - unsigned int timerID; - - static void STDMETHODCALLTYPE callbackFunction (UINT, UINT, DWORD_PTR userInfo, DWORD_PTR, DWORD_PTR) - { - if (Pimpl* const timer = reinterpret_cast (userInfo)) - if (timer->periodMs != 0) - timer->owner.hiResTimerCallback(); - } - - JUCE_DECLARE_NON_COPYABLE (Pimpl) -}; - } // namespace juce diff --git a/modules/juce_core/system/juce_SystemStats.h b/modules/juce_core/system/juce_SystemStats.h index aa827aeda8..a80cc541de 100644 --- a/modules/juce_core/system/juce_SystemStats.h +++ b/modules/juce_core/system/juce_SystemStats.h @@ -243,7 +243,6 @@ public: */ static bool isRunningInAppExtensionSandbox() noexcept; - //============================================================================== #ifndef DOXYGEN [[deprecated ("This method was spelt wrong! Please change your code to use getCpuSpeedInMegahertz instead.")]] diff --git a/modules/juce_core/threads/juce_HighResolutionTimer.cpp b/modules/juce_core/threads/juce_HighResolutionTimer.cpp index de0f3a7670..e0f00e240e 100644 --- a/modules/juce_core/threads/juce_HighResolutionTimer.cpp +++ b/modules/juce_core/threads/juce_HighResolutionTimer.cpp @@ -23,13 +23,96 @@ namespace juce { -HighResolutionTimer::HighResolutionTimer() : pimpl (new Pimpl (*this)) {} -HighResolutionTimer::~HighResolutionTimer() { stopTimer(); } +class HighResolutionTimer::Pimpl : private Thread +{ + using steady_clock = std::chrono::steady_clock; + using milliseconds = std::chrono::milliseconds; -void HighResolutionTimer::startTimer (int periodMs) { pimpl->start (jmax (1, periodMs)); } -void HighResolutionTimer::stopTimer() { pimpl->stop(); } +public: + explicit Pimpl (HighResolutionTimer& ownerRef) + : Thread ("HighResolutionTimerThread"), + owner (ownerRef) + { + } -bool HighResolutionTimer::isTimerRunning() const noexcept { return pimpl->periodMs != 0; } -int HighResolutionTimer::getTimerInterval() const noexcept { return pimpl->periodMs; } + using Thread::isThreadRunning; + + void start (int periodMs) + { + { + const std::scoped_lock lk { mutex }; + periodMillis = periodMs; + nextTickTime = steady_clock::now() + milliseconds (periodMillis); + } + + if (! isThreadRunning()) + startThread (Thread::Priority::high); + } + + void stop() + { + { + const std::scoped_lock lk { mutex }; + periodMillis = 0; + } + + waitEvent.notify_one(); + + if (Thread::getCurrentThreadId() != getThreadId()) + stopThread (-1); + } + + int getPeriod() const + { + return periodMillis; + } + +private: + void run() override + { + for (;;) + { + { + std::unique_lock lk { mutex }; + + if (waitEvent.wait_until (lk, nextTickTime, [this] { return periodMillis == 0; })) + break; + + nextTickTime = steady_clock::now() + milliseconds (periodMillis); + } + + owner.hiResTimerCallback(); + } + } + + HighResolutionTimer& owner; + std::atomic periodMillis { 0 }; + steady_clock::time_point nextTickTime; + std::mutex mutex; + std::condition_variable waitEvent; +}; + +HighResolutionTimer::HighResolutionTimer() + : pimpl (new Pimpl (*this)) +{ +} + +HighResolutionTimer::~HighResolutionTimer() +{ + stopTimer(); +} + +void HighResolutionTimer::startTimer (int periodMs) +{ + pimpl->start (jmax (1, periodMs)); +} + +void HighResolutionTimer::stopTimer() +{ + pimpl->stop(); +} + +bool HighResolutionTimer::isTimerRunning() const noexcept { return getTimerInterval() != 0; } +int HighResolutionTimer::getTimerInterval() const noexcept { return pimpl->getPeriod(); } } // namespace juce diff --git a/modules/juce_core/threads/juce_HighResolutionTimer.h b/modules/juce_core/threads/juce_HighResolutionTimer.h index 29ad4fd9d6..2ac6402a93 100644 --- a/modules/juce_core/threads/juce_HighResolutionTimer.h +++ b/modules/juce_core/threads/juce_HighResolutionTimer.h @@ -93,7 +93,7 @@ public: int getTimerInterval() const noexcept; private: - struct Pimpl; + class Pimpl; std::unique_ptr pimpl; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HighResolutionTimer) diff --git a/modules/juce_core/threads/juce_Process.h b/modules/juce_core/threads/juce_Process.h index dfc0c3a23d..61f2157b11 100644 --- a/modules/juce_core/threads/juce_Process.h +++ b/modules/juce_core/threads/juce_Process.h @@ -50,7 +50,7 @@ public: @param priority the process priority, where 0=low, 1=normal, 2=high, 3=realtime */ - static void JUCE_CALLTYPE setPriority (const ProcessPriority priority); + static void JUCE_CALLTYPE setPriority (ProcessPriority priority); /** Kills the current process immediately. diff --git a/modules/juce_core/threads/juce_Thread.cpp b/modules/juce_core/threads/juce_Thread.cpp index 6c981e0afe..8d120235de 100644 --- a/modules/juce_core/threads/juce_Thread.cpp +++ b/modules/juce_core/threads/juce_Thread.cpp @@ -22,9 +22,9 @@ namespace juce { - -Thread::Thread (const String& name, size_t stackSize) - : threadName (name), threadStackSize (stackSize) +//============================================================================== +Thread::Thread (const String& name, size_t stackSize) : threadName (name), + threadStackSize (stackSize) { } @@ -81,12 +81,18 @@ void Thread::threadEntryPoint() const CurrentThreadHolder::Ptr currentThreadHolder (getCurrentThreadHolder()); currentThreadHolder->value = this; + #if JUCE_ANDROID + setPriority (priority); + #endif + if (threadName.isNotEmpty()) setCurrentThreadName (threadName); + // This 'startSuspensionEvent' protects 'threadId' which is initialised after the platform's native 'CreateThread' method. + // This ensures it has been initialised correctly before it reaches this point. if (startSuspensionEvent.wait (10000)) { - jassert (getCurrentThreadId() == threadId.get()); + jassert (getCurrentThreadId() == threadId); if (affinityMask != 0) setCurrentThreadAffinityMask (affinityMask); @@ -119,42 +125,65 @@ void JUCE_API juce_threadEntryPoint (void* userData) } //============================================================================== -void Thread::startThread() +bool Thread::startThreadInternal (Priority threadPriority) { - const ScopedLock sl (startStopLock); + shouldExit = false; - shouldExit = 0; + // 'priority' is essentially useless on Linux as only realtime + // has any options but we need to set this here to satsify + // later queries, otherwise we get inconsistent results across + // platforms. + #if JUCE_LINUX || JUCE_BSD + priority = threadPriority; + #endif - if (threadHandle.get() == nullptr) + if (createNativeThread (threadPriority)) { - launchThread(); - setThreadPriority (threadHandle.get(), threadPriority); startSuspensionEvent.signal(); + return true; } + + return false; } -void Thread::startThread (int priority) +bool Thread::startThread() +{ + return startThread (Priority::normal); +} + +bool Thread::startThread (Priority threadPriority) { const ScopedLock sl (startStopLock); - if (threadHandle.get() == nullptr) + if (threadHandle == nullptr) { - #if JUCE_ANDROID - isAndroidRealtimeThread = (priority == realtimeAudioPriority); - #endif + realtimeOptions.reset(); + return startThreadInternal (threadPriority); + } - threadPriority = getAdjustedPriority (priority); - startThread(); - } - else + return false; +} + +bool Thread::startRealtimeThread (const RealtimeOptions& options) +{ + const ScopedLock sl (startStopLock); + + if (threadHandle == nullptr) { - setPriority (priority); + realtimeOptions = makeOptional (options); + + if (startThreadInternal (Priority::normal)) + return true; + + realtimeOptions.reset(); } + + return false; } bool Thread::isThreadRunning() const { - return threadHandle.get() != nullptr; + return threadHandle != nullptr; } Thread* JUCE_CALLTYPE Thread::getCurrentThread() @@ -164,19 +193,19 @@ Thread* JUCE_CALLTYPE Thread::getCurrentThread() Thread::ThreadID Thread::getThreadId() const noexcept { - return threadId.get(); + return threadId; } //============================================================================== void Thread::signalThreadShouldExit() { - shouldExit = 1; + shouldExit = true; listeners.call ([] (Listener& l) { l.exitSignalSent(); }); } bool Thread::threadShouldExit() const { - return shouldExit.get() != 0; + return shouldExit; } bool Thread::currentThreadShouldExit() @@ -249,40 +278,9 @@ void Thread::removeListener (Listener* listener) listeners.remove (listener); } -//============================================================================== -bool Thread::setPriority (int newPriority) +bool Thread::isRealtime() const { - newPriority = getAdjustedPriority (newPriority); - - // NB: deadlock possible if you try to set the thread prio from the thread itself, - // so using setCurrentThreadPriority instead in that case. - if (getCurrentThreadId() == getThreadId()) - return setCurrentThreadPriority (newPriority); - - const ScopedLock sl (startStopLock); - - #if JUCE_ANDROID - bool isRealtime = (newPriority == realtimeAudioPriority); - - // you cannot switch from or to an Android realtime thread once the - // thread is already running! - jassert (isThreadRunning() && (isRealtime == isAndroidRealtimeThread)); - - isAndroidRealtimeThread = isRealtime; - #endif - - if ((! isThreadRunning()) || setThreadPriority (threadHandle.get(), newPriority)) - { - threadPriority = newPriority; - return true; - } - - return false; -} - -bool Thread::setCurrentThreadPriority (const int newPriority) -{ - return setThreadPriority ({}, getAdjustedPriority (newPriority)); + return realtimeOptions.hasValue(); } void Thread::setAffinityMask (const uint32 newAffinityMask) @@ -290,11 +288,6 @@ void Thread::setAffinityMask (const uint32 newAffinityMask) affinityMask = newAffinityMask; } -int Thread::getAdjustedPriority (int newPriority) -{ - return jlimit (0, 10, newPriority == realtimeAudioPriority ? 9 : newPriority); -} - //============================================================================== bool Thread::wait (const int timeOutMilliseconds) const { @@ -349,7 +342,6 @@ bool JUCE_CALLTYPE Process::isRunningUnderDebugger() noexcept return juce_isRunningUnderDebugger(); } - //============================================================================== //============================================================================== #if JUCE_UNIT_TESTS diff --git a/modules/juce_core/threads/juce_Thread.h b/modules/juce_core/threads/juce_Thread.h index 19634d678c..635daebc28 100644 --- a/modules/juce_core/threads/juce_Thread.h +++ b/modules/juce_core/threads/juce_Thread.h @@ -28,8 +28,8 @@ namespace juce Encapsulates a thread. Subclasses derive from Thread and implement the run() method, in which they - do their business. The thread can then be started with the startThread() method - and controlled with various other methods. + do their business. The thread can then be started with the startThread() or + startRealtimeThread() methods and controlled with various other methods. This class also contains some thread-related static methods, such as sleep(), yield(), getCurrentThreadId() etc. @@ -42,6 +42,46 @@ namespace juce class JUCE_API Thread { public: + //============================================================================== + /** The different runtime priorities of non-realtime threads. + + @see startThread + */ + enum class Priority + { + /** The highest possible priority that isn't a dedicated realtime thread. */ + highest, + + /** Makes use of performance cores and higher clocks. */ + high, + + /** The OS default. It will balance out across all cores. */ + normal, + + /** Uses efficiency cores when possible. */ + low, + + /** Restricted to efficiency cores on platforms that have them. */ + background + }; + + //============================================================================== + /** A selection of options available when creating realtime threads. + + @see startRealtimeThread + */ + struct RealtimeOptions + { + /** Linux only: A value with a range of 0-10, where 10 is the highest priority. */ + int priority = 5; + + /* iOS/macOS only: A millisecond value representing the estimated time between each + 'Thread::run' call. Your thread may be penalised if you frequently + overrun this. + */ + uint32_t workDurationMs = 0; + }; + //============================================================================== /** Creates a thread. @@ -78,23 +118,51 @@ public: virtual void run() = 0; //============================================================================== - /** Starts the thread running. + /** Attempts to start a new thread with default ('Priority::normal') priority. This will cause the thread's run() method to be called by a new thread. If this thread is already running, startThread() won't do anything. + If a thread cannot be created with the requested priority, this will return false + and Thread::run() will not be called. An exception to this is the Android platform, + which always starts a thread and attempts to upgrade the thread after creation. + + @returns true if the thread started successfully. false if it was unsuccesful. + @see stopThread */ - void startThread(); + bool startThread(); - /** Starts the thread with a given priority. + /** Attempts to start a new thread with a given priority. - Launches the thread with a given priority, where 0 = lowest, 10 = highest. - If the thread is already running, its priority will be changed. + This will cause the thread's run() method to be called by a new thread. + If this thread is already running, startThread() won't do anything. - @see startThread, setPriority, realtimeAudioPriority + If a thread cannot be created with the requested priority, this will return false + and Thread::run() will not be called. An exception to this is the Android platform, + which always starts a thread and attempts to upgrade the thread after creation. + + @param newPriority Priority the thread should be assigned. This parameter is ignored + on Linux. + + @returns true if the thread started successfully, false if it was unsuccesful. + + @see startThread, setPriority, startRealtimeThread */ - void startThread (int priority); + bool startThread (Priority newPriority); + + /** Starts the thread with realtime performance characteristics on platforms + that support it. + + You cannot change the options of a running realtime thread, nor switch + a non-realtime thread to a realtime thread. To make these changes you must + first stop the thread and then restart with different options. + + @param options Realtime options the thread should be created with. + + @see startThread, RealtimeOptions + */ + bool startRealtimeThread (const RealtimeOptions& options); /** Attempts to stop the thread running. @@ -198,50 +266,8 @@ public: /** Removes a listener added with addListener. */ void removeListener (Listener*); - //============================================================================== - /** Special realtime audio thread priority - - This priority will create a high-priority thread which is best suited - for realtime audio processing. - - Currently, this priority is identical to priority 9, except when building - for Android with OpenSL/Oboe support. - - In this case, JUCE will ask OpenSL/Oboe to construct a super high priority thread - specifically for realtime audio processing. - - Note that this priority can only be set **before** the thread has - started. Switching to this priority, or from this priority to a different - priority, is not supported under Android and will assert. - - For best performance this thread should yield at regular intervals - and not call any blocking APIs. - - @see startThread, setPriority, sleep, WaitableEvent - */ - enum - { - realtimeAudioPriority = -1 - }; - - /** Changes the thread's priority. - - May return false if for some reason the priority can't be changed. - - @param priority the new priority, in the range 0 (lowest) to 10 (highest). A priority - of 5 is normal. - @see realtimeAudioPriority - */ - bool setPriority (int priority); - - /** Changes the priority of the caller thread. - - Similar to setPriority(), but this static method acts on the caller thread. - May return false if for some reason the priority can't be changed. - - @see setPriority - */ - static bool setCurrentThreadPriority (int priority); + /** Returns true if this Thread represents a realtime thread. */ + bool isRealtime() const; //============================================================================== /** Sets the affinity mask for the thread. @@ -380,34 +406,58 @@ public: static void initialiseJUCE (void* jniEnv, void* jContext); #endif +protected: + //============================================================================== + /** Returns the current priority of this thread. + + This can only be called from the target thread. Doing so from another thread + will cause an assert. + + @see setPriority + */ + Priority getPriority() const; + + /** Attempts to set the priority for this thread. Returns true if the new priority + was set successfully, false if not. + + This can only be called from the target thread. Doing so from another thread + will cause an assert. + + @param newPriority The new priority to be applied to the thread. Note: This + has no effect on Linux platforms, subsequent calls to + 'getPriority' will return this value. + + @see Priority + */ + bool setPriority (Priority newPriority); + private: //============================================================================== const String threadName; - Atomic threadHandle { nullptr }; - Atomic threadId = {}; + std::atomic threadHandle { nullptr }; + std::atomic threadId { nullptr }; + Optional realtimeOptions = {}; CriticalSection startStopLock; WaitableEvent startSuspensionEvent, defaultEvent; - int threadPriority = 5; size_t threadStackSize; uint32 affinityMask = 0; bool deleteOnThreadEnd = false; - Atomic shouldExit { 0 }; + std::atomic shouldExit { false }; ListenerList> listeners; - #if JUCE_ANDROID - bool isAndroidRealtimeThread = false; + #if JUCE_ANDROID || JUCE_LINUX || JUCE_BSD + std::atomic priority; #endif #ifndef DOXYGEN friend void JUCE_API juce_threadEntryPoint (void*); #endif - void launchThread(); + bool startThreadInternal (Priority); + bool createNativeThread (Priority); void closeThreadHandle(); void killThread(); void threadEntryPoint(); - static bool setThreadPriority (void*, int); - static int getAdjustedPriority (int); JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Thread) }; diff --git a/modules/juce_core/threads/juce_ThreadPool.cpp b/modules/juce_core/threads/juce_ThreadPool.cpp index 9b6c446f6d..8639481de6 100644 --- a/modules/juce_core/threads/juce_ThreadPool.cpp +++ b/modules/juce_core/threads/juce_ThreadPool.cpp @@ -33,11 +33,14 @@ struct ThreadPool::ThreadPoolThread : public Thread void run() override { while (! threadShouldExit()) + { if (! pool.runNextJob (*this)) wait (500); + } } std::atomic currentJob { nullptr }; + ThreadPool& pool; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ThreadPoolThread) @@ -90,16 +93,19 @@ ThreadPoolJob* ThreadPoolJob::getCurrentThreadPoolJob() } //============================================================================== -ThreadPool::ThreadPool (int numThreads, size_t threadStackSize) +ThreadPool::ThreadPool (int numThreads, size_t threadStackSize, Thread::Priority priority) { jassert (numThreads > 0); // not much point having a pool without any threads! - createThreads (numThreads, threadStackSize); + for (int i = jmax (1, numThreads); --i >= 0;) + threads.add (new ThreadPoolThread (*this, threadStackSize)); + + for (auto* t : threads) + t->startThread (priority); } -ThreadPool::ThreadPool() +ThreadPool::ThreadPool() : ThreadPool (SystemStats::getNumCpus(), 0, Thread::Priority::normal) { - createThreads (SystemStats::getNumCpus()); } ThreadPool::~ThreadPool() @@ -108,15 +114,6 @@ ThreadPool::~ThreadPool() stopThreads(); } -void ThreadPool::createThreads (int numThreads, size_t threadStackSize) -{ - for (int i = jmax (1, numThreads); --i >= 0;) - threads.add (new ThreadPoolThread (*this, threadStackSize)); - - for (auto* t : threads) - t->startThread(); -} - void ThreadPool::stopThreads() { for (auto* t : threads) @@ -330,17 +327,6 @@ StringArray ThreadPool::getNamesOfAllJobs (bool onlyReturnActiveJobs) const return s; } -bool ThreadPool::setThreadPriorities (int newPriority) -{ - bool ok = true; - - for (auto* t : threads) - if (! t->setPriority (newPriority)) - ok = false; - - return ok; -} - ThreadPoolJob* ThreadPool::pickNextJobToRun() { OwnedArray deletionList; diff --git a/modules/juce_core/threads/juce_ThreadPool.h b/modules/juce_core/threads/juce_ThreadPool.h index 64394010c1..779b1d02fe 100644 --- a/modules/juce_core/threads/juce_ThreadPool.h +++ b/modules/juce_core/threads/juce_ThreadPool.h @@ -164,8 +164,9 @@ public: @param threadStackSize the size of the stack of each thread. If this value is zero then the default stack size of the OS will be used. + @param priority the desired priority of each thread in the pool. */ - ThreadPool (int numberOfThreads, size_t threadStackSize = 0); + ThreadPool (int numberOfThreads, size_t threadStackSize = 0, Thread::Priority priority = Thread::Priority::normal); /** Creates a thread pool with one thread per CPU core. Once you've created a pool, you can give it some jobs by calling addJob(). @@ -309,13 +310,6 @@ public: */ StringArray getNamesOfAllJobs (bool onlyReturnActiveJobs) const; - /** Changes the priority of all the threads. - This will call Thread::setPriority() for each thread in the pool. - May return false if for some reason the priority can't be changed. - */ - bool setThreadPriorities (int newPriority); - - private: //============================================================================== Array jobs; @@ -330,7 +324,6 @@ private: bool runNextJob (ThreadPoolThread&); ThreadPoolJob* pickNextJobToRun(); void addToDeleteList (OwnedArray&, ThreadPoolJob*) const; - void createThreads (int numThreads, size_t threadStackSize = 0); void stopThreads(); // Note that this method has changed, and no longer has a parameter to indicate diff --git a/modules/juce_events/interprocess/juce_ConnectedChildProcess.cpp b/modules/juce_events/interprocess/juce_ConnectedChildProcess.cpp index d7e4b00ad5..dce744f392 100644 --- a/modules/juce_events/interprocess/juce_ConnectedChildProcess.cpp +++ b/modules/juce_events/interprocess/juce_ConnectedChildProcess.cpp @@ -51,7 +51,7 @@ struct ChildProcessPingThread : public Thread, pingReceived(); } - void startPinging() { startThread (4); } + void startPinging() { startThread (Priority::low); } void pingReceived() noexcept { countdown = timeoutMs / 1000 + 1; } void triggerConnectionLostMessage() { triggerAsyncUpdate(); } diff --git a/modules/juce_events/interprocess/juce_NetworkServiceDiscovery.cpp b/modules/juce_events/interprocess/juce_NetworkServiceDiscovery.cpp index f1b006d6a0..add5e52170 100644 --- a/modules/juce_events/interprocess/juce_NetworkServiceDiscovery.cpp +++ b/modules/juce_events/interprocess/juce_NetworkServiceDiscovery.cpp @@ -41,7 +41,7 @@ NetworkServiceDiscovery::Advertiser::Advertiser (const String& serviceTypeUID, message.setAttribute ("address", String()); message.setAttribute ("port", connectionPort); - startThread (2); + startThread (Priority::background); } NetworkServiceDiscovery::Advertiser::~Advertiser() @@ -92,7 +92,7 @@ NetworkServiceDiscovery::AvailableServiceList::AvailableServiceList (const Strin #endif socket.bindToPort (broadcastPort); - startThread (2); + startThread (Priority::background); } NetworkServiceDiscovery::AvailableServiceList::~AvailableServiceList() diff --git a/modules/juce_events/timers/juce_Timer.cpp b/modules/juce_events/timers/juce_Timer.cpp index 3da0766dee..2dde51bc11 100644 --- a/modules/juce_events/timers/juce_Timer.cpp +++ b/modules/juce_events/timers/juce_Timer.cpp @@ -303,7 +303,7 @@ private: void handleAsyncUpdate() override { - startThread (7); + startThread (Priority::high); } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TimerThread) diff --git a/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.cpp b/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.cpp index 0aa38321de..15593e6c71 100644 --- a/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.cpp +++ b/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.cpp @@ -64,7 +64,7 @@ FileBrowserComponent::FileBrowserComponent (int flags_, } // The thread must be started before the DirectoryContentsList attempts to scan any file - thread.startThread (4); + thread.startThread (Thread::Priority::low); fileList.reset (new DirectoryContentsList (this, thread)); fileList->setDirectory (currentRoot, true, true); diff --git a/modules/juce_gui_basics/native/juce_win32_Windowing.cpp b/modules/juce_gui_basics/native/juce_win32_Windowing.cpp index a49e190f89..9f97d07768 100644 --- a/modules/juce_gui_basics/native/juce_win32_Windowing.cpp +++ b/modules/juce_gui_basics/native/juce_win32_Windowing.cpp @@ -1403,7 +1403,7 @@ public: monitor (mon) { listeners.push_back (listener); - startThread (10); + startThread (Priority::highest); } ~VSyncThread() override diff --git a/modules/juce_gui_basics/windows/juce_ThreadWithProgressWindow.cpp b/modules/juce_gui_basics/windows/juce_ThreadWithProgressWindow.cpp index 4ec6b765f8..2cd5ff42da 100644 --- a/modules/juce_gui_basics/windows/juce_ThreadWithProgressWindow.cpp +++ b/modules/juce_gui_basics/windows/juce_ThreadWithProgressWindow.cpp @@ -56,11 +56,11 @@ ThreadWithProgressWindow::~ThreadWithProgressWindow() stopThread (timeOutMsWhenCancelling); } -void ThreadWithProgressWindow::launchThread (int priority) +void ThreadWithProgressWindow::launchThread (Priority threadPriority) { JUCE_ASSERT_MESSAGE_THREAD - startThread (priority); + startThread (threadPriority); startTimer (100); { @@ -105,9 +105,9 @@ void ThreadWithProgressWindow::timerCallback() void ThreadWithProgressWindow::threadComplete (bool) {} #if JUCE_MODAL_LOOPS_PERMITTED -bool ThreadWithProgressWindow::runThread (const int priority) +bool ThreadWithProgressWindow::runThread (Priority threadPriority) { - launchThread (priority); + launchThread (threadPriority); while (isTimerRunning()) MessageManager::getInstance()->runDispatchLoopUntil (5); diff --git a/modules/juce_gui_basics/windows/juce_ThreadWithProgressWindow.h b/modules/juce_gui_basics/windows/juce_ThreadWithProgressWindow.h index 0e6c3c42e2..b0d6ed059c 100644 --- a/modules/juce_gui_basics/windows/juce_ThreadWithProgressWindow.h +++ b/modules/juce_gui_basics/windows/juce_ThreadWithProgressWindow.h @@ -122,10 +122,10 @@ public: Before returning, the dialog box will be hidden. @param priority the priority to use when starting the thread - see - Thread::startThread() for values + Thread::Priority for values @returns true if the thread finished normally; false if the user pressed cancel */ - bool runThread (int priority = 5); + bool runThread (Priority Priority = Priority::normal); #endif /** Starts the thread and returns. @@ -135,9 +135,9 @@ public: hidden and the threadComplete() method will be called. @param priority the priority to use when starting the thread - see - Thread::startThread() for values + Thread::Priority for values */ - void launchThread (int priority = 5); + void launchThread (Priority priority = Priority::normal); /** The thread should call this periodically to update the position of the progress bar. diff --git a/modules/juce_product_unlocking/marketplace/juce_OnlineUnlockForm.cpp b/modules/juce_product_unlocking/marketplace/juce_OnlineUnlockForm.cpp index 9872a08057..90dda0546b 100644 --- a/modules/juce_product_unlocking/marketplace/juce_OnlineUnlockForm.cpp +++ b/modules/juce_product_unlocking/marketplace/juce_OnlineUnlockForm.cpp @@ -58,7 +58,7 @@ struct OnlineUnlockForm::OverlayComp : public Component, cancelButton->addListener (this); } - startThread (4); + startThread (Priority::normal); } ~OverlayComp() override