Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent data races in napaudio #31

Merged
merged 18 commits into from
Jul 30, 2024

Conversation

CasimirGeelhoed
Copy link
Contributor

@CasimirGeelhoed CasimirGeelhoed commented Jul 9, 2024

These changes prevent data races warnings that were reported by the Thread Sanitizer when running our 4DSOUND software.

Changes in napaudio:

  • Added atomics to members of LinearSmoothedValue and RampedValue that are often set by non-audio threads.
  • The LevelMeterNode is rewritten so it doesn't trigger data race warnings, by calculating on the audio thread and storing the result in a single atomic float. To keep the cpu load stable, the algorithms are rewritten so that calculations are happening continuously instead of in chuncks.
  • InputPinBase::connect(), InputPinBase::disconnect() and InputPinBase::disconnectAll() will now enqueue their connections, so they can be safely called form the main (or in our case: control) thread.

Change in napmath:

  • Made the std::mt19937 generator in mathutils 'thread_local', so math::random() can safely be called from multiple threads at the same time.

@cklosters
Copy link
Member

cklosters commented Jul 10, 2024

Thanks for this change, looking good! Unfortunately all builds are currently failing (GCC & MSVC) with the following message, which should be trivial to fix:

[00:02:36]
[Step 1/1] [ 27%] Building CXX object system_modules/napaudio/CMakeFiles/napaudio.dir/src/audio/core/audionode.cpp.o
[00:02:37]
[Step 1/1] In file included from /home/cklosters/agent/work/a62e0057f2f90802/nap/system_modules/napaudio/src/audio/core/audionode.h:16,
[00:02:37]
[Step 1/1]                  from /home/cklosters/agent/work/a62e0057f2f90802/nap/system_modules/napaudio/src/audio/core/audionode.cpp:5:
[00:02:37]
[Step 1/1] /home/cklosters/agent/work/a62e0057f2f90802/nap/system_modules/napaudio/src/audio/core/audiopin.h:129:9: error: ‘void nap::audio::InputPin::disconnectAll()’ marked ‘override’, but does not override
[00:02:37]
[Step 1/1]   129 |    void disconnectAll() override;
[00:02:37]
[Step 1/1]       |         ^~~~~~~~~~~~~
[00:02:37]
[Step 1/1] /home/cklosters/agent/work/a62e0057f2f90802/nap/system_modules/napaudio/src/audio/core/audiopin.h:134:9: error: ‘bool nap::audio::InputPin::isConnected() const’ marked ‘override’, but does not override
[00:02:37]
[Step 1/1]   134 |    bool isConnected() const override { return mInput != nullptr; }
[00:02:37]
[Step 1/1]       |         ^~~~~~~~~~~
[00:02:37]
[Step 1/1] /home/cklosters/agent/work/a62e0057f2f90802/nap/system_modules/napaudio/src/audio/core/audiopin.h:175:9: error: ‘void nap::audio::MultiInputPin::connect(nap::audio::OutputPin&)’ marked ‘override’, but does not override
[00:02:37]
[Step 1/1]   175 |    void connect(OutputPin& input) override;
[00:02:37]
[Step 1/1]       |         ^~~~~~~
[00:02:37]
[Step 1/1] /home/cklosters/agent/work/a62e0057f2f90802/nap/system_modules/napaudio/src/audio/core/audiopin.h:181:9: error: ‘void nap::audio::MultiInputPin::disconnect(nap::audio::OutputPin&)’ marked ‘override’, but does not override
[00:02:37]
[Step 1/1]   181 |    void disconnect(OutputPin& input) override;
[00:02:37]
[Step 1/1]       |         ^~~~~~~~~~
[00:02:37]
[Step 1/1] /home/cklosters/agent/work/a62e0057f2f90802/nap/system_modules/napaudio/src/audio/core/audiopin.h:198:9: error: ‘void nap::audio::MultiInputPin::disconnectAll()’ marked ‘override’, but does not override
[00:02:37]
[Step 1/1]   198 |    void disconnectAll() override;
[00:02:37]
[Step 1/1]       |         ^~~~~~~~~~~~~
[00:02:37]
[Step 1/1] /home/cklosters/agent/work/a62e0057f2f90802/nap/system_modules/napaudio/src/audio/core/audiopin.h:203:9: error: ‘bool nap::audio::MultiInputPin::isConnected() const’ marked ‘override’, but does not override
[00:02:37]
[Step 1/1]   203 |    bool isConnected() const override { return !mInputs.empty(); }
[00:02:37]
[Step 1/1]       |         ^~~~~~~~~~~
[00:02:38]
[Step 1/1] make[2]: *** [system_modules/napaudio/CMakeFiles/napaudio.dir/build.make:90: system_modules/napaudio/CMakeFiles/napaudio.dir/src/audio/core/audionode.cpp.o] Error 1
[00:02:38]
[Step 1/1] make[1]: *** [CMakeFiles/Makefile2:4006: system_modules/napaudio/CMakeFiles/napaudio.dir/all] Error 2
[00:02:38]
[Step 1/1] make[1]: *** Waiting for unfinished jobs....

@CasimirGeelhoed
Copy link
Contributor Author

CasimirGeelhoed commented Jul 10, 2024

Sorry, something went wrong merging audiopin.h during the git rebase, it should be fixed now.

There is one more change I should note: we decided to remove the InputPinBase::isConnected() method, as its result would be unpredictable when (dis)connections are enqueued on the audio thread (for example, calling isConnected() directly after calling disconnectAll() on the main thread could still return true). This is a potentially breaking change.

@cklosters
Copy link
Member

Build keeps failing unfortunately, I recommend building your changes on a linux / windows machine to validate your changes:

[01:05:38]
[Step 1/1] /home/cklosters/agent/work/a62e0057f2f90802/nap/system_modules/napsequenceaudio/src/sequenceplayeraudiooutput.cpp: In member function ‘virtual bool nap::SequencePlayerAudioOutput::init(nap::utility::ErrorState&)’:
[01:05:38]
[Step 1/1] /home/cklosters/agent/work/a62e0057f2f90802/nap/system_modules/napsequenceaudio/src/sequenceplayeraudiooutput.cpp:57:41: error: ‘class nap::audio::InputPin’ has no member named ‘enqueueConnect’
[01:05:38]
[Step 1/1]    57 |                 output_node->audioInput.enqueueConnect(mix_nodes[i]->audioOutput);
[01:05:38]
[Step 1/1]       |                                         ^~~~~~~~~~~~~~
[01:05:38]
[Step 1/1] /home/cklosters/agent/work/a62e0057f2f90802/nap/system_modules/napsequenceaudio/src/sequenceplayeraudiooutput.cpp: In member function ‘void nap::SequencePlayerAudioOutput::registerAdapter(const nap::SequencePlayerAudioAdapter*)’:
[01:05:38]
[Step 1/1] /home/cklosters/agent/work/a62e0057f2f90802/nap/system_modules/napsequenceaudio/src/sequenceplayeraudiooutput.cpp:119:38: error: ‘class nap::audio::MultiInputPin’ has no member named ‘enqueueConnect’
[01:05:38]
[Step 1/1]   119 |                 mMixNodes[i]->inputs.enqueueConnect(*buffer_player->getOutputPins()[i]);
[01:05:38]
[Step 1/1]       |                                      ^~~~~~~~~~~~~~
[01:05:38]
[Step 1/1] /home/cklosters/agent/work/a62e0057f2f90802/nap/system_modules/napsequenceaudio/src/sequenceplayeraudiooutput.cpp: In member function ‘void nap::SequencePlayerAudioOutput::unregisterAdapter(const nap::SequencePlayerAudioAdapter*)’:
[01:05:38]
[Step 1/1] /home/cklosters/agent/work/a62e0057f2f90802/nap/system_modules/napsequenceaudio/src/sequenceplayeraudiooutput.cpp:149:42: error: ‘class nap::audio::MultiInputPin’ has no member named ‘enqueueDisconnect’
[01:05:38]
[Step 1/1]   149 |                     mMixNodes[i]->inputs.enqueueDisconnect(*output_pins[i]);
[01:05:38]
[Step 1/1]       |                                          ^~~~~~~~~~~~~~~~~
[01:05:38]
[Step 1/1] /home/cklosters/agent/work/a62e0057f2f90802/nap/system_modules/napsequenceaudio/src/sequenceplayeraudiooutput.cpp: In member function ‘void nap::SequencePlayerAudioOutput::connectInputPin(nap::audio::InputPin&, int)’:
[01:05:38]
[Step 1/1] /home/cklosters/agent/work/a62e0057f2f90802/nap/system_modules/napsequenceaudio/src/sequenceplayeraudiooutput.cpp:173:18: error: ‘class nap::audio::InputPin’ has no member named ‘enqueueConnect’
[01:05:38]
[Step 1/1]   173 |         inputPin.enqueueConnect(mMixNodes[channel]->audioOutput);
[01:05:38]
[Step 1/1]       |                  ^~~~~~~~~~~~~~
[01:05:38]
[Step 1/1] /home/cklosters/agent/work/a62e0057f2f90802/nap/system_modules/napsequenceaudio/src/sequenceplayeraudiooutput.cpp: In member function ‘void nap::SequencePlayerAudioOutput::disconnectInputPin(nap::audio::InputPin&, int)’:
[01:05:38]
[Step 1/1] /home/cklosters/agent/work/a62e0057f2f90802/nap/system_modules/napsequenceaudio/src/sequenceplayeraudiooutput.cpp:180:18: error: ‘class nap::audio::InputPin’ has no member named ‘enqueueDisconnect’
[01:05:38]
[Step 1/1]   180 |         inputPin.enqueueDisconnect(mMixNodes[channel]->audioOutput);
[01:05:38]
[Step 1/1]       |                  ^~~~~~~~~~~~~~~~~

@cklosters
Copy link
Member

It has been 2 weeks and no changes - the build is still broken. Do you expect to fix the builld for this review to continue or should I close it?

Quick note on our review policy: We welcome changes but the changes must pass the build checks in order for them to be reviewed in the first place, that's what the build system is for: it ensures your code doesn't break core functionality, which it does. Simply letting the PR hang isn't very good practice either: close it if you don't wish to continue or notify us when you do.

If no changes are submitted and @stijnvanbeek hasn't reviewed this change in the next two weeks I will close this PR - feel free to open a new one when ready.

@stijnvanbeek
Copy link
Contributor

Will look at it this week!

@CasimirGeelhoed
Copy link
Contributor Author

It has been 2 weeks and no changes - the build is still broken. Do you expect to fix the builld for this review to continue or should I close it?

Quick note on our review policy: We welcome changes but the changes must pass the build checks in order for them to be reviewed in the first place, that's what the build system is for: it ensures your code doesn't break core functionality, which it does. Simply letting the PR hang isn't very good practice either: close it if you don't wish to continue or notify us when you do.

If no changes are submitted and @stijnvanbeek hasn't reviewed this change in the next two weeks I will close this PR - feel free to open a new one when ready.

Hey Coen, of course, totally understand this policy. Apologies for the delay & silence, @stijnvanbeek will review it this week. For next time, I'll make sure I can test all targets on Windows myself before making a PR.

@cklosters
Copy link
Member

Hey Coen, of course, totally understand this policy. Apologies for the delay & silence, @stijnvanbeek will review it this week. For next time, I'll make sure I can test all targets on Windows myself before making a PR.

Thanks for taking the time to fix the build, all checks pass including package validation (where all demos are tested in every environment). So probably good if @stijnvanbeek reviews this code, I'll also take a look at it after he is done, merci!

Copy link
Contributor

@stijnvanbeek stijnvanbeek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the sum buffer in the LevelMeterNode can be refactored away.
For the rest all good!

@stijnvanbeek stijnvanbeek dismissed their stale review July 25, 2024 11:17

I was wrooooong :)

@stijnvanbeek
Copy link
Contributor

I understand the use of the sum buffer now, so all great to me.

@stijnvanbeek
Copy link
Contributor

Only thing important to note is to mention the breaking changes:

  • enqueConnect() and enqueDisconnect() no loinger exist and need to be replaced by just connect() and disconnect()
  • the isConnected() function is removed as the result is unreliable now in a multithreading context and its existence is unnecessary because users can keep track of this themselves.

@cklosters cklosters added enhancement New feature or request audio Audio related Questions & Issues labels Jul 29, 2024
Copy link
Member

@cklosters cklosters left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good change, thanks & congrats on your new contributor status ;) See comments for future PRs

for (auto& sample : *inputBuffer)
{
// keep track of peak, sample by sample
float newValue = sample;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For future reference, local variables are never camelCase, they are separated using an underscore: float new_value , this allows you to differentiate between arguments and local variables, which is currently not the case. This is part of the official style guide.

@@ -27,7 +27,7 @@ namespace nap

static std::mt19937& getGenerator()
{
static std::unique_ptr<std::mt19937> generator = nullptr;
static thread_local std::unique_ptr<std::mt19937> generator = nullptr;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this is ok, assuming we want different random sequences for every thread. I can't think of a situation where this would be a problem atm, but considering the current implementation is not thread safe this solutions outweighs the other.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interestingly, declaring something static in the cpp (such as the generator) is thread safe since C++ 11. Also, looking at the implementation it can be simplified to:

		static std::mt19937& getGenerator()
		{
			static thread_local std::mt19937 generator(time(0));
			return generator;
		}

I'll implement and test that some other time.

@cklosters cklosters merged commit 94321e6 into napframework:main Jul 30, 2024
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
audio Audio related Questions & Issues enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants