-
Notifications
You must be signed in to change notification settings - Fork 133
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge AppRuntime and WorkQueue to fix race condition
- Loading branch information
Showing
18 changed files
with
243 additions
and
244 deletions.
There are no files selected for viewing
This file contains 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 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 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,65 +1,36 @@ | ||
#include "AppRuntime.h" | ||
#include "WorkQueue.h" | ||
#include <Babylon/JsRuntime.h> | ||
#include "AppRuntimeImpl.h" | ||
|
||
namespace Babylon | ||
{ | ||
AppRuntime::AppRuntime() | ||
: AppRuntime{DefaultUnhandledExceptionHandler} | ||
: m_impl{std::make_unique<AppRuntimeImpl>()} | ||
{ | ||
} | ||
|
||
AppRuntime::AppRuntime(std::function<void(const std::exception&)> unhandledExceptionHandler) | ||
: m_workQueue{std::make_unique<WorkQueue>([this] { RunPlatformTier(); })} | ||
, m_unhandledExceptionHandler{unhandledExceptionHandler} | ||
: m_impl{std::make_unique<AppRuntimeImpl>(unhandledExceptionHandler)} | ||
{ | ||
Dispatch([this](Napi::Env env) { | ||
JsRuntime::CreateForJavaScript(env, [this](auto func) { Dispatch(std::move(func)); }); | ||
}); | ||
} | ||
|
||
AppRuntime::~AppRuntime() | ||
{ | ||
// Notify the JsRuntime on the JavaScript thread that the JavaScript | ||
// runtime shutdown sequence has begun. The JsRuntimeScheduler will | ||
// use this signal to gracefully cancel asynchronous operations. | ||
Dispatch([](Napi::Env env) { | ||
JsRuntime::NotifyDisposing(JsRuntime::GetFromJavaScript(env)); | ||
}); | ||
} | ||
AppRuntime::~AppRuntime() = default; | ||
|
||
void AppRuntime::Run(Napi::Env env) | ||
{ | ||
m_workQueue->Run(env); | ||
} | ||
// Move semantics | ||
AppRuntime::AppRuntime(AppRuntime&&) noexcept = default; | ||
AppRuntime& AppRuntime::operator=(AppRuntime&&) noexcept = default; | ||
|
||
void AppRuntime::Suspend() | ||
{ | ||
m_workQueue->Suspend(); | ||
m_impl->Suspend(); | ||
} | ||
|
||
void AppRuntime::Resume() | ||
{ | ||
m_workQueue->Resume(); | ||
m_impl->Resume(); | ||
} | ||
|
||
void AppRuntime::Dispatch(Dispatchable<void(Napi::Env)> func) | ||
void AppRuntime::Dispatch(Dispatchable<void(Napi::Env)> callback) | ||
{ | ||
m_workQueue->Append([this, func{std::move(func)}](Napi::Env env) mutable { | ||
Execute([this, env, func{std::move(func)}]() mutable { | ||
try | ||
{ | ||
func(env); | ||
} | ||
catch (const std::exception& error) | ||
{ | ||
m_unhandledExceptionHandler(error); | ||
} | ||
catch (...) | ||
{ | ||
std::abort(); | ||
} | ||
}); | ||
}); | ||
m_impl->Dispatch(std::move(callback)); | ||
} | ||
} |
This file contains 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 |
---|---|---|
@@ -0,0 +1,88 @@ | ||
#include "AppRuntimeImpl.h" | ||
#include <Babylon/JsRuntime.h> | ||
|
||
namespace Babylon | ||
{ | ||
AppRuntimeImpl::AppRuntimeImpl(std::function<void(const std::exception&)> unhandledExceptionHandler) | ||
: m_unhandledExceptionHandler{std::move(unhandledExceptionHandler)} | ||
, m_thread{[this] { RunPlatformTier(); }} | ||
{ | ||
Dispatch([this](Napi::Env env) { | ||
JsRuntime::CreateForJavaScript(env, [this](auto func) { Dispatch(std::move(func)); }); | ||
}); | ||
} | ||
|
||
AppRuntimeImpl::~AppRuntimeImpl() | ||
{ | ||
if (m_suspensionLock.has_value()) | ||
{ | ||
m_suspensionLock.reset(); | ||
} | ||
|
||
Dispatch([this](Napi::Env env) { | ||
// Notify the JsRuntime on the JavaScript thread that the JavaScript runtime shutdown sequence has | ||
// begun. The JsRuntimeScheduler will use this signal to gracefully cancel asynchronous operations. | ||
JsRuntime::NotifyDisposing(JsRuntime::GetFromJavaScript(env)); | ||
|
||
// Cancel on the JavaScript thread to signal the Run function to gracefully end. It must be | ||
// dispatched and not canceled directly to ensure that existing work is executed and executed in | ||
// the correct order. | ||
m_cancellationSource.cancel(); | ||
}); | ||
|
||
m_thread.join(); | ||
} | ||
|
||
void AppRuntimeImpl::Suspend() | ||
{ | ||
auto suspensionMutex = std::make_shared<std::mutex>(); | ||
m_suspensionLock.emplace(*suspensionMutex); | ||
Append([suspensionMutex = std::move(suspensionMutex)](Napi::Env) { | ||
std::scoped_lock lock{*suspensionMutex}; | ||
}); | ||
} | ||
|
||
void AppRuntimeImpl::Resume() | ||
{ | ||
m_suspensionLock.reset(); | ||
} | ||
|
||
void AppRuntimeImpl::Dispatch(Dispatchable<void(Napi::Env)> func) | ||
{ | ||
Append([this, func{std::move(func)}](Napi::Env env) mutable { | ||
Execute([this, env, func{std::move(func)}]() mutable { | ||
try | ||
{ | ||
func(env); | ||
} | ||
catch (const std::exception& error) | ||
{ | ||
m_unhandledExceptionHandler(error); | ||
} | ||
catch (...) | ||
{ | ||
std::abort(); | ||
} | ||
}); | ||
}); | ||
} | ||
|
||
void AppRuntimeImpl::Run(Napi::Env env) | ||
{ | ||
m_env = std::make_optional(env); | ||
|
||
m_dispatcher.set_affinity(std::this_thread::get_id()); | ||
|
||
while (!m_cancellationSource.cancelled()) | ||
{ | ||
m_dispatcher.blocking_tick(m_cancellationSource); | ||
} | ||
|
||
// The dispatcher can be non-empty if something is dispatched after cancellation. | ||
// For example, Chakra's JsSetPromiseContinuationCallback may potentially dispatch | ||
// a continuation after cancellation. | ||
m_dispatcher.clear(); | ||
|
||
m_env.reset(); | ||
} | ||
} |
This file contains 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 |
---|---|---|
@@ -0,0 +1,68 @@ | ||
#pragma once | ||
|
||
#include "AppRuntime.h" | ||
#include <optional> | ||
#include <mutex> | ||
#include <arcana/threading/dispatcher.h> | ||
|
||
namespace Babylon | ||
{ | ||
class AppRuntimeImpl | ||
{ | ||
public: | ||
AppRuntimeImpl(std::function<void(const std::exception&)> unhandledExceptionHandler = DefaultUnhandledExceptionHandler); | ||
~AppRuntimeImpl(); | ||
|
||
void Suspend(); | ||
void Resume(); | ||
|
||
void Dispatch(Dispatchable<void(Napi::Env)> func); | ||
|
||
private: | ||
static void DefaultUnhandledExceptionHandler(const std::exception& error); | ||
|
||
template<typename CallableT> | ||
void Append(CallableT callable) | ||
{ | ||
// Manual dispatcher queueing requires a copyable CallableT, we use a shared pointer trick to make a | ||
// copyable callable if necessary. | ||
if constexpr (std::is_copy_constructible<CallableT>::value) | ||
{ | ||
m_dispatcher.queue([this, callable = std::move(callable)]() { | ||
callable(m_env.value()); | ||
}); | ||
} | ||
else | ||
{ | ||
m_dispatcher.queue([this, callablePtr = std::make_shared<CallableT>(std::move(callable))]() { | ||
(*callablePtr)(m_env.value()); | ||
}); | ||
} | ||
} | ||
|
||
// These three methods are the mechanism by which platform- and JavaScript-specific | ||
// code can be "injected" into the execution of the JavaScript thread. These three | ||
// functions are implemented in separate files, thus allowing implementations to be | ||
// mixed and matched by the build system based on the platform and JavaScript engine | ||
// being targeted, without resorting to virtuality. An important nuance of these | ||
// functions is that they are all intended to call each other: RunPlatformTier MUST | ||
// call RunEnvironmentTier, which MUST create the initial Napi::Env and pass it to | ||
// Run. This arrangement allows not only for an arbitrary assemblage of platforms, | ||
// but it also allows us to respect the requirement by certain platforms (notably V8) | ||
// that certain program state be allocated and stored only on the stack. | ||
void RunPlatformTier(); | ||
void RunEnvironmentTier(const char* executablePath = "."); | ||
void Run(Napi::Env); | ||
|
||
// This method is called from Dispatch to allow platform-specific code to add | ||
// extra logic around the invocation of a dispatched callback. | ||
void Execute(Dispatchable<void()> callback); | ||
|
||
std::function<void(const std::exception&)> m_unhandledExceptionHandler{}; | ||
std::optional<Napi::Env> m_env{}; | ||
std::optional<std::scoped_lock<std::mutex>> m_suspensionLock{}; | ||
arcana::cancellation_source m_cancellationSource{}; | ||
arcana::manual_dispatcher<128> m_dispatcher{}; | ||
std::thread m_thread{}; | ||
}; | ||
} |
8 changes: 4 additions & 4 deletions
8
.../AppRuntime/Source/AppRuntime_Android.cpp → ...Runtime/Source/AppRuntimeImpl_Android.cpp
This file contains 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 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
Oops, something went wrong.