@onekeyfe/react-native-background-thread 3.0.40 → 3.0.43
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/cpp/SharedRPC.cpp +57 -59
- package/cpp/SharedRPC.h +10 -2
- package/cpp/SharedStore.cpp +5 -0
- package/cpp/SharedStore.h +6 -0
- package/lib/typescript/src/SharedRPC.d.ts +2 -4
- package/package.json +1 -1
- package/src/SharedRPC.ts +10 -4
package/cpp/SharedRPC.cpp
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#include "SharedRPC.h"
|
|
2
2
|
|
|
3
|
+
#include "SharedStore.h"
|
|
4
|
+
|
|
3
5
|
#include <algorithm>
|
|
4
6
|
|
|
5
7
|
#ifdef __ANDROID__
|
|
@@ -10,8 +12,8 @@
|
|
|
10
12
|
#endif
|
|
11
13
|
|
|
12
14
|
std::mutex SharedRPC::mutex_;
|
|
13
|
-
std::unordered_map<std::string, RPCValue> SharedRPC::slots_;
|
|
14
15
|
std::vector<RuntimeListener> SharedRPC::listeners_;
|
|
16
|
+
std::unordered_map<std::string, std::string> SharedRPC::readinessKeys_;
|
|
15
17
|
|
|
16
18
|
void SharedRPC::install(jsi::Runtime &rt) {
|
|
17
19
|
auto rpc = std::make_shared<SharedRPC>();
|
|
@@ -86,12 +88,21 @@ bool SharedRPC::invalidate(const std::string &runtimeId) {
|
|
|
86
88
|
return l.runtimeId == runtimeId;
|
|
87
89
|
}),
|
|
88
90
|
listeners_.end());
|
|
91
|
+
|
|
92
|
+
// Restart freshness: drop this runtime's latched readiness key from
|
|
93
|
+
// SharedStore so a respawned reader can never observe a prior-life
|
|
94
|
+
// "peer ready". Robust to crash-restart because it runs in the native
|
|
95
|
+
// invalidate path, not a JS teardown hook. SharedStore::eraseKey takes its
|
|
96
|
+
// own mutex (distinct from mutex_, only ever locked SharedRPC→SharedStore).
|
|
97
|
+
auto keyIt = readinessKeys_.find(runtimeId);
|
|
98
|
+
if (keyIt != readinessKeys_.end()) {
|
|
99
|
+
SharedStore::eraseKey(keyIt->second);
|
|
100
|
+
}
|
|
89
101
|
return found;
|
|
90
102
|
}
|
|
91
103
|
|
|
92
104
|
void SharedRPC::reset() {
|
|
93
105
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
94
|
-
slots_.clear();
|
|
95
106
|
// Intentionally leak jsi::Function callbacks to avoid destroying them on the
|
|
96
107
|
// wrong thread (same rationale as the leak in install() for reload scenarios).
|
|
97
108
|
// Also flip alive=false so any executor lambda still in flight short-circuits
|
|
@@ -107,7 +118,8 @@ void SharedRPC::reset() {
|
|
|
107
118
|
listeners_.clear();
|
|
108
119
|
}
|
|
109
120
|
|
|
110
|
-
void SharedRPC::notifyOtherRuntime(jsi::Runtime &callerRt,
|
|
121
|
+
void SharedRPC::notifyOtherRuntime(jsi::Runtime &callerRt,
|
|
122
|
+
const std::string &callId, RPCValue value) {
|
|
111
123
|
// Collect executors and callbacks under lock, then invoke outside lock
|
|
112
124
|
// to avoid deadlock (executor may schedule work that also acquires mutex_).
|
|
113
125
|
//
|
|
@@ -140,12 +152,22 @@ void SharedRPC::notifyOtherRuntime(jsi::Runtime &callerRt, const std::string &ca
|
|
|
140
152
|
RPC_LOG(" toNotify count: %zu", toNotify.size());
|
|
141
153
|
}
|
|
142
154
|
|
|
143
|
-
for (
|
|
155
|
+
for (size_t i = 0; i < toNotify.size(); ++i) {
|
|
156
|
+
auto &snap = toNotify[i];
|
|
144
157
|
auto id = callId;
|
|
145
158
|
RPC_LOG(" invoking executor for callId=%s", id.c_str());
|
|
146
159
|
auto cb = snap.callback;
|
|
147
160
|
auto alive = snap.alive;
|
|
148
|
-
|
|
161
|
+
// The payload rides the dispatched lambda's capture. In practice there is
|
|
162
|
+
// exactly one non-caller listener, so the last (only) iteration moves the
|
|
163
|
+
// value; any earlier listener takes a copy. A captured RPCValue is a pure
|
|
164
|
+
// C++ value type — its destructor is thread-agnostic, so dropping this
|
|
165
|
+
// lambda during teardown is safe (unlike the per-runtime jsi::Function,
|
|
166
|
+
// which is why `cb` is leaked on invalidate/reset). The jsi::String/value
|
|
167
|
+
// is materialized via toJSI ONLY inside cb->call, i.e. on the target
|
|
168
|
+
// runtime's JS thread where it is legal.
|
|
169
|
+
RPCValue v = (i + 1 == toNotify.size()) ? std::move(value) : value;
|
|
170
|
+
snap.executor([cb, alive, id, v = std::move(v)](jsi::Runtime &rt) {
|
|
149
171
|
// Listener was invalidated between snapshot and dispatch — bail
|
|
150
172
|
// before calling into a runtime that may already be torn down.
|
|
151
173
|
if (!alive || !alive->load()) {
|
|
@@ -155,7 +177,7 @@ void SharedRPC::notifyOtherRuntime(jsi::Runtime &callerRt, const std::string &ca
|
|
|
155
177
|
}
|
|
156
178
|
RPC_LOG(" executor work running for callId=%s", id.c_str());
|
|
157
179
|
try {
|
|
158
|
-
cb->call(rt, jsi::String::createFromUtf8(rt, id));
|
|
180
|
+
cb->call(rt, jsi::String::createFromUtf8(rt, id), toJSI(rt, v));
|
|
159
181
|
RPC_LOG(" cb->call succeeded for callId=%s", id.c_str());
|
|
160
182
|
} catch (const jsi::JSError &e) {
|
|
161
183
|
RPC_LOG(" JSError in cb->call: %s", e.getMessage().c_str());
|
|
@@ -206,83 +228,61 @@ jsi::Value SharedRPC::get(jsi::Runtime &rt, const jsi::PropNameID &name) {
|
|
|
206
228
|
rt, "SharedRPC.write expects (callId: string, value)");
|
|
207
229
|
}
|
|
208
230
|
auto callId = args[0].getString(rt).utf8(rt);
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
notifyOtherRuntime(rt, callId);
|
|
231
|
+
RPCValue value = extractValue(rt, args[1]);
|
|
232
|
+
// Value-inline: no slot map. The payload is handed straight to
|
|
233
|
+
// notifyOtherRuntime, which moves it into the dispatched lambda's
|
|
234
|
+
// capture and delivers it as the callback's 2nd argument on the
|
|
235
|
+
// target runtime. No lock here — notify takes the lock internally.
|
|
236
|
+
notifyOtherRuntime(rt, callId, std::move(value));
|
|
216
237
|
return jsi::Value::undefined();
|
|
217
238
|
});
|
|
218
239
|
}
|
|
219
240
|
|
|
220
|
-
//
|
|
221
|
-
|
|
222
|
-
if (prop == "read") {
|
|
241
|
+
// onWrite(callback: (callId: string, value: string|number|boolean) => void)
|
|
242
|
+
if (prop == "onWrite") {
|
|
223
243
|
return jsi::Function::createFromHostFunction(
|
|
224
244
|
rt, name, 1,
|
|
225
245
|
[](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args,
|
|
226
246
|
size_t count) -> jsi::Value {
|
|
227
|
-
if (count < 1 || !args[0].
|
|
228
|
-
|
|
229
|
-
|
|
247
|
+
if (count < 1 || !args[0].isObject() ||
|
|
248
|
+
!args[0].asObject(rt).isFunction(rt)) {
|
|
249
|
+
throw jsi::JSError(rt, "SharedRPC.onWrite expects a function");
|
|
230
250
|
}
|
|
231
|
-
auto
|
|
251
|
+
auto fn = std::make_shared<jsi::Function>(
|
|
252
|
+
args[0].asObject(rt).asFunction(rt));
|
|
232
253
|
{
|
|
233
254
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
234
|
-
auto
|
|
235
|
-
|
|
236
|
-
|
|
255
|
+
for (auto &listener : listeners_) {
|
|
256
|
+
if (listener.runtime == &rt) {
|
|
257
|
+
listener.callback = std::move(fn);
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
237
260
|
}
|
|
238
|
-
auto value = std::move(it->second);
|
|
239
|
-
slots_.erase(it);
|
|
240
|
-
return toJSI(rt, value);
|
|
241
261
|
}
|
|
262
|
+
return jsi::Value::undefined();
|
|
242
263
|
});
|
|
243
264
|
}
|
|
244
265
|
|
|
245
|
-
//
|
|
246
|
-
|
|
266
|
+
// registerReadinessKey(key: string): void
|
|
267
|
+
// The calling runtime declares which SharedStore key holds its readiness
|
|
268
|
+
// payload, so invalidate() can clear exactly that key on teardown (restart
|
|
269
|
+
// freshness). Identified by the calling runtime pointer → its listener's
|
|
270
|
+
// runtimeId, mirroring how onWrite finds the listener.
|
|
271
|
+
if (prop == "registerReadinessKey") {
|
|
247
272
|
return jsi::Function::createFromHostFunction(
|
|
248
273
|
rt, name, 1,
|
|
249
274
|
[](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args,
|
|
250
275
|
size_t count) -> jsi::Value {
|
|
251
276
|
if (count < 1 || !args[0].isString()) {
|
|
252
277
|
throw jsi::JSError(
|
|
253
|
-
rt, "SharedRPC.
|
|
278
|
+
rt, "SharedRPC.registerReadinessKey expects (key: string)");
|
|
254
279
|
}
|
|
255
|
-
auto
|
|
256
|
-
{
|
|
257
|
-
std::lock_guard<std::mutex> lock(mutex_);
|
|
258
|
-
return jsi::Value(slots_.count(callId) > 0);
|
|
259
|
-
}
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// pendingCount: number (getter, not a function)
|
|
264
|
-
if (prop == "pendingCount") {
|
|
265
|
-
std::lock_guard<std::mutex> lock(mutex_);
|
|
266
|
-
return jsi::Value(static_cast<double>(slots_.size()));
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// onWrite(callback: (callId: string) => void): void
|
|
270
|
-
if (prop == "onWrite") {
|
|
271
|
-
return jsi::Function::createFromHostFunction(
|
|
272
|
-
rt, name, 1,
|
|
273
|
-
[](jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args,
|
|
274
|
-
size_t count) -> jsi::Value {
|
|
275
|
-
if (count < 1 || !args[0].isObject() ||
|
|
276
|
-
!args[0].asObject(rt).isFunction(rt)) {
|
|
277
|
-
throw jsi::JSError(rt, "SharedRPC.onWrite expects a function");
|
|
278
|
-
}
|
|
279
|
-
auto fn = std::make_shared<jsi::Function>(
|
|
280
|
-
args[0].asObject(rt).asFunction(rt));
|
|
280
|
+
auto key = args[0].getString(rt).utf8(rt);
|
|
281
281
|
{
|
|
282
282
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
283
283
|
for (auto &listener : listeners_) {
|
|
284
284
|
if (listener.runtime == &rt) {
|
|
285
|
-
listener.
|
|
285
|
+
readinessKeys_[listener.runtimeId] = std::move(key);
|
|
286
286
|
break;
|
|
287
287
|
}
|
|
288
288
|
}
|
|
@@ -297,9 +297,7 @@ jsi::Value SharedRPC::get(jsi::Runtime &rt, const jsi::PropNameID &name) {
|
|
|
297
297
|
std::vector<jsi::PropNameID> SharedRPC::getPropertyNames(jsi::Runtime &rt) {
|
|
298
298
|
std::vector<jsi::PropNameID> props;
|
|
299
299
|
props.push_back(jsi::PropNameID::forUtf8(rt, "write"));
|
|
300
|
-
props.push_back(jsi::PropNameID::forUtf8(rt, "read"));
|
|
301
|
-
props.push_back(jsi::PropNameID::forUtf8(rt, "has"));
|
|
302
|
-
props.push_back(jsi::PropNameID::forUtf8(rt, "pendingCount"));
|
|
303
300
|
props.push_back(jsi::PropNameID::forUtf8(rt, "onWrite"));
|
|
301
|
+
props.push_back(jsi::PropNameID::forUtf8(rt, "registerReadinessKey"));
|
|
304
302
|
return props;
|
|
305
303
|
}
|
package/cpp/SharedRPC.h
CHANGED
|
@@ -56,9 +56,17 @@ public:
|
|
|
56
56
|
private:
|
|
57
57
|
static RPCValue extractValue(jsi::Runtime &rt, const jsi::Value &val);
|
|
58
58
|
static jsi::Value toJSI(jsi::Runtime &rt, const RPCValue &val);
|
|
59
|
-
|
|
59
|
+
// Value-inline messaging: the payload rides the dispatched lambda's capture
|
|
60
|
+
// (delivered as the 2nd callback arg on the target runtime) instead of being
|
|
61
|
+
// parked in a shared slot map and read back. No `slots_` involved.
|
|
62
|
+
void notifyOtherRuntime(jsi::Runtime &callerRt, const std::string &callId,
|
|
63
|
+
RPCValue value);
|
|
60
64
|
|
|
61
65
|
static std::mutex mutex_;
|
|
62
|
-
static std::unordered_map<std::string, RPCValue> slots_;
|
|
63
66
|
static std::vector<RuntimeListener> listeners_;
|
|
67
|
+
// runtimeId ("main"/"background") → the SharedStore readiness key that
|
|
68
|
+
// runtime owns. Registered by the JS via `registerReadinessKey`; consumed by
|
|
69
|
+
// invalidate() to clear that key from SharedStore on teardown so a respawned
|
|
70
|
+
// reader never sees a prior-life "peer ready" value (restart freshness).
|
|
71
|
+
static std::unordered_map<std::string, std::string> readinessKeys_;
|
|
64
72
|
};
|
package/cpp/SharedStore.cpp
CHANGED
|
@@ -15,6 +15,11 @@ void SharedStore::reset() {
|
|
|
15
15
|
data_.clear();
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
void SharedStore::eraseKey(const std::string &key) {
|
|
19
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
20
|
+
data_.erase(key);
|
|
21
|
+
}
|
|
22
|
+
|
|
18
23
|
StoreValue SharedStore::extractValue(jsi::Runtime &rt,
|
|
19
24
|
const jsi::Value &val) {
|
|
20
25
|
if (val.isBool()) {
|
package/cpp/SharedStore.h
CHANGED
|
@@ -19,6 +19,12 @@ public:
|
|
|
19
19
|
static void install(jsi::Runtime &rt);
|
|
20
20
|
static void reset();
|
|
21
21
|
|
|
22
|
+
/// Native-side erase of a single key. Used by SharedRPC::invalidate to drop
|
|
23
|
+
/// the readiness key owned by a runtime being torn down, so a respawned
|
|
24
|
+
/// reader can never observe a prior-life "peer ready" (restart freshness).
|
|
25
|
+
/// Safe to call from any thread; takes the SharedStore mutex internally.
|
|
26
|
+
static void eraseKey(const std::string &key);
|
|
27
|
+
|
|
22
28
|
private:
|
|
23
29
|
static StoreValue extractValue(jsi::Runtime &rt, const jsi::Value &val);
|
|
24
30
|
static jsi::Value toJSI(jsi::Runtime &rt, const StoreValue &val);
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
export interface ISharedRPC {
|
|
2
2
|
write(callId: string, value: string | number | boolean): void;
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
readonly pendingCount: number;
|
|
6
|
-
onWrite(callback: (callId: string) => void): void;
|
|
3
|
+
onWrite(callback: (callId: string, value: string | number | boolean) => void): void;
|
|
4
|
+
registerReadinessKey(key: string): void;
|
|
7
5
|
}
|
|
8
6
|
declare global {
|
|
9
7
|
var sharedRPC: ISharedRPC | undefined;
|
package/package.json
CHANGED
package/src/SharedRPC.ts
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
export interface ISharedRPC {
|
|
2
2
|
write(callId: string, value: string | number | boolean): void;
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
onWrite(
|
|
3
|
+
// Value-inline messaging: the notify callback delivers BOTH the callId and
|
|
4
|
+
// the payload value, so there is no read-back. `read`/`has`/`pendingCount`
|
|
5
|
+
// (the old slot-map introspection) are gone.
|
|
6
|
+
onWrite(
|
|
7
|
+
callback: (callId: string, value: string | number | boolean) => void,
|
|
8
|
+
): void;
|
|
9
|
+
// The calling runtime declares which SharedStore key holds its readiness
|
|
10
|
+
// payload, so the native invalidate path clears exactly that key on teardown
|
|
11
|
+
// (restart freshness — prevents a respawned reader seeing prior-life state).
|
|
12
|
+
registerReadinessKey(key: string): void;
|
|
7
13
|
}
|
|
8
14
|
|
|
9
15
|
declare global {
|