-
Notifications
You must be signed in to change notification settings - Fork 23
Expand file tree
/
Copy pathAppRuntime.cpp
More file actions
142 lines (123 loc) · 4.58 KB
/
Copy pathAppRuntime.cpp
File metadata and controls
142 lines (123 loc) · 4.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
#include "AppRuntime.h"
#include <arcana/threading/cancellation.h>
#include <arcana/threading/dispatcher.h>
#include <cassert>
#include <optional>
#include <mutex>
#include <thread>
#include <type_traits>
namespace Babylon
{
class AppRuntime::Impl
{
public:
template<typename CallableT>
void Append(CallableT callable)
{
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());
});
}
}
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{};
std::thread m_thread;
};
AppRuntime::AppRuntime() :
AppRuntime{{}}
{
}
AppRuntime::AppRuntime(Options options)
: m_options{std::move(options)}
, m_impl{std::make_unique<Impl>()}
{
m_impl->m_thread = std::thread{[this] { RunPlatformTier(); }};
Dispatch([this](Napi::Env env) {
JsRuntime::CreateForJavaScript(env, [this](auto func) { Dispatch(std::move(func)); });
});
}
AppRuntime::~AppRuntime()
{
if (m_impl->m_suspensionLock.has_value())
{
m_impl->m_suspensionLock.reset();
}
// Cancel immediately so pending work is dropped promptly, then append
// a no-op work item to wake the worker thread from blocking_tick. The
// no-op goes through push() which acquires the queue mutex, avoiding
// the race where a bare notify_all() can be missed by wait().
//
// NOTE: This preserves the existing shutdown behavior where pending
// callbacks are dropped on cancellation. A more complete solution
// would add cooperative shutdown (e.g. NotifyDisposing/Rundown) so
// consumers can finish cleanup work before the runtime is destroyed.
m_impl->m_cancelSource.cancel();
m_impl->Append([](Napi::Env) {});
m_impl->m_thread.join();
}
void AppRuntime::Run(Napi::Env env)
{
m_impl->m_env = std::make_optional(env);
m_impl->m_dispatcher.set_affinity(std::this_thread::get_id());
while (!m_impl->m_cancelSource.cancelled())
{
m_impl->m_dispatcher.blocking_tick(m_impl->m_cancelSource);
}
// The dispatcher can be non-empty if something is dispatched after cancellation.
m_impl->m_dispatcher.clear();
}
void AppRuntime::Suspend()
{
auto suspensionMutex = std::make_shared<std::mutex>();
m_impl->m_suspensionLock.emplace(*suspensionMutex);
m_impl->Append([suspensionMutex{std::move(suspensionMutex)}](Napi::Env) {
std::scoped_lock lock{*suspensionMutex};
});
}
void AppRuntime::Resume()
{
m_impl->m_suspensionLock.reset();
}
void AppRuntime::Dispatch(Dispatchable<void(Napi::Env)> func)
{
m_impl->Append([this, func{std::move(func)}](Napi::Env env) mutable {
Execute([this, env, func{std::move(func)}]() mutable {
// Some engines (notably Hermes) require an open NAPI handle
// scope before any napi_* call that materializes a value.
// The other engines (V8/Chakra/JSC) already provide an outer
// scope at the RunEnvironmentTier level, so this extra
// scope is harmless there but mandatory for Hermes.
Napi::HandleScope scope{env};
try
{
func(env);
}
catch (const Napi::Error& error)
{
m_options.UnhandledExceptionHandler(error);
}
catch (...)
{
assert(false);
std::abort();
}
// Drain engine-level microtasks/jobs queued during the
// callback (Promise continuations, queueMicrotask, etc.) so
// they run before the next top-level Dispatch. No-op for
// engines that drain automatically; Hermes needs an explicit
// pump.
DrainMicrotasks(env);
});
});
}
}