@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
|
-
|
|
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
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
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
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
|