@onekeyfe/react-native-background-thread 3.0.40 → 3.0.42

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 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, const std::string &callId) {
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 (auto &snap : toNotify) {
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
- snap.executor([cb, alive, id](jsi::Runtime &rt) {
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
- auto value = extractValue(rt, args[1]);
210
- {
211
- std::lock_guard<std::mutex> lock(mutex_);
212
- slots_.insert_or_assign(callId, std::move(value));
213
- }
214
- // Notify OUTSIDE the lock
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
- // read(callId: string): bool | number | string | undefined
221
- // Deletes the entry after reading (read-and-delete semantics).
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].isString()) {
228
- throw jsi::JSError(
229
- rt, "SharedRPC.read expects (callId: string)");
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 callId = args[0].getString(rt).utf8(rt);
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 it = slots_.find(callId);
235
- if (it == slots_.end()) {
236
- return jsi::Value::undefined();
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
- // has(callId: string): boolean
246
- if (prop == "has") {
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.has expects (callId: string)");
278
+ rt, "SharedRPC.registerReadinessKey expects (key: string)");
254
279
  }
255
- auto callId = args[0].getString(rt).utf8(rt);
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.callback = std::move(fn);
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
- void notifyOtherRuntime(jsi::Runtime &callerRt, const std::string &callId);
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
  };
@@ -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
- read(callId: string): string | number | boolean | undefined;
4
- has(callId: string): boolean;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/react-native-background-thread",
3
- "version": "3.0.40",
3
+ "version": "3.0.42",
4
4
  "description": "react-native-background-thread",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
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
- read(callId: string): string | number | boolean | undefined;
4
- has(callId: string): boolean;
5
- readonly pendingCount: number;
6
- onWrite(callback: (callId: string) => void): void;
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 {