-
Notifications
You must be signed in to change notification settings - Fork 149
Fixes for shutting down during async operations #1141
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
Open
bghgary
wants to merge
25
commits into
BabylonJS:master
Choose a base branch
from
bghgary:async-fixes
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 12 commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
43dce92
WIP: Fixes for shutting down during async operations
bghgary 925d243
Change to explicitly call Rundown
bghgary c999153
Cannot throw in destructors
bghgary 204efb9
Merge remote-tracking branch 'origin/master' into async-fixes
bghgary 9d1e098
Fix merge issues
bghgary 124def4
Merge with new timeout code with work queue fixes
bghgary 309c4d9
Fix Canvas
bghgary ad88d30
Fix Android build
bghgary 11ca510
Merge remote-tracking branch 'origin/master' into async-fixes
bghgary 31248b6
Merge branch 'async-fixes' of https://github.com/bghgary/BabylonNativ…
bghgary 9322360
Update arcana.cpp
bghgary b4a3c73
Temp fixes for MediaStream
bghgary 9305ca8
Add missing std::forward calls for perfect forwarding
bghgary df246e2
Work queue shutdown fixes
bghgary 7350715
Miscellaneous Windows AppRuntime fixes
bghgary 77dc8f3
Fix typo in AppRuntime for Win32
bghgary def047c
Better fix for work queue shutdown issue
bghgary bdaf1b9
Merge remote-tracking branch 'origin/master' into async-fixes
bghgary 0ae6cf4
Fix build issues from merge
bghgary 71efaf4
Update arcana.cpp to include continuation fix
bghgary a0db419
Minor style fixes
bghgary 6909f55
Update comment
bghgary e9d229d
Update comment 2
bghgary 423ed5e
More style fixes
bghgary 36a4661
Merge AppRuntime and WorkQueue to fix race condition
bghgary File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,30 +1,146 @@ | ||
#pragma once | ||
|
||
#include "JsRuntime.h" | ||
#include <arcana/threading/dispatcher.h> | ||
#include <atomic> | ||
#include <cassert> | ||
|
||
namespace Babylon | ||
{ | ||
/** | ||
* Scheduler that invokes continuations via JsRuntime::Dispatch. | ||
* Intended to be consumed by arcana.cpp tasks. | ||
*/ | ||
class JsRuntimeScheduler | ||
// This class encapsulates a coding pattern for invoking continuations on the JavaScript thread while properly | ||
// handling garbage collection and shutdown scenarios. This class provides and manages the schedulers intended | ||
// for a N-API object to use with arcana tasks. It is different than the typical scheduler as this class itself | ||
// is not a scheduler directly, but instead hands out scheduler via its `Get()` function. It provides special | ||
// handling for when the JsRuntime begins shutting down, i.e., when JsRuntime::NotifyDisposing is called. | ||
// 1. Calling `Rundown` blocks execution until all outstanding schedulers are invoked on the JavaScript thread. | ||
// 2. Once the JsRuntime begins shutting down, all schedulers will reroute its dispatch calls from the | ||
// JsRuntime to a separate dispatcher owned by the JsRuntimeScheduler itself. This class will then be able | ||
// to pump this dispatcher in its destructor to prevent deadlocks. | ||
// | ||
// The typical pattern for an arcana task will look something like this: | ||
// class MyClass | ||
bghgary marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// { | ||
// public: | ||
// ~MyClass() | ||
// { | ||
// m_cancellationSource.cancel(); | ||
// | ||
// // Wait for asynchronous operations to complete. | ||
// m_runtimeScheduler.Rundown(); | ||
// } | ||
// | ||
// void MyFunction(const Napi::CallbackInfo& info) | ||
// { | ||
// const auto callback{info[0].As<Napi::Function>()}; | ||
// | ||
// arcana::make_task(arcana::threadpool_scheduler, m_cancellationSource, []() { | ||
// // do some asynchronous work | ||
// }).then(m_runtimeScheduler.Get(), m_cancellationSource, [thisRef = Napi::Persistent(info.This()), callback = Napi::Persistent(callback)]() { | ||
// callback.Call({}); | ||
// }); | ||
// } | ||
// | ||
// private: | ||
// arcana::cancellation_source m_cancellationSource; | ||
// JsRuntimeScheduler m_runtimeScheduler; | ||
// }; | ||
// | ||
// **IMPORTANT**: | ||
// 1. To prevent continuations from accessing freed memory, the destructor of the N-API class is expected to call | ||
// `Rundown()` which blocks execution until all of its schedulers are invoked. Failing to do so will result in | ||
// an assert if there are outstanding schedulers not yet invoked. | ||
// 2. The last continuation that accesses members of the N-API object, including the cancellation associated with | ||
// the continuation, must capture a persistent reference to the N-API object itself to prevent the GC from | ||
// collecting the N-API object during the asynchronous operation. Failing to do so will result in a hang | ||
// when `Rundown()` is called in the N-API class destructor. | ||
class JsRuntimeScheduler : public JsRuntime::IDisposingCallback | ||
{ | ||
public: | ||
explicit JsRuntimeScheduler(JsRuntime& runtime) | ||
: m_runtime{runtime} | ||
: m_runtime{&runtime} | ||
, m_scheduler{*this} | ||
{ | ||
std::scoped_lock lock{m_mutex}; | ||
JsRuntime::RegisterDisposing(*m_runtime, this); | ||
} | ||
|
||
template<typename CallableT> | ||
void operator()(CallableT&& callable) const | ||
~JsRuntimeScheduler() | ||
{ | ||
assert(m_count == 0 && "Schedulers for the JavaScript thread are not yet invoked"); | ||
|
||
std::scoped_lock lock{m_mutex}; | ||
if (m_runtime != nullptr) | ||
{ | ||
JsRuntime::UnregisterDisposing(*m_runtime, this); | ||
} | ||
} | ||
|
||
// Wait until all of the schedulers are invoked. | ||
void Rundown() | ||
{ | ||
m_runtime.Dispatch([callable{std::forward<CallableT>(callable)}](Napi::Env) { | ||
callable(); | ||
}); | ||
while (m_count > 0) | ||
{ | ||
m_disposingDispatcher.blocking_tick(arcana::cancellation::none()); | ||
} | ||
} | ||
|
||
// Get a scheduler to invoke continuations on the JavaScript thread. | ||
const auto& Get() | ||
{ | ||
++m_count; | ||
return m_scheduler; | ||
} | ||
|
||
private: | ||
JsRuntime& m_runtime; | ||
void Disposing() override | ||
{ | ||
std::scoped_lock lock{m_mutex}; | ||
m_runtime = nullptr; | ||
} | ||
|
||
class SchedulerImpl | ||
{ | ||
public: | ||
explicit SchedulerImpl(JsRuntimeScheduler& parent) : m_parent{parent} | ||
{ | ||
} | ||
|
||
template<typename CallableT> | ||
void operator()(CallableT&& callable) const | ||
{ | ||
m_parent.Dispatch(callable); | ||
} | ||
|
||
private: | ||
JsRuntimeScheduler& m_parent; | ||
}; | ||
|
||
template<typename CallableT> | ||
void Dispatch(CallableT&& callable) | ||
{ | ||
std::scoped_lock lock{m_mutex}; | ||
|
||
if (m_runtime != nullptr) | ||
{ | ||
m_runtime->Dispatch([callable{std::forward<CallableT>(callable)}](Napi::Env) | ||
{ | ||
callable(); | ||
}); | ||
} | ||
else | ||
{ | ||
m_disposingDispatcher(callable); | ||
} | ||
|
||
--m_count; | ||
} | ||
|
||
mutable std::mutex m_mutex{}; | ||
JsRuntime* m_runtime{}; | ||
std::function<void()> m_disposingCallback{}; | ||
|
||
SchedulerImpl m_scheduler; | ||
std::atomic<int> m_count{0}; | ||
arcana::manual_dispatcher<128> m_disposingDispatcher{}; | ||
}; | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.