@lucent-lang/lucent 0.0.4 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +97 -0
- package/app.plugin.js +46 -10
- package/dist/{bench-CR3E7e2e.js → bench-eEez4jhQ.js} +10 -6
- package/dist/{build-CAIrRSYA.js → build-CpUjeHFz.js} +1 -1
- package/dist/{build-check-QozuNJm5.js → build-check-BA1ZEp9s.js} +28 -13
- package/dist/{check-1SCmYlqA.js → check-BZx3FgI6.js} +1 -1
- package/dist/cli.js +62 -13
- package/dist/{codes-CIuwXt1S.js → codes-D-6LxUqD.js} +163 -7
- package/dist/compiler.js +4 -4
- package/dist/{dashboard-B-NzjjaN.js → dashboard-CC-HXhcd.js} +2 -2
- package/dist/{dev-BYWMP7oC.js → dev-DI5yL8zx.js} +62 -23
- package/dist/{diagnostic-BM9sXmwl.js → diagnostic-BvwLZQS1.js} +3 -1
- package/dist/{doctor-C0PrfWT6.js → doctor-CTAX3euc.js} +1 -1
- package/dist/{doctor-DdR4JnVc.js → doctor-CrgOAV5L.js} +1 -1
- package/dist/{explain--vV1oaRl.js → explain-Byit07F-.js} +1 -1
- package/dist/{packages-HyskKIsY.js → packages-CSmKeaAh.js} +2 -33
- package/dist/pipeline-BM_VM2X_.js +700 -0
- package/dist/project-BKNoTEEp.js +342 -0
- package/dist/sdk-coverage-iiv3JqMp.js +113 -0
- package/dist/sdk-diff-ByJ1Gw-F.js +109 -0
- package/dist/sdk-lock-BQn7IyRs.js +38 -0
- package/dist/{sdk-prefetch-mo04JDZV.js → sdk-prefetch-Bv2pNega.js} +2 -2
- package/dist/{sdk-search-Iv4K3-8a.js → sdk-search-CPGJm_Ne.js} +2 -2
- package/dist/{sdk-show-CkrdlqlP.js → sdk-show-BUkLqRnA.js} +5 -4
- package/dist/sdk-usage-0ZFMAYg1.js +95 -0
- package/dist/src-ChnmWLwn.js +40851 -0
- package/dist/trace-Bi4hkmPx.js +139 -0
- package/lib/core.cjs +368 -1
- package/lib/sdk/android.d.ts +54 -1
- package/lib/sdk/compose.d.ts +94 -0
- package/lib/sdk/compose.schemas.json.gz +0 -0
- package/lib/sdk/core.d.ts +201 -0
- package/lib/sdk/ios.d.ts +75 -2
- package/lib/sdk/ui.d.ts +129 -0
- package/metro/transformer.cjs +11 -2
- package/package.json +4 -3
- package/runtime/cpp/lucent/abort.cpp +59 -11
- package/runtime/cpp/lucent/abort.h +22 -6
- package/runtime/cpp/lucent/array.h +12 -2
- package/runtime/cpp/lucent/async.h +101 -21
- package/runtime/cpp/lucent/bigint.cpp +898 -0
- package/runtime/cpp/lucent/bigint.h +302 -0
- package/runtime/cpp/lucent/buffer.cpp +228 -0
- package/runtime/cpp/lucent/buffer.h +328 -0
- package/runtime/cpp/lucent/builtins.cpp +4 -1
- package/runtime/cpp/lucent/bytes.h +21 -2
- package/runtime/cpp/lucent/callback.cpp +29 -0
- package/runtime/cpp/lucent/callback.h +230 -0
- package/runtime/cpp/lucent/compute.cpp +370 -0
- package/runtime/cpp/lucent/compute.h +455 -0
- package/runtime/cpp/lucent/conversions.h +111 -0
- package/runtime/cpp/lucent/core.h +16 -5
- package/runtime/cpp/lucent/equality.h +107 -28
- package/runtime/cpp/lucent/execution.cpp +416 -0
- package/runtime/cpp/lucent/execution.h +270 -0
- package/runtime/cpp/lucent/extension.cpp +97 -0
- package/runtime/cpp/lucent/extension.h +210 -0
- package/runtime/cpp/lucent/helpers.h +12 -5
- package/runtime/cpp/lucent/items.h +52 -0
- package/runtime/cpp/lucent/jserror.cpp +9 -0
- package/runtime/cpp/lucent/jserror.h +11 -0
- package/runtime/cpp/lucent/jsi/convert.cpp +138 -0
- package/runtime/cpp/lucent/jsi/convert.h +128 -59
- package/runtime/cpp/lucent/jsi/host.cpp +213 -35
- package/runtime/cpp/lucent/jsi/host.h +116 -16
- package/runtime/cpp/lucent/json.h +19 -1
- package/runtime/cpp/lucent/lifecycle.cpp +260 -0
- package/runtime/cpp/lucent/lifecycle.h +124 -0
- package/runtime/cpp/lucent/lucent.h +7 -0
- package/runtime/cpp/lucent/map.h +41 -7
- package/runtime/cpp/lucent/native.cpp +35 -47
- package/runtime/cpp/lucent/native.h +116 -26
- package/runtime/cpp/lucent/number.cpp +38 -38
- package/runtime/cpp/lucent/number.h +33 -0
- package/runtime/cpp/lucent/operation.h +65 -0
- package/runtime/cpp/lucent/ops.h +14 -6
- package/runtime/cpp/lucent/platform/android.cpp +223 -10
- package/runtime/cpp/lucent/platform/android.h +227 -4
- package/runtime/cpp/lucent/platform/android_activity.cpp +258 -0
- package/runtime/cpp/lucent/platform/android_requests.h +140 -0
- package/runtime/cpp/lucent/platform/compose.h +256 -0
- package/runtime/cpp/lucent/platform/ios.h +325 -25
- package/runtime/cpp/lucent/platform/ios.mm +1 -14
- package/runtime/cpp/lucent/platform/ios_ui.h +192 -0
- package/runtime/cpp/lucent/platform/ios_ui.mm +454 -0
- package/runtime/cpp/lucent/platform/swiftui.h +190 -0
- package/runtime/cpp/lucent/presentation.cpp +11 -0
- package/runtime/cpp/lucent/presentation.h +109 -0
- package/runtime/cpp/lucent/reactive.cpp +406 -0
- package/runtime/cpp/lucent/reactive.h +566 -0
- package/runtime/cpp/lucent/report.cpp +40 -0
- package/runtime/cpp/lucent/report.h +16 -0
- package/runtime/cpp/lucent/resource.cpp +83 -0
- package/runtime/cpp/lucent/resource.h +75 -0
- package/runtime/cpp/lucent/scheduler.cpp +38 -124
- package/runtime/cpp/lucent/scheduler.h +102 -83
- package/runtime/cpp/lucent/scope.cpp +308 -0
- package/runtime/cpp/lucent/scope.h +452 -0
- package/runtime/cpp/lucent/sizing.h +263 -0
- package/runtime/cpp/lucent/slots.h +231 -0
- package/runtime/cpp/lucent/trace.cpp +539 -0
- package/runtime/cpp/lucent/trace.h +251 -0
- package/runtime/cpp/lucent/transport.cpp +9 -0
- package/runtime/cpp/lucent/transport.h +341 -0
- package/runtime/cpp/lucent/view.cpp +123 -0
- package/runtime/cpp/lucent/view.h +157 -0
- package/runtime/cpp/rn/LucentComponentView.h +121 -0
- package/runtime/cpp/rn/LucentComponentView.mm +479 -0
- package/runtime/cpp/rn/LucentModule.cpp +37 -7
- package/runtime/cpp/rn/LucentModule.h +3 -0
- package/runtime/cpp/rn/LucentViewChildren.h +80 -0
- package/runtime/cpp/rn/LucentViewChildren.mm +211 -0
- package/runtime/cpp/rn/LucentViewRegistry.h +62 -0
- package/runtime/cpp/rn/LucentViewRequests.cpp +91 -0
- package/runtime/cpp/rn/LucentViewRequests.h +104 -0
- package/runtime/cpp/rn/LucentViewSizing.h +345 -0
- package/runtime/cpp/rn/LucentViewSlots.h +242 -0
- package/runtime/cpp/rn/LucentViewValues.h +125 -0
- package/runtime/cpp/rn/LucentViews.h +440 -0
- package/runtime/cpp/rn/LucentViewsAndroid.cpp +439 -0
- package/runtime/cpp/rn/LucentViewsAndroid.h +138 -0
- package/runtime/cpp/third_party/dragonbox/LICENSE-Boost +23 -0
- package/runtime/cpp/third_party/dragonbox/README.md +16 -0
- package/runtime/cpp/third_party/dragonbox/dragonbox.h +4440 -0
- package/runtime/js/index.d.ts +11 -1
- package/runtime/js/index.js +84 -8
- package/runtime/js/views.d.ts +38 -0
- package/runtime/js/views.js +226 -0
- package/runtime/native/android/CMakeLists.txt +4 -1
- package/runtime/native/android/src/compose/java/dev/lucent/compose/CompositionRules.kt +44 -0
- package/runtime/native/android/src/compose/java/dev/lucent/compose/LucentComposition.kt +161 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentActivities.java +292 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentChildren.java +174 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentHostView.java +232 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentInitializer.java +46 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentPackage.java +4 -3
- package/runtime/native/android/src/main/java/dev/lucent/LucentRequestActivity.java +92 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentSlotView.java +102 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentViewManager.java +149 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentViewManagers.java +16 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentViews.java +44 -0
- package/runtime/test/jsi/harness.cpp +9 -0
- package/schemas/build.schema.json +96 -1
- package/schemas/check.schema.json +56 -1
- package/schemas/lucent.schema.json +495 -9
- package/schemas/sdk-diff.schema.json +70 -0
- package/schemas/sdk-lock.schema.json +67 -0
- package/ts-plugin/index.js +85 -4
- package/dist/pipeline-CH7-2cxu.js +0 -232
- package/dist/project-D8tEet0k.js +0 -150
- package/dist/sdk-coverage-BoW_3Efd.js +0 -64
- package/dist/src-BD7f1_aZ.js +0 -11493
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Lucent runtime — resources: what Lucent code opens and must close once.
|
|
2
|
+
//
|
|
3
|
+
// A Resource holds the release of something native (a camera session, a
|
|
4
|
+
// file, a subscription). It is open until it closes, closing while its
|
|
5
|
+
// release runs, then closed. It closes at the first of an explicit
|
|
6
|
+
// close(), the disposal of the scope it belongs to, or its last reference
|
|
7
|
+
// going: finalization is a backstop, never the only way. Its release runs
|
|
8
|
+
// exactly once, on the context the resource requires.
|
|
9
|
+
#pragma once
|
|
10
|
+
|
|
11
|
+
#include <atomic>
|
|
12
|
+
#include <cstdint>
|
|
13
|
+
#include <functional>
|
|
14
|
+
#include <memory>
|
|
15
|
+
|
|
16
|
+
#include "execution.h"
|
|
17
|
+
#include "jserror.h"
|
|
18
|
+
#include "jsstring.h"
|
|
19
|
+
#include "scope.h"
|
|
20
|
+
|
|
21
|
+
namespace lucent {
|
|
22
|
+
|
|
23
|
+
class Resource : public std::enable_shared_from_this<Resource> {
|
|
24
|
+
public:
|
|
25
|
+
enum class State : uint8_t { Open, Closing, Closed };
|
|
26
|
+
|
|
27
|
+
/// Opens a resource named `what` in its errors ("Camera is closed").
|
|
28
|
+
/// `release` runs once, when it closes: on `releaseOn`'s thread if given
|
|
29
|
+
/// (posted there from any other), else wherever it closes. With a
|
|
30
|
+
/// `scope`, disposing the scope closes the resource; the scope refers to
|
|
31
|
+
/// it weakly, so it never keeps the resource alive. Under a scope that is
|
|
32
|
+
/// no longer active, the resource is closed at once.
|
|
33
|
+
static std::shared_ptr<Resource> open(String what, std::function<void()> release, ExecutionContext* releaseOn = nullptr,
|
|
34
|
+
const std::shared_ptr<Scope>& scope = nullptr);
|
|
35
|
+
|
|
36
|
+
/// Closes it if still open (the backstop). An error from its release is
|
|
37
|
+
/// reported (reportUncaught).
|
|
38
|
+
~Resource();
|
|
39
|
+
|
|
40
|
+
Resource(const Resource&) = delete;
|
|
41
|
+
Resource& operator=(const Resource&) = delete;
|
|
42
|
+
|
|
43
|
+
State state() const { return state_.load(std::memory_order_acquire); }
|
|
44
|
+
|
|
45
|
+
bool isOpen() const { return state() == State::Open; }
|
|
46
|
+
|
|
47
|
+
/// What every ordinary use checks first: throws InvalidStateError
|
|
48
|
+
/// "<what> is closed" unless the resource is open.
|
|
49
|
+
void check() const;
|
|
50
|
+
|
|
51
|
+
/// Any thread, idempotent. The first call closes the resource and
|
|
52
|
+
/// returns true; any other returns false at once, without waiting for a
|
|
53
|
+
/// release in progress. The resource is closed once its release has run,
|
|
54
|
+
/// even if the release throws: run here, its error reaches the caller;
|
|
55
|
+
/// posted, it is reported.
|
|
56
|
+
bool close();
|
|
57
|
+
|
|
58
|
+
private:
|
|
59
|
+
Resource(String what, std::function<void()> release, ExecutionContext* releaseOn);
|
|
60
|
+
|
|
61
|
+
/// Forgets the scope's cleanup, if it has not run.
|
|
62
|
+
void unlink();
|
|
63
|
+
|
|
64
|
+
const String what_;
|
|
65
|
+
ExecutionContext* const releaseOn_;
|
|
66
|
+
|
|
67
|
+
/// Taken by whichever closes the resource first.
|
|
68
|
+
std::function<void()> release_;
|
|
69
|
+
std::atomic<State> state_{State::Open};
|
|
70
|
+
|
|
71
|
+
std::weak_ptr<Scope> scope_;
|
|
72
|
+
std::atomic<Scope::CleanupId> cleanup_{0};
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
} // namespace lucent
|
|
@@ -1,153 +1,67 @@
|
|
|
1
1
|
#include "scheduler.h"
|
|
2
2
|
|
|
3
|
-
#include <cstdio>
|
|
4
|
-
#include <exception>
|
|
5
|
-
#include <string>
|
|
6
|
-
|
|
7
|
-
#if defined(__ANDROID__)
|
|
8
|
-
#include <android/log.h>
|
|
9
|
-
#elif defined(__APPLE__)
|
|
10
|
-
#include <os/log.h>
|
|
11
|
-
#endif
|
|
12
|
-
|
|
13
3
|
namespace lucent {
|
|
14
4
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
#if defined(__ANDROID__)
|
|
20
|
-
__android_log_write(ANDROID_LOG_ERROR, "Lucent", message.c_str());
|
|
21
|
-
#elif defined(__APPLE__)
|
|
22
|
-
os_log_error(OS_LOG_DEFAULT, "%{public}s", message.c_str());
|
|
23
|
-
#endif
|
|
24
|
-
std::fprintf(stderr, "%s\n", message.c_str());
|
|
25
|
-
}
|
|
26
|
-
} // namespace
|
|
27
|
-
|
|
28
|
-
void logError(const char* message) { writeError(message); }
|
|
5
|
+
Scheduler::Scheduler() : ExecutionContext(µtaskCount_) {
|
|
6
|
+
// The module context starts with the first module: tracing asked for by
|
|
7
|
+
// the environment (LUCENT_TRACE) starts with it.
|
|
8
|
+
trace::startFromEnvironment();
|
|
29
9
|
|
|
30
|
-
|
|
31
|
-
try {
|
|
32
|
-
std::rethrow_exception(e);
|
|
33
|
-
} catch (const std::exception& x) {
|
|
34
|
-
writeError(std::string("[lucent] uncaught exception in ") + where + ": " + x.what());
|
|
35
|
-
} catch (...) {
|
|
36
|
-
writeError(std::string("[lucent] uncaught exception in ") + where);
|
|
37
|
-
}
|
|
10
|
+
worker_ = WorkerThread::start([this](Job& job) { runTurn(job); });
|
|
38
11
|
}
|
|
39
12
|
|
|
40
13
|
namespace {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
reportUncaught(std::current_exception(), "job");
|
|
46
|
-
}
|
|
47
|
-
}
|
|
14
|
+
/// The scheduler's owner, leaked on purpose (jobs may still reference the
|
|
15
|
+
/// scheduler during static destruction at process exit), and kept
|
|
16
|
+
/// reachable: a leak checker (LeakSanitizer) reports what it cannot reach.
|
|
17
|
+
std::shared_ptr<Scheduler>* keptScheduler = nullptr;
|
|
48
18
|
} // namespace
|
|
49
19
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
20
|
+
Scheduler& Scheduler::instance() {
|
|
21
|
+
// The scheduler itself, one load away on the call path.
|
|
22
|
+
static Scheduler* s = [] {
|
|
23
|
+
keptScheduler = new std::shared_ptr<Scheduler>(new Scheduler());
|
|
24
|
+
(*keptScheduler)->makeRoot();
|
|
25
|
+
return keptScheduler->get();
|
|
26
|
+
}();
|
|
27
|
+
return *s;
|
|
54
28
|
}
|
|
55
29
|
|
|
56
|
-
Scheduler
|
|
57
|
-
{
|
|
58
|
-
|
|
59
|
-
|
|
30
|
+
void Scheduler::runTurn(Job& job) {
|
|
31
|
+
if (trace::enabled()) [[unlikely]] {
|
|
32
|
+
LucentScope::lockTraced(lock_);
|
|
33
|
+
} else {
|
|
34
|
+
lock_.lock();
|
|
60
35
|
}
|
|
61
|
-
cv_.notify_all();
|
|
62
|
-
if (thread_.joinable()) thread_.join();
|
|
63
|
-
}
|
|
64
36
|
|
|
65
|
-
|
|
66
|
-
{
|
|
67
|
-
std::lock_guard<std::mutex> g(queueMutex_);
|
|
68
|
-
jobs_.push_back(std::move(job));
|
|
69
|
-
}
|
|
70
|
-
cv_.notify_one();
|
|
71
|
-
}
|
|
37
|
+
std::lock_guard<LucentLock> lucent(lock_, std::adopt_lock);
|
|
72
38
|
|
|
73
|
-
void Scheduler::postDelayed(double ms, Job job) {
|
|
74
|
-
if (!(ms > 0)) ms = 0;
|
|
75
|
-
auto at = std::chrono::steady_clock::now() + std::chrono::microseconds(static_cast<int64_t>(ms * 1000));
|
|
76
39
|
{
|
|
77
|
-
std::
|
|
78
|
-
|
|
40
|
+
Job local = std::move(job);
|
|
41
|
+
detail::runGuarded(local, "job");
|
|
79
42
|
}
|
|
80
|
-
cv_.notify_one();
|
|
81
|
-
}
|
|
82
43
|
|
|
83
|
-
|
|
84
|
-
microtasks_.push_back(std::move(job));
|
|
85
|
-
pendingMicrotasks_++;
|
|
44
|
+
drainMicrotasks();
|
|
86
45
|
}
|
|
87
46
|
|
|
88
|
-
void
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
runGuarded(job);
|
|
47
|
+
void LucentScope::lockTraced(LucentLock& lock) {
|
|
48
|
+
// Already held here (a nested call): no wait to record.
|
|
49
|
+
if (lock.heldByCurrentThread()) {
|
|
50
|
+
lock.lock();
|
|
51
|
+
return;
|
|
94
52
|
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
size_t Scheduler::pendingWork() {
|
|
98
|
-
std::lock_guard<std::mutex> g(queueMutex_);
|
|
99
|
-
return jobs_.size() + timers_.size() + running_;
|
|
100
|
-
}
|
|
101
53
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
[this] { return jobs_.empty() && timers_.empty() && running_ == 0; });
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
void Scheduler::run() {
|
|
109
|
-
std::unique_lock<std::mutex> g(queueMutex_);
|
|
110
|
-
for (;;) {
|
|
111
|
-
if (stopping_) return;
|
|
112
|
-
auto now = std::chrono::steady_clock::now();
|
|
113
|
-
while (!timers_.empty() && timers_.top().at <= now) {
|
|
114
|
-
jobs_.push_back(std::move(const_cast<Timer&>(timers_.top()).job));
|
|
115
|
-
timers_.pop();
|
|
116
|
-
}
|
|
117
|
-
if (!jobs_.empty()) {
|
|
118
|
-
Job job = std::move(jobs_.front());
|
|
119
|
-
jobs_.pop_front();
|
|
120
|
-
running_++;
|
|
121
|
-
g.unlock();
|
|
122
|
-
{
|
|
123
|
-
std::lock_guard<LucentLock> lucent(lock_);
|
|
124
|
-
runGuarded(job);
|
|
125
|
-
drainMicrotasks();
|
|
126
|
-
}
|
|
127
|
-
g.lock();
|
|
128
|
-
running_--;
|
|
129
|
-
if (jobs_.empty() && timers_.empty() && running_ == 0) idleCv_.notify_all();
|
|
130
|
-
continue;
|
|
131
|
-
}
|
|
132
|
-
if (timers_.empty()) {
|
|
133
|
-
cv_.wait(g);
|
|
134
|
-
} else {
|
|
135
|
-
// A copy: wait_until reads the deadline after unlocking, when a
|
|
136
|
-
// postDelayed may have reallocated the heap.
|
|
137
|
-
const auto deadline = timers_.top().at;
|
|
138
|
-
cv_.wait_until(g, deadline);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
54
|
+
trace::Mark wait = trace::begin(trace::Category::Lock, "lucent-lock");
|
|
55
|
+
lock.lock();
|
|
56
|
+
trace::end(wait, trace::Category::Lock, "lucent-lock", {.parent = trace::currentId()});
|
|
141
57
|
}
|
|
142
58
|
|
|
143
59
|
void LucentScope::handOffMicrotasks() {
|
|
144
60
|
Scheduler& s = Scheduler::instance();
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
s.post([] {});
|
|
150
|
-
}
|
|
61
|
+
|
|
62
|
+
// Continuations run on the Lucent thread, never on the JS thread. There,
|
|
63
|
+
// the turn this scope is nested in runs them after its job.
|
|
64
|
+
if (!s.onLucentThread()) s.post([] {});
|
|
151
65
|
}
|
|
152
66
|
|
|
153
67
|
} // namespace lucent
|
|
@@ -1,160 +1,179 @@
|
|
|
1
|
-
// Lucent runtime —
|
|
1
|
+
// Lucent runtime — the legacy module context.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// interleave only at `await`, so
|
|
3
|
+
// Module code runs one piece at a time, like JavaScript: every entry into it
|
|
4
|
+
// (a synchronous call from the JS thread, or a job on the Lucent thread)
|
|
5
|
+
// holds the Lucent lock. Async functions run on the Lucent thread and
|
|
6
|
+
// interleave only at `await`, so module code never races with itself.
|
|
7
7
|
//
|
|
8
|
-
// Invariant: the Lucent
|
|
9
|
-
// thread may
|
|
8
|
+
// Invariant: code holding the Lucent lock never waits for another thread,
|
|
9
|
+
// so any thread may block on the lock without deadlocking. The main context
|
|
10
|
+
// and isolated contexts (execution.h) never take it.
|
|
10
11
|
#pragma once
|
|
11
12
|
|
|
12
13
|
#include <atomic>
|
|
13
|
-
#include <chrono>
|
|
14
|
-
#include <condition_variable>
|
|
15
14
|
#include <cstdint>
|
|
16
|
-
#include <
|
|
17
|
-
#include <exception>
|
|
18
|
-
#include <functional>
|
|
15
|
+
#include <memory>
|
|
19
16
|
#include <mutex>
|
|
20
|
-
#include <queue>
|
|
21
17
|
#include <thread>
|
|
22
|
-
#include <vector>
|
|
23
18
|
|
|
24
|
-
|
|
19
|
+
#include "execution.h"
|
|
20
|
+
#include "report.h"
|
|
21
|
+
#include "trace.h"
|
|
25
22
|
|
|
26
|
-
|
|
27
|
-
/// job, or by a callback the platform made.
|
|
28
|
-
void reportUncaught(std::exception_ptr e, const char* where);
|
|
29
|
-
/// Logs an error where the platform shows an app's errors (logcat, the
|
|
30
|
-
/// unified log) and to stderr.
|
|
31
|
-
void logError(const char* message);
|
|
23
|
+
namespace lucent {
|
|
32
24
|
|
|
33
25
|
/// The Lucent lock: recursive, as a Lucent call may call back into
|
|
34
26
|
/// JavaScript that calls Lucent again. Every synchronous call from
|
|
35
|
-
/// JavaScript takes it, so the uncontended path is one
|
|
36
|
-
/// std::recursive_mutex (a pthread mutex) cost a third of a
|
|
27
|
+
/// JavaScript takes it, so the uncontended path is one atomic increment
|
|
28
|
+
/// and a load; std::recursive_mutex (a pthread mutex) cost a third of a
|
|
29
|
+
/// call.
|
|
30
|
+
///
|
|
31
|
+
/// It is fair: a ticket lock, serving threads in the order they asked. A
|
|
32
|
+
/// thread that releases it and asks again (the Lucent thread between the
|
|
33
|
+
/// turns of a loop that yields) queues behind the threads already waiting,
|
|
34
|
+
/// so a waiter gets in within one turn per thread ahead of it. Waiters
|
|
37
35
|
/// block in atomic::wait (a futex, or __ulock on Apple platforms).
|
|
38
36
|
class LucentLock {
|
|
39
37
|
public:
|
|
40
38
|
void lock() {
|
|
41
39
|
std::thread::id self = std::this_thread::get_id();
|
|
40
|
+
|
|
42
41
|
// Only this thread stores its own id, so a relaxed read that sees it is
|
|
43
42
|
// exact; any other value means another owner or none.
|
|
44
43
|
if (owner_.load(std::memory_order_relaxed) == self) {
|
|
45
44
|
depth_++;
|
|
46
45
|
return;
|
|
47
46
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
47
|
+
|
|
48
|
+
// Sequentially consistent, with unlock(): either it sees this ticket
|
|
49
|
+
// and wakes the waiters, or this thread sees the ticket it serves.
|
|
50
|
+
const uint32_t ticket = next_.fetch_add(1, std::memory_order_seq_cst);
|
|
51
|
+
|
|
52
|
+
uint32_t serving = serving_.load(std::memory_order_seq_cst);
|
|
53
|
+
while (serving != ticket) {
|
|
54
|
+
serving_.wait(serving, std::memory_order_relaxed);
|
|
55
|
+
serving = serving_.load(std::memory_order_seq_cst);
|
|
55
56
|
}
|
|
57
|
+
|
|
56
58
|
owner_.store(self, std::memory_order_relaxed);
|
|
57
59
|
depth_ = 1;
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
bool try_lock() {
|
|
61
63
|
std::thread::id self = std::this_thread::get_id();
|
|
64
|
+
|
|
62
65
|
if (owner_.load(std::memory_order_relaxed) == self) {
|
|
63
66
|
depth_++;
|
|
64
67
|
return true;
|
|
65
68
|
}
|
|
66
|
-
|
|
67
|
-
|
|
69
|
+
|
|
70
|
+
// Free is no ticket handed out beyond the one served: take that one.
|
|
71
|
+
uint32_t serving = serving_.load(std::memory_order_seq_cst);
|
|
72
|
+
uint32_t expected = serving;
|
|
73
|
+
if (!next_.compare_exchange_strong(expected, serving + 1, std::memory_order_seq_cst, std::memory_order_relaxed)) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
68
77
|
owner_.store(self, std::memory_order_relaxed);
|
|
69
78
|
depth_ = 1;
|
|
70
79
|
return true;
|
|
71
80
|
}
|
|
72
81
|
|
|
82
|
+
/// Whether the calling thread holds the lock.
|
|
83
|
+
bool heldByCurrentThread() const { return owner_.load(std::memory_order_relaxed) == std::this_thread::get_id(); }
|
|
84
|
+
|
|
73
85
|
void unlock() {
|
|
74
86
|
if (--depth_ > 0) return;
|
|
87
|
+
|
|
75
88
|
owner_.store(std::thread::id(), std::memory_order_relaxed);
|
|
76
|
-
|
|
89
|
+
|
|
90
|
+
// Only the owner moves `serving_`, so it may read it relaxed.
|
|
91
|
+
const uint32_t next = serving_.load(std::memory_order_relaxed) + 1;
|
|
92
|
+
serving_.store(next, std::memory_order_seq_cst);
|
|
93
|
+
|
|
94
|
+
// A ticket beyond the one now served: someone waits. Every waiter
|
|
95
|
+
// wakes, and the one holding `next` goes in.
|
|
96
|
+
if (next_.load(std::memory_order_seq_cst) != next) serving_.notify_all();
|
|
77
97
|
}
|
|
78
98
|
|
|
79
99
|
private:
|
|
80
|
-
|
|
81
|
-
|
|
100
|
+
/// The next ticket to hand out, and the one that holds the lock (or may
|
|
101
|
+
/// take it). Equal: free.
|
|
102
|
+
std::atomic<uint32_t> next_{0};
|
|
103
|
+
std::atomic<uint32_t> serving_{0};
|
|
104
|
+
|
|
82
105
|
std::atomic<std::thread::id> owner_{};
|
|
83
106
|
unsigned depth_ = 0;
|
|
84
107
|
};
|
|
85
108
|
|
|
86
|
-
|
|
109
|
+
/// The legacy module context: every module's code, serialized by the Lucent
|
|
110
|
+
/// lock. Its turns run on the Lucent thread; synchronous calls from other
|
|
111
|
+
/// threads (JavaScript's, or a platform callback's) enter it by taking the
|
|
112
|
+
/// lock, which is what makes it current (ExecutionContext::current).
|
|
113
|
+
class Scheduler final : public ExecutionContext {
|
|
87
114
|
public:
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
static Scheduler& instance() {
|
|
91
|
-
// Leaked on purpose: jobs may still reference the scheduler during
|
|
92
|
-
// static destruction at process exit.
|
|
93
|
-
static Scheduler* s = new Scheduler();
|
|
94
|
-
return *s;
|
|
95
|
-
}
|
|
115
|
+
static Scheduler& instance();
|
|
96
116
|
|
|
97
|
-
/// Static, like
|
|
117
|
+
/// Static, like microtasksPending(): a call from JavaScript reaches both
|
|
98
118
|
/// without the scheduler's function-local static.
|
|
99
119
|
static LucentLock& lock() { return lock_; }
|
|
100
120
|
|
|
101
|
-
///
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
void enqueueMicrotask(Job job);
|
|
107
|
-
/// Runs queued microtasks. Callers hold the lock.
|
|
108
|
-
void drainMicrotasks();
|
|
109
|
-
static bool hasMicrotasks() { return pendingMicrotasks_ > 0; }
|
|
110
|
-
|
|
111
|
-
bool onLucentThread() const { return std::this_thread::get_id() == threadId_; }
|
|
121
|
+
/// Whether microtasks are queued here. Callers hold the lock.
|
|
122
|
+
static bool microtasksPending() { return microtaskCount_ > 0; }
|
|
123
|
+
|
|
124
|
+
bool onLucentThread() const { return worker_->isCurrent(); }
|
|
125
|
+
|
|
112
126
|
/// Jobs queued or running, and timers pending (for tests and shutdown).
|
|
113
|
-
size_t pendingWork();
|
|
127
|
+
size_t pendingWork() { return worker_->pendingWork(); }
|
|
128
|
+
|
|
114
129
|
/// Blocks until no jobs or timers are pending, or the timeout elapses.
|
|
115
|
-
bool waitIdle(double timeoutMs);
|
|
130
|
+
bool waitIdle(double timeoutMs) { return worker_->waitIdle(timeoutMs); }
|
|
116
131
|
|
|
117
132
|
private:
|
|
118
133
|
Scheduler();
|
|
119
|
-
~Scheduler();
|
|
120
|
-
void run();
|
|
121
134
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
135
|
+
bool dispatch(Job job) override { return worker_->post(std::move(job)); }
|
|
136
|
+
bool dispatchDelayed(double ms, Job job) override { return worker_->postDelayed(ms, std::move(job)); }
|
|
137
|
+
bool onExecutor() const override { return worker_->isCurrent(); }
|
|
138
|
+
|
|
139
|
+
/// Holds the lock for the job and the microtasks after it.
|
|
140
|
+
void runTurn(Job& job) override;
|
|
128
141
|
|
|
129
142
|
static inline LucentLock lock_;
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
std::
|
|
133
|
-
std::deque<Job> jobs_;
|
|
134
|
-
std::priority_queue<Timer, std::vector<Timer>, std::greater<Timer>> timers_;
|
|
135
|
-
uint64_t timerSeq_ = 0;
|
|
136
|
-
size_t running_ = 0;
|
|
137
|
-
std::deque<Job> microtasks_;
|
|
138
|
-
static inline size_t pendingMicrotasks_ = 0; // microtasks_.size(), under the lock
|
|
139
|
-
bool stopping_ = false;
|
|
140
|
-
std::thread thread_;
|
|
141
|
-
std::thread::id threadId_;
|
|
143
|
+
/// Guarded by the lock.
|
|
144
|
+
static inline size_t microtaskCount_ = 0;
|
|
145
|
+
std::shared_ptr<WorkerThread> worker_;
|
|
142
146
|
};
|
|
143
147
|
|
|
144
|
-
/// Entered for every call from JavaScript into Lucent code
|
|
145
|
-
///
|
|
148
|
+
/// Entered for every call from JavaScript into Lucent code, and by platform
|
|
149
|
+
/// callbacks into module code (callNow): enters the legacy module context
|
|
150
|
+
/// by holding the Lucent lock. On exit off the Lucent thread, hands pending
|
|
151
|
+
/// microtasks to it; on the Lucent thread, the enclosing turn runs them once
|
|
152
|
+
/// its stack is empty.
|
|
146
153
|
class LucentScope {
|
|
147
154
|
public:
|
|
148
|
-
LucentScope()
|
|
155
|
+
LucentScope() {
|
|
156
|
+
if (trace::enabled()) [[unlikely]] {
|
|
157
|
+
lockTraced(Scheduler::lock());
|
|
158
|
+
} else {
|
|
159
|
+
Scheduler::lock().lock();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
149
163
|
~LucentScope() {
|
|
150
|
-
if (Scheduler::
|
|
164
|
+
if (Scheduler::microtasksPending()) handOffMicrotasks();
|
|
165
|
+
|
|
166
|
+
Scheduler::lock().unlock();
|
|
151
167
|
}
|
|
168
|
+
|
|
152
169
|
LucentScope(const LucentScope&) = delete;
|
|
153
170
|
LucentScope& operator=(const LucentScope&) = delete;
|
|
154
171
|
|
|
172
|
+
/// Takes `lock`, recording the wait as a span (a call's, or a turn's).
|
|
173
|
+
static void lockTraced(LucentLock& lock);
|
|
174
|
+
|
|
155
175
|
private:
|
|
156
176
|
static void handOffMicrotasks();
|
|
157
|
-
std::unique_lock<LucentLock> guard_;
|
|
158
177
|
};
|
|
159
178
|
|
|
160
179
|
} // namespace lucent
|