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

Fix #277 - Stack overflow in Bin Mon message handler #278

Merged
merged 4 commits into from
May 15, 2024

Conversation

firthm01
Copy link
Contributor

As per #277, a crash would occur (probably only on windows) if the AdmPresetDefinitionsHandler was first instantiated as a singleton via an NNG message, due to the NNG threads having a small stack. We had previously overcome a similar issue in the LS monitoring plugins by wrapping SceneGainsCalculator::update code in a thread. I used a similar fix here, but it made sense for the thread launching to occur further up the call stack since for both the LS mons and Bin mon, the message handler call stems from MonitoringMetadataReceiver::handleReceive

LS mon call stack from message received to SceneGainsCalculator::update where a new thread would be launched:

	EAR Monitoring 9+10+3.vst3!ear::plugin::SceneGainsCalculator::update(ear::plugin::proto::SceneStore store) Line 56
 	EAR Monitoring 9+10+3.vst3!ear::plugin::MonitoringBackend::updateActiveGains(ear::plugin::proto::SceneStore store) Line 63
 	EAR Monitoring 9+10+3.vst3!ear::plugin::MonitoringBackend::onSceneReceived(ear::plugin::proto::SceneStore store) Line 52
 	[External Code]	
 	EAR Monitoring 9+10+3.vst3!ear::plugin::communication::MonitoringMetadataReceiver::handleReceive::__l9::<lambda_1>::operator()() Line 70
 	[External Code]	
 	EAR Binaural Monitoring.vst3!nng::detail::AsyncReadAction::operator()() Line 261
 	[External Code]	
 	EAR Binaural Monitoring.vst3!nng::AsyncIO::callback() Line 227

Bin mon call stack from message received to stack overflow (still on NNG thread)

	EAR Binaural Monitoring.vst3!__chkstk() Line 109	
 	EAR Binaural Monitoring.vst3!adm::xml::XmlParser::parse() Line 43
 	EAR Binaural Monitoring.vst3!adm::getCommonDefinitions() Line 163	
 	EAR Binaural Monitoring.vst3!adm::parseXml(std::basic_istream<char,std::char_traits<char>> & stream, adm::xml::ParserOptions options) Line 18
 	EAR Binaural Monitoring.vst3!AdmPresetDefinitionsHelper::AdmPresetDefinitionsHelper() Line 54
 	EAR Binaural Monitoring.vst3!AdmPresetDefinitionsHelper::getSingleton() Line 65
 	EAR Binaural Monitoring.vst3!ear::plugin::BinauralMonitoringBackend::onSceneReceived(ear::plugin::proto::SceneStore store) Line 228
 	[External Code]	
	EAR Binaural Monitoring.vst3!ear::plugin::communication::MonitoringMetadataReceiver::handleReceive(std::error_code ec, nng::Message message) Line 65
 	[External Code]	
 	EAR Binaural Monitoring.vst3!nng::detail::AsyncReadAction::operator()() Line 261
 	[External Code]	
 	EAR Binaural Monitoring.vst3!nng::AsyncIO::callback() Line 227

Fix was to launch message handlers in new threads from the common MonitoringMetadataReceiver::handleReceive method.

Closes #277

This ensures message handlers for both Bin mon and LS mons are launched in a new thread to overcome NNG stack limits
@firthm01 firthm01 requested a review from rsjbailey May 13, 2024 11:51
@firthm01 firthm01 self-assigned this May 13, 2024
// Called by NNG callback on thread with small stack.
// Launch task in another thread to overcome stack limitation.
auto future = std::async(std::launch::async, [this, sceneStore]() {
handler_(sceneStore);
Copy link
Contributor Author

@firthm01 firthm01 May 13, 2024

Choose a reason for hiding this comment

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

This bit isn't ideal. I think this causes a copy. Using std::move makes the compiler complain because sceneStore is apparently a const here... not sure if that happens as part of the capture list?
I think as future.get() is blocking, we are safe in terms of lifetime.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I believe this is fixed by d216065

Copy link
Contributor

@rsjbailey rsjbailey May 14, 2024

Choose a reason for hiding this comment

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

Would marking the lambda as mutable fix the const warning? I can't get a small example that looks the same to emit that warning so I'm guessing a bit. I should probably check out the project and try it properly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just to clarify as the later commit makes my message confusing. This code causes a compiler warning;

auto future = std::async(std::launch::async, [this, sceneStore]() {
    handler_(std::move(sceneStore));
});

The current code (below) it's happy with, but I'm not sure if this still causes a copy from the capture list to passing in to handle_ ;

auto future = std::async(std::launch::async, [this, sceneStore = std::move(sceneStore)]() {
    handler_(sceneStore);
});

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, I think the mutable thing fixes it, so

auto future = std::async(std::launch::async, [this, sceneStore = std::move(sceneStore)]() mutable {
  handler_(std::move(sceneStore));
}

I'm a bit rusty, but I think under the hood a lambda creates a function object with the captures as data members, and by default the call operator is const so that repeated calls to the same lambda with the same arguments produce the same results, so it's as if you did

struct MyLambda {
  void operator()() const {
    that->handler(std::move(sceneStore)); // won't compile as sceneStore is const on a const object
  }
  MonitoringMetadataReceiver* that;
  proto::SceneStore sceneStore;
};

MonitoringMetadataReceiver::handleReceive() {
// snip ...
  MyLambda const lambda{this, std::move(sceneStore)};
  auto future = std::async(std::launch::async, lambda);
}

The mutable keyword just removes the consts

Bit weird, but if anything, the rest of the language is wrong and lambdas have the correct defaults.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ahh thanks for the explanation - that makes sense why it complains!

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure why the handler doesn't just take a const reference, maybe it should? (I've not checked to see what it's doing at the other end, if it needs to keep hold of a copy then passing by value makes sense)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed to const refs in ab7adb9

Copy link
Contributor

Choose a reason for hiding this comment

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

Looks good, could capture by reference in the async lambda too (as we're immediately blocking on it's completion)

auto future = std::async(std::launch::async, [this, &sceneStore]() {
    handler_(sceneStore);
});
future.get(); //blocking

@firthm01 firthm01 merged commit 2b312be into main May 15, 2024
7 checks passed
@firthm01 firthm01 deleted the fix277-stack-overflow branch May 15, 2024 12:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Stack overflow if AdmPresetDefinitionsHelper first inited on NNG thread
2 participants