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

Fixes for shutting down during async operations #1141

Open
wants to merge 25 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 14 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 Sep 29, 2022
925d243
Change to explicitly call Rundown
bghgary Sep 29, 2022
c999153
Cannot throw in destructors
bghgary Sep 30, 2022
204efb9
Merge remote-tracking branch 'origin/master' into async-fixes
bghgary Oct 26, 2022
9d1e098
Fix merge issues
bghgary Oct 26, 2022
124def4
Merge with new timeout code with work queue fixes
bghgary Oct 27, 2022
309c4d9
Fix Canvas
bghgary Oct 28, 2022
ad88d30
Fix Android build
bghgary Oct 28, 2022
11ca510
Merge remote-tracking branch 'origin/master' into async-fixes
bghgary Mar 29, 2023
31248b6
Merge branch 'async-fixes' of https://github.com/bghgary/BabylonNativ…
bghgary Mar 29, 2023
9322360
Update arcana.cpp
bghgary Apr 4, 2023
b4a3c73
Temp fixes for MediaStream
bghgary Apr 5, 2023
9305ca8
Add missing std::forward calls for perfect forwarding
bghgary Apr 19, 2023
df246e2
Work queue shutdown fixes
bghgary Apr 28, 2023
7350715
Miscellaneous Windows AppRuntime fixes
bghgary Apr 28, 2023
77dc8f3
Fix typo in AppRuntime for Win32
bghgary Apr 28, 2023
def047c
Better fix for work queue shutdown issue
bghgary May 2, 2023
bdaf1b9
Merge remote-tracking branch 'origin/master' into async-fixes
bghgary May 4, 2023
0ae6cf4
Fix build issues from merge
bghgary May 4, 2023
71efaf4
Update arcana.cpp to include continuation fix
bghgary May 9, 2023
a0db419
Minor style fixes
bghgary May 9, 2023
6909f55
Update comment
bghgary May 9, 2023
e9d229d
Update comment 2
bghgary May 9, 2023
423ed5e
More style fixes
bghgary May 9, 2023
36a4661
Merge AppRuntime and WorkQueue to fix race condition
bghgary May 24, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions Core/AppRuntime/Source/AppRuntime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,21 @@ namespace Babylon
: m_workQueue{std::make_unique<WorkQueue>([this] { RunPlatformTier(); })}
, m_unhandledExceptionHandler{unhandledExceptionHandler}
{
Dispatch([this](Napi::Env env) {
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));
});
}

void AppRuntime::Run(Napi::Env env)
Expand All @@ -38,8 +46,10 @@ namespace Babylon

void AppRuntime::Dispatch(Dispatchable<void(Napi::Env)> func)
{
m_workQueue->Append([this, func{std::move(func)}](Napi::Env env) mutable {
Execute([this, env, func{std::move(func)}]() mutable {
m_workQueue->Append([this, func{std::move(func)}](Napi::Env env) mutable
{
Execute([this, env, func{std::move(func)}]() mutable
{
try
{
func(env);
Expand Down
26 changes: 20 additions & 6 deletions Core/AppRuntime/Source/WorkQueue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,12 @@ namespace Babylon
Resume();
}

m_cancelSource.cancel();
m_dispatcher.cancelled();
// Dispatch a cancel 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_dispatcher([this]() {
m_cancellationSource.cancel();
});

m_thread.join();
}
Expand All @@ -36,14 +40,24 @@ namespace Babylon

void WorkQueue::Run(Napi::Env env)
{
m_env = std::make_optional(env);
m_dispatcher.set_affinity(std::this_thread::get_id());

while (!m_cancelSource.cancelled())
m_env = std::make_optional(env);

while (!m_cancellationSource.cancelled())
{
m_dispatcher.blocking_tick(m_cancelSource);
m_dispatcher.blocking_tick(m_cancellationSource);
}

m_dispatcher.clear();
// Drain the queue to complete work dispatched after cancellation.
m_dispatcher.tick(arcana::cancellation::none());

// There should no longer be any outstanding work once the queue is drained.
assert(m_dispatcher.empty());

// Clear the shutdown queue to make sure the callables are destroyed on this thread.
m_shutdownQueue.clear();
bghgary marked this conversation as resolved.
Show resolved Hide resolved

m_env.reset();
}
}
39 changes: 28 additions & 11 deletions Core/AppRuntime/Source/WorkQueue.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,29 @@ namespace Babylon
// copyable callable if necessary.
if constexpr (std::is_copy_constructible<CallableT>::value)
{
m_dispatcher.queue([this, callable = std::move(callable)]() { Invoke(callable); });
if (m_cancellationSource.cancelled())
{
m_shutdownQueue.push([callable = std::move(callable)] {});
}
else
{
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))]() { Invoke(*callablePtr); });
if (m_cancellationSource.cancelled())
{
m_shutdownQueue.push([callablePtr = std::make_shared<CallableT>(std::move(callable))] {});
}
else
{
m_dispatcher.queue([this, callablePtr = std::make_shared<CallableT>(std::move(callable))]() {
(*callablePtr)(m_env.value());
});
}
}
}

Expand All @@ -36,19 +54,18 @@ namespace Babylon
void Run(Napi::Env);

private:
template<typename CallableT>
void Invoke(CallableT& callable)
{
callable(m_env.value());
}

std::optional<Napi::Env> m_env{};

std::optional<std::scoped_lock<std::mutex>> m_suspensionLock{};

arcana::cancellation_source m_cancelSource{};
arcana::manual_dispatcher<128> m_dispatcher{};
arcana::cancellation_source m_cancellationSource{};

using DispatcherT = arcana::manual_dispatcher<128>;
DispatcherT m_dispatcher{};

// Put the callables in a separate queue during shutdown to ensure the callables are destroyed on the right thread.
arcana::blocking_concurrent_queue<DispatcherT::callback_t> m_shutdownQueue{};

std::thread m_thread;
std::thread m_thread{};
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include <napi/env.h>

#include <bx/allocator.h>
#include <bgfx/bgfx.h>
#include <bgfx/platform.h>

Expand Down Expand Up @@ -94,7 +95,7 @@ namespace Babylon::Graphics

Update GetUpdate(const char* updateName);

void RequestScreenShot(std::function<void(std::vector<uint8_t>)> callback);
arcana::task<std::vector<uint8_t>, std::exception_ptr> RequestScreenShotAsync();

arcana::task<void, std::exception_ptr> ReadTextureAsync(bgfx::TextureHandle handle, gsl::span<uint8_t> data, uint8_t mipLevel = 0);

Expand All @@ -115,12 +116,16 @@ namespace Babylon::Graphics
void RemoveTexture(bgfx::TextureHandle handle);
TextureInfo GetTextureInfo(bgfx::TextureHandle handle);

bx::AllocatorI& Allocator() { return m_allocator; }

private:
friend UpdateToken;

DeviceImpl& m_graphicsImpl;

std::unordered_map<uint16_t, TextureInfo> m_textureHandleToInfo{};
std::mutex m_textureHandleToInfoMutex{};

bx::DefaultAllocator m_allocator{};
};
}
4 changes: 2 additions & 2 deletions Core/Graphics/Source/DeviceContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ namespace Babylon::Graphics
return {m_graphicsImpl.GetSafeTimespanGuarantor(updateName), *this};
}

void DeviceContext::RequestScreenShot(std::function<void(std::vector<uint8_t>)> callback)
arcana::task<std::vector<uint8_t>, std::exception_ptr> DeviceContext::RequestScreenShotAsync()
{
return m_graphicsImpl.RequestScreenShot(std::move(callback));
return m_graphicsImpl.RequestScreenShotAsync();
}

arcana::task<void, std::exception_ptr> DeviceContext::ReadTextureAsync(bgfx::TextureHandle handle, gsl::span<uint8_t> data, uint8_t mipLevel)
Expand Down
11 changes: 9 additions & 2 deletions Core/Graphics/Source/DeviceImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -272,9 +272,16 @@ namespace Babylon::Graphics
m_bgfxCallback.SetDiagnosticOutput(std::move(diagnosticOutput));
}

void DeviceImpl::RequestScreenShot(std::function<void(std::vector<uint8_t>)> callback)
arcana::task<std::vector<uint8_t>, std::exception_ptr> DeviceImpl::RequestScreenShotAsync()
{
m_screenShotCallbacks.push(std::move(callback));
arcana::task_completion_source<std::vector<uint8_t>, std::exception_ptr> taskCompletionSource{};

m_screenShotCallbacks.push([taskCompletionSource](std::vector<uint8_t> bytes) mutable
{
taskCompletionSource.complete(std::move(bytes));
});

return taskCompletionSource.as_task();
}

arcana::task<void, std::exception_ptr> DeviceImpl::ReadTextureAsync(bgfx::TextureHandle handle, gsl::span<uint8_t> data, uint8_t mipLevel)
Expand Down
2 changes: 1 addition & 1 deletion Core/Graphics/Source/DeviceImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ namespace Babylon::Graphics

Update GetUpdate(const char* updateName);

void RequestScreenShot(std::function<void(std::vector<uint8_t>)> callback);
arcana::task<std::vector<uint8_t>, std::exception_ptr> RequestScreenShotAsync();

arcana::task<void, std::exception_ptr> ReadTextureAsync(bgfx::TextureHandle handle, gsl::span<uint8_t> data, uint8_t mipLevel);

Expand Down
33 changes: 26 additions & 7 deletions Core/JsRuntime/Include/Babylon/JsRuntime.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include <functional>
#include <mutex>
#include <vector>

namespace Babylon
{
Expand All @@ -22,31 +23,49 @@ namespace Babylon
}
};

struct InternalState;
friend struct InternalState;
JsRuntime(const JsRuntime&) = delete;
JsRuntime& operator=(const JsRuntime&) = delete;

// Any JavaScript errors that occur will bubble up as a Napi::Error C++ exception.
// JsRuntime expects the provided dispatch function to handle this exception,
// such as with a try/catch and logging the exception message.
using DispatchFunctionT = std::function<void(std::function<void(Napi::Env)>)>;

// Creates the JsRuntime object owned by the JavaScript environment.
// Note: It is the contract of JsRuntime that its dispatch function must be usable
// at the moment of construction. JsRuntime cannot be built with dispatch function
// that captures a reference to a not-yet-completed object that will be completed
// later -- an instance of an inheriting type, for example. The dispatch function
// must be safely callable as soon as it is passed to the JsRuntime constructor.
static JsRuntime& CreateForJavaScript(Napi::Env, DispatchFunctionT);

// Gets the JsRuntime from the given N-API environment.
static JsRuntime& GetFromJavaScript(Napi::Env);
void Dispatch(std::function<void(Napi::Env)>);

protected:
JsRuntime(const JsRuntime&) = delete;
JsRuntime& operator=(const JsRuntime&) = delete;
struct IDisposingCallback
{
virtual void Disposing() = 0;
};
bghgary marked this conversation as resolved.
Show resolved Hide resolved

// Notifies the JsRuntime that the JavaScript environment will begin shutting down.
// Calling this function will signal callbacks registered with RegisterDisposing.
static void NotifyDisposing(JsRuntime&);

// Registers a callback for when the JavaScript environment will begin shutting down.
static void RegisterDisposing(JsRuntime&, IDisposingCallback*);

// Unregisters a callback for when the JavaScript environment will begin shutting down.
static void UnregisterDisposing(JsRuntime&, IDisposingCallback*);

// Dispatches work onto the JavaScript thread and provides access to the N-API
// environment.
void Dispatch(std::function<void(Napi::Env)>);

private:
JsRuntime(Napi::Env, DispatchFunctionT);

DispatchFunctionT m_dispatchFunction{};
std::mutex m_mutex{};
DispatchFunctionT m_dispatchFunction{};
std::vector<IDisposingCallback*> m_disposingCallbacks{};
};
}
Loading