Skip to content

Commit 2dca7ea

Browse files
committed
Add NOTEAHEAD_AUDIO_WORKERS override for the audio pool
1 parent 4171938 commit 2dca7ea

4 files changed

Lines changed: 47 additions & 1 deletion

File tree

CHANGELOG

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,12 @@ Bug fixes:
7070

7171
Other:
7272

73+
* Add NOTEAHEAD_AUDIO_WORKERS env var to control audio worker threads
74+
- Set to 0 to process devices serially on the audio thread; useful as a
75+
workaround (and for diagnosis) when worker threads cannot get real-time
76+
scheduling and audio stutters under load
77+
- Warn in the log when real-time priority cannot be set for worker threads
78+
7379
* Double the maximum number of devices and effects per rack to 16
7480

7581
* Update README's device and effect listings

src/infra/audio/real_time_worker_pool.cpp

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,16 @@
1818
#include "../../contrib/SimpleLogger/src/simple_logger.hpp"
1919

2020
#include <algorithm>
21+
#include <cstdlib>
2122
#include <pthread.h>
2223

2324
namespace noteahead {
2425

2526
RealTimeWorkerPool::RealTimeWorkerPool(size_t workerCount)
2627
{
28+
juzzlin::L("RealTimeWorkerPool").info() << "Starting with " << workerCount << " worker thread(s)"
29+
<< (workerCount == 0 ? " (serial processing on the audio thread)" : "");
30+
2731
m_startSemaphores.reserve(workerCount);
2832
m_workers.reserve(workerCount);
2933

@@ -43,7 +47,9 @@ RealTimeWorkerPool::RealTimeWorkerPool(size_t workerCount)
4347
struct sched_param param;
4448
param.sched_priority = 80; // High priority for audio
4549
if (pthread_setschedparam(pthread_self(), SCHED_FIFO, &param) != 0) {
46-
juzzlin::L("RealTimeWorkerPool").warning() << "Failed to set RT priority for " << threadName;
50+
juzzlin::L("RealTimeWorkerPool").warning() << "Failed to set RT priority for " << threadName
51+
<< " (needs rtprio limits, e.g. the 'audio' group); "
52+
<< "audio may stutter under load. Set NOTEAHEAD_AUDIO_WORKERS=0 to disable the pool.";
4753
}
4854

4955
workerLoop(i);
@@ -117,6 +123,22 @@ void RealTimeWorkerPool::run(size_t taskCount, void * context, TaskCallback call
117123
size_t RealTimeWorkerPool::defaultWorkerCount()
118124
{
119125
const auto hardwareThreads = std::thread::hardware_concurrency();
126+
127+
// Allow overriding the worker count via the environment. NOTEAHEAD_AUDIO_WORKERS=0 disables the
128+
// pool entirely (all device processing runs serially on the audio thread), which avoids the
129+
// worker wakeup latency that can cause stutter when the worker threads cannot obtain real-time
130+
// scheduling. Useful both as a workaround and for diagnosing threading-related audio glitches.
131+
if (const char * env = std::getenv("NOTEAHEAD_AUDIO_WORKERS"); env && *env) {
132+
char * end = nullptr;
133+
const long requested = std::strtol(env, &end, 10);
134+
if (end != env && requested >= 0) {
135+
const auto clamped = std::min<long>(requested, hardwareThreads > 0 ? hardwareThreads : requested);
136+
juzzlin::L("RealTimeWorkerPool").info() << "Worker count overridden via NOTEAHEAD_AUDIO_WORKERS: " << clamped;
137+
return static_cast<size_t>(clamped);
138+
}
139+
juzzlin::L("RealTimeWorkerPool").warning() << "Ignoring invalid NOTEAHEAD_AUDIO_WORKERS value: " << env;
140+
}
141+
120142
if (hardwareThreads <= 1) {
121143
return 0;
122144
}

src/unit_tests/real_time_worker_pool_test/real_time_worker_pool_test.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,23 @@ void RealTimeWorkerPoolTest::test_singleTaskUsesCallerThread_shouldExecuteOnCurr
7979
QCOMPARE(context.callerThreadTasks.load(std::memory_order_relaxed), 1);
8080
}
8181

82+
void RealTimeWorkerPoolTest::test_defaultWorkerCount_envOverride_shouldBeRespected()
83+
{
84+
qputenv("NOTEAHEAD_AUDIO_WORKERS", "0");
85+
QCOMPARE(RealTimeWorkerPool::defaultWorkerCount(), static_cast<size_t>(0));
86+
87+
qputenv("NOTEAHEAD_AUDIO_WORKERS", "1");
88+
QCOMPARE(RealTimeWorkerPool::defaultWorkerCount(), static_cast<size_t>(1));
89+
90+
// Invalid values fall back to the computed hardware-based default.
91+
qunsetenv("NOTEAHEAD_AUDIO_WORKERS");
92+
const auto computedDefault = RealTimeWorkerPool::defaultWorkerCount();
93+
qputenv("NOTEAHEAD_AUDIO_WORKERS", "not-a-number");
94+
QCOMPARE(RealTimeWorkerPool::defaultWorkerCount(), computedDefault);
95+
96+
qunsetenv("NOTEAHEAD_AUDIO_WORKERS");
97+
}
98+
8299
} // namespace noteahead
83100

84101
QTEST_GUILESS_MAIN(noteahead::RealTimeWorkerPoolTest)

src/unit_tests/real_time_worker_pool_test/real_time_worker_pool_test.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class RealTimeWorkerPoolTest : public QObject
2727
private slots:
2828
void test_runExecutesEveryTaskOnce_shouldCompleteAllTasks();
2929
void test_singleTaskUsesCallerThread_shouldExecuteOnCurrentThread();
30+
void test_defaultWorkerCount_envOverride_shouldBeRespected();
3031
};
3132

3233
} // namespace noteahead

0 commit comments

Comments
 (0)