@onekeyfe/react-native-background-thread 3.0.60 → 3.0.62

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.
@@ -70,6 +70,166 @@ static std::mutex gWorkMutex;
70
70
  static std::unordered_map<int64_t, std::function<void(jsi::Runtime &)>> gPendingWork;
71
71
  static int64_t gNextWorkId = 0;
72
72
 
73
+ using JavaObjectRef = std::shared_ptr<_jobject>;
74
+
75
+ static constexpr size_t kRuntimeDrainBatchSize = 64;
76
+ static constexpr size_t kRuntimeQueueWarnThreshold = 128;
77
+ static constexpr size_t kRuntimeQueueWarnInterval = 128;
78
+
79
+ struct RuntimeWorkQueue {
80
+ std::deque<std::function<void(jsi::Runtime &)>> items;
81
+ bool drainScheduled = false;
82
+ };
83
+
84
+ static RuntimeWorkQueue gMainRuntimeWorkQueue;
85
+ static RuntimeWorkQueue gBgRuntimeWorkQueue;
86
+
87
+ static RuntimeWorkQueue &getRuntimeWorkQueue(bool isMain) {
88
+ return isMain ? gMainRuntimeWorkQueue : gBgRuntimeWorkQueue;
89
+ }
90
+
91
+ static bool callScheduleOnJSThread(const JavaObjectRef &ref, bool isMain, int64_t workId) {
92
+ JNIEnv *env = getJNIEnv();
93
+ if (!env || !ref) {
94
+ LOGE("executor: env=%p, ref=%p — aborting", env, ref.get());
95
+ return false;
96
+ }
97
+
98
+ jclass cls = env->GetObjectClass(ref.get());
99
+ if (!cls) {
100
+ LOGE("executor: GetObjectClass failed");
101
+ if (env->ExceptionCheck()) {
102
+ env->ExceptionDescribe();
103
+ env->ExceptionClear();
104
+ }
105
+ return false;
106
+ }
107
+
108
+ jmethodID mid = env->GetMethodID(cls, "scheduleOnJSThread", "(ZJ)Z");
109
+ if (!mid) {
110
+ LOGE("executor: scheduleOnJSThread method not found!");
111
+ if (env->ExceptionCheck()) {
112
+ env->ExceptionDescribe();
113
+ env->ExceptionClear();
114
+ }
115
+ env->DeleteLocalRef(cls);
116
+ return false;
117
+ }
118
+
119
+ LOGI("executor: calling scheduleOnJSThread(isMain=%d, workId=%ld)", isMain, (long)workId);
120
+ jboolean scheduled = env->CallBooleanMethod(
121
+ ref.get(),
122
+ mid,
123
+ static_cast<jboolean>(isMain),
124
+ static_cast<jlong>(workId));
125
+ if (env->ExceptionCheck()) {
126
+ LOGE("executor: JNI exception after scheduleOnJSThread");
127
+ env->ExceptionDescribe();
128
+ env->ExceptionClear();
129
+ env->DeleteLocalRef(cls);
130
+ return false;
131
+ }
132
+ env->DeleteLocalRef(cls);
133
+ return scheduled == JNI_TRUE;
134
+ }
135
+
136
+ static void scheduleRuntimeDrain(const JavaObjectRef &ref, bool isMain);
137
+
138
+ static void drainRuntimeWorkQueue(jsi::Runtime &rt, JavaObjectRef ref, bool isMain) {
139
+ size_t drained = 0;
140
+
141
+ while (drained < kRuntimeDrainBatchSize) {
142
+ std::function<void(jsi::Runtime &)> work;
143
+ {
144
+ std::lock_guard<std::mutex> lock(gWorkMutex);
145
+ auto &queue = getRuntimeWorkQueue(isMain);
146
+ if (queue.items.empty()) {
147
+ break;
148
+ }
149
+ work = std::move(queue.items.front());
150
+ queue.items.pop_front();
151
+ }
152
+
153
+ try {
154
+ work(rt);
155
+ } catch (const jsi::JSError &e) {
156
+ LOGE("JSError in runtime drain work: %s", e.getMessage().c_str());
157
+ } catch (const std::exception &e) {
158
+ LOGE("Error in runtime drain work: %s", e.what());
159
+ } catch (...) {
160
+ LOGE("Unknown error in runtime drain work");
161
+ }
162
+ drained += 1;
163
+ }
164
+
165
+ bool shouldReschedule = false;
166
+ size_t remaining = 0;
167
+ {
168
+ std::lock_guard<std::mutex> lock(gWorkMutex);
169
+ auto &queue = getRuntimeWorkQueue(isMain);
170
+ remaining = queue.items.size();
171
+ if (remaining == 0) {
172
+ queue.drainScheduled = false;
173
+ } else {
174
+ shouldReschedule = true;
175
+ }
176
+ }
177
+
178
+ if (drained > 1 || remaining > 0) {
179
+ LOGI("executor: drained runtime queue isMain=%d, drained=%zu, remaining=%zu",
180
+ isMain, drained, remaining);
181
+ }
182
+
183
+ if (shouldReschedule) {
184
+ scheduleRuntimeDrain(ref, isMain);
185
+ }
186
+ }
187
+
188
+ static void scheduleRuntimeDrain(const JavaObjectRef &ref, bool isMain) {
189
+ int64_t workId;
190
+ size_t queued = 0;
191
+ {
192
+ std::lock_guard<std::mutex> lock(gWorkMutex);
193
+ workId = gNextWorkId++;
194
+ queued = getRuntimeWorkQueue(isMain).items.size();
195
+ gPendingWork[workId] = [ref, isMain](jsi::Runtime &rt) {
196
+ drainRuntimeWorkQueue(rt, ref, isMain);
197
+ };
198
+ }
199
+
200
+ bool scheduled = callScheduleOnJSThread(ref, isMain, workId);
201
+ if (!scheduled) {
202
+ std::lock_guard<std::mutex> lock(gWorkMutex);
203
+ gPendingWork.erase(workId);
204
+ getRuntimeWorkQueue(isMain).drainScheduled = false;
205
+ LOGE("executor: failed to schedule runtime drain isMain=%d, workId=%ld, queued=%zu",
206
+ isMain, (long)workId, queued);
207
+ }
208
+ }
209
+
210
+ static void enqueueRuntimeWork(JavaObjectRef ref, bool isMain, std::function<void(jsi::Runtime &)> work) {
211
+ bool shouldSchedule = false;
212
+ size_t queued = 0;
213
+ {
214
+ std::lock_guard<std::mutex> lock(gWorkMutex);
215
+ auto &queue = getRuntimeWorkQueue(isMain);
216
+ queue.items.push_back(std::move(work));
217
+ queued = queue.items.size();
218
+ if (!queue.drainScheduled) {
219
+ queue.drainScheduled = true;
220
+ shouldSchedule = true;
221
+ }
222
+ }
223
+
224
+ if (queued >= kRuntimeQueueWarnThreshold && queued % kRuntimeQueueWarnInterval == 0) {
225
+ LOGE("executor: runtime queue backlog isMain=%d, queued=%zu", isMain, queued);
226
+ }
227
+
228
+ if (shouldSchedule) {
229
+ scheduleRuntimeDrain(ref, isMain);
230
+ }
231
+ }
232
+
73
233
  // Called from Kotlin after runOnJSQueueThread dispatches to the correct thread.
74
234
  extern "C" JNIEXPORT void JNICALL
75
235
  Java_com_backgroundthread_BackgroundThreadManager_nativeExecuteWork(
@@ -470,37 +630,7 @@ Java_com_backgroundthread_BackgroundThreadManager_nativeInstallSharedBridge(
470
630
  bool capturedIsMain = static_cast<bool>(isMain);
471
631
 
472
632
  RPCRuntimeExecutor executor = [ref, capturedIsMain](std::function<void(jsi::Runtime &)> work) {
473
- JNIEnv *env = getJNIEnv();
474
- if (!env || !ref) {
475
- LOGE("executor: env=%p, ref=%p — aborting", env, ref.get());
476
- return;
477
- }
478
-
479
- int64_t workId;
480
- {
481
- std::lock_guard<std::mutex> lock(gWorkMutex);
482
- workId = gNextWorkId++;
483
- gPendingWork[workId] = std::move(work);
484
- }
485
-
486
- jclass cls = env->GetObjectClass(ref.get());
487
- jmethodID mid = env->GetMethodID(cls, "scheduleOnJSThread", "(ZJ)V");
488
- if (mid) {
489
- LOGI("executor: calling scheduleOnJSThread(isMain=%d, workId=%ld)", capturedIsMain, (long)workId);
490
- env->CallVoidMethod(ref.get(), mid, static_cast<jboolean>(capturedIsMain), static_cast<jlong>(workId));
491
- if (env->ExceptionCheck()) {
492
- LOGE("executor: JNI exception after scheduleOnJSThread");
493
- env->ExceptionDescribe();
494
- env->ExceptionClear();
495
- }
496
- } else {
497
- LOGE("executor: scheduleOnJSThread method not found!");
498
- if (env->ExceptionCheck()) {
499
- env->ExceptionDescribe();
500
- env->ExceptionClear();
501
- }
502
- }
503
- env->DeleteLocalRef(cls);
633
+ enqueueRuntimeWork(ref, capturedIsMain, std::move(work));
504
634
  };
505
635
 
506
636
  std::string runtimeId = isMain ? "main" : "background";
@@ -652,6 +782,13 @@ Java_com_backgroundthread_BackgroundThreadManager_nativeDestroy(
652
782
  new std::function<void(jsi::Runtime &)>(std::move(entry.second));
653
783
  }
654
784
  gPendingWork.clear();
785
+ for (auto *queue : {&gMainRuntimeWorkQueue, &gBgRuntimeWorkQueue}) {
786
+ for (auto &work : queue->items) {
787
+ new std::function<void(jsi::Runtime &)>(std::move(work));
788
+ }
789
+ queue->items.clear();
790
+ queue->drainScheduled = false;
791
+ }
655
792
  }
656
793
 
657
794
  LOGI("Native resources cleaned up");
@@ -407,26 +407,36 @@ class BackgroundThreadManager private constructor() {
407
407
  * Routes to main or background runtime's JS queue thread, then calls nativeExecuteWork.
408
408
  */
409
409
  @DoNotStrip
410
- fun scheduleOnJSThread(isMain: Boolean, workId: Long) {
410
+ fun scheduleOnJSThread(isMain: Boolean, workId: Long): Boolean {
411
411
  val context = if (isMain) mainReactContext else bgReactHost?.currentReactContext
412
412
  BTLogger.info("scheduleOnJSThread: isMain=$isMain, workId=$workId, context=${context != null}")
413
413
  if (context == null) {
414
414
  BTLogger.error("scheduleOnJSThread: context is null! isMain=$isMain, mainCtx=${mainReactContext != null}, bgHost=${bgReactHost != null}, bgCtx=${bgReactHost?.currentReactContext != null}")
415
+ return false
415
416
  }
416
- context?.runOnJSQueueThread {
417
- // Re-read ptr inside the block — if a reload happened between
418
- // scheduling and execution, the old ptr may be stale.
419
- val ptr = if (isMain) mainRuntimePtr else bgRuntimePtr
420
- BTLogger.info("scheduleOnJSThread runOnJSQueueThread: isMain=$isMain, workId=$workId, ptr=$ptr")
421
- if (ptr != 0L) {
422
- try {
423
- nativeExecuteWork(ptr, workId)
424
- } catch (e: Exception) {
425
- BTLogger.error("Error executing work on JS thread: ${e.message}")
417
+ return try {
418
+ val posted = context.runOnJSQueueThread {
419
+ // Re-read ptr inside the block if a reload happened between
420
+ // scheduling and execution, the old ptr may be stale.
421
+ val ptr = if (isMain) mainRuntimePtr else bgRuntimePtr
422
+ BTLogger.info("scheduleOnJSThread runOnJSQueueThread: isMain=$isMain, workId=$workId, ptr=$ptr")
423
+ if (ptr != 0L) {
424
+ try {
425
+ nativeExecuteWork(ptr, workId)
426
+ } catch (e: Exception) {
427
+ BTLogger.error("Error executing work on JS thread: ${e.message}")
428
+ }
429
+ } else {
430
+ BTLogger.error("scheduleOnJSThread: ptr is 0! isMain=$isMain")
426
431
  }
427
- } else {
428
- BTLogger.error("scheduleOnJSThread: ptr is 0! isMain=$isMain")
429
432
  }
433
+ if (!posted) {
434
+ BTLogger.error("scheduleOnJSThread: runOnJSQueueThread rejected workId=$workId isMain=$isMain")
435
+ }
436
+ posted
437
+ } catch (e: Exception) {
438
+ BTLogger.error("scheduleOnJSThread: failed to post workId=$workId isMain=$isMain error=${e.message}")
439
+ false
430
440
  }
431
441
  }
432
442
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/react-native-background-thread",
3
- "version": "3.0.60",
3
+ "version": "3.0.62",
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
@@ -4,7 +4,7 @@ export interface ISharedRPC {
4
4
  // the payload value, so there is no read-back. `read`/`has`/`pendingCount`
5
5
  // (the old slot-map introspection) are gone.
6
6
  onWrite(
7
- callback: (callId: string, value: string | number | boolean) => void,
7
+ callback: (callId: string, value: string | number | boolean) => void
8
8
  ): void;
9
9
  // The calling runtime declares which SharedStore key holds its readiness
10
10
  // payload, so the native invalidate path clears exactly that key on teardown