@onekeyfe/react-native-background-thread 3.0.90 → 3.0.92
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/android/src/main/cpp/cpp-adapter.cpp +215 -31
- package/android/src/main/java/com/backgroundthread/BackgroundThreadManager.kt +701 -106
- package/ios/BackgroundRunnerReactNativeDelegate.h +7 -1
- package/ios/BackgroundRunnerReactNativeDelegate.mm +213 -46
- package/ios/BackgroundThreadManager.h +4 -1
- package/ios/BackgroundThreadManager.mm +159 -14
- package/package.json +1 -1
|
@@ -70,6 +70,10 @@ static void invokeOptionalGlobalFunction(jsi::Runtime &runtime, const char *name
|
|
|
70
70
|
static std::mutex gWorkMutex;
|
|
71
71
|
static std::unordered_map<int64_t, std::function<void(jsi::Runtime &)>> gPendingWork;
|
|
72
72
|
static int64_t gNextWorkId = 0;
|
|
73
|
+
// Protected by gWorkMutex. Every background executor captures the generation
|
|
74
|
+
// that installed it, so work from an outgoing Hermes runtime can be discarded
|
|
75
|
+
// without ever touching its replacement runtime.
|
|
76
|
+
static int64_t gBgRuntimeGeneration = 0;
|
|
73
77
|
|
|
74
78
|
using JavaObjectRef = std::shared_ptr<_jobject>;
|
|
75
79
|
|
|
@@ -108,7 +112,11 @@ static void leakAndClearRuntimeQueue(RuntimeWorkQueue &queue) {
|
|
|
108
112
|
queue.scheduledDrainWorkId = -1;
|
|
109
113
|
}
|
|
110
114
|
|
|
111
|
-
static bool callScheduleOnJSThread(
|
|
115
|
+
static bool callScheduleOnJSThread(
|
|
116
|
+
const JavaObjectRef &ref,
|
|
117
|
+
bool isMain,
|
|
118
|
+
int64_t workId,
|
|
119
|
+
int64_t runtimeGeneration) {
|
|
112
120
|
JNIEnv *env = getJNIEnv();
|
|
113
121
|
if (!env || !ref) {
|
|
114
122
|
LOGE("executor: env=%p, ref=%p — aborting", env, ref.get());
|
|
@@ -125,7 +133,7 @@ static bool callScheduleOnJSThread(const JavaObjectRef &ref, bool isMain, int64_
|
|
|
125
133
|
return false;
|
|
126
134
|
}
|
|
127
135
|
|
|
128
|
-
jmethodID mid = env->GetMethodID(cls, "scheduleOnJSThread", "(
|
|
136
|
+
jmethodID mid = env->GetMethodID(cls, "scheduleOnJSThread", "(ZJJ)Z");
|
|
129
137
|
if (!mid) {
|
|
130
138
|
LOGE("executor: scheduleOnJSThread method not found!");
|
|
131
139
|
if (env->ExceptionCheck()) {
|
|
@@ -136,12 +144,14 @@ static bool callScheduleOnJSThread(const JavaObjectRef &ref, bool isMain, int64_
|
|
|
136
144
|
return false;
|
|
137
145
|
}
|
|
138
146
|
|
|
139
|
-
LOGI("executor: calling scheduleOnJSThread(isMain=%d, workId=%ld)",
|
|
147
|
+
LOGI("executor: calling scheduleOnJSThread(isMain=%d, workId=%ld, generation=%ld)",
|
|
148
|
+
isMain, (long)workId, (long)runtimeGeneration);
|
|
140
149
|
jboolean scheduled = env->CallBooleanMethod(
|
|
141
150
|
ref.get(),
|
|
142
151
|
mid,
|
|
143
152
|
static_cast<jboolean>(isMain),
|
|
144
|
-
static_cast<jlong>(workId)
|
|
153
|
+
static_cast<jlong>(workId),
|
|
154
|
+
static_cast<jlong>(runtimeGeneration));
|
|
145
155
|
if (env->ExceptionCheck()) {
|
|
146
156
|
LOGE("executor: JNI exception after scheduleOnJSThread");
|
|
147
157
|
env->ExceptionDescribe();
|
|
@@ -155,15 +165,25 @@ static bool callScheduleOnJSThread(const JavaObjectRef &ref, bool isMain, int64_
|
|
|
155
165
|
|
|
156
166
|
static void drainPendingBgEvals(const std::string &reason);
|
|
157
167
|
|
|
158
|
-
static void scheduleRuntimeDrain(
|
|
168
|
+
static void scheduleRuntimeDrain(
|
|
169
|
+
const JavaObjectRef &ref,
|
|
170
|
+
bool isMain,
|
|
171
|
+
int64_t runtimeGeneration);
|
|
159
172
|
|
|
160
|
-
static void drainRuntimeWorkQueue(
|
|
173
|
+
static void drainRuntimeWorkQueue(
|
|
174
|
+
jsi::Runtime &rt,
|
|
175
|
+
JavaObjectRef ref,
|
|
176
|
+
bool isMain,
|
|
177
|
+
int64_t runtimeGeneration) {
|
|
161
178
|
size_t drained = 0;
|
|
162
179
|
|
|
163
180
|
while (drained < kRuntimeDrainBatchSize) {
|
|
164
181
|
std::function<void(jsi::Runtime &)> work;
|
|
165
182
|
{
|
|
166
183
|
std::lock_guard<std::mutex> lock(gWorkMutex);
|
|
184
|
+
if (!isMain && runtimeGeneration != gBgRuntimeGeneration) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
167
187
|
auto &queue = getRuntimeWorkQueue(isMain);
|
|
168
188
|
if (queue.items.empty()) {
|
|
169
189
|
break;
|
|
@@ -188,6 +208,9 @@ static void drainRuntimeWorkQueue(jsi::Runtime &rt, JavaObjectRef ref, bool isMa
|
|
|
188
208
|
size_t remaining = 0;
|
|
189
209
|
{
|
|
190
210
|
std::lock_guard<std::mutex> lock(gWorkMutex);
|
|
211
|
+
if (!isMain && runtimeGeneration != gBgRuntimeGeneration) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
191
214
|
auto &queue = getRuntimeWorkQueue(isMain);
|
|
192
215
|
remaining = queue.items.size();
|
|
193
216
|
if (remaining == 0) {
|
|
@@ -206,15 +229,21 @@ static void drainRuntimeWorkQueue(jsi::Runtime &rt, JavaObjectRef ref, bool isMa
|
|
|
206
229
|
}
|
|
207
230
|
|
|
208
231
|
if (shouldReschedule) {
|
|
209
|
-
scheduleRuntimeDrain(ref, isMain);
|
|
232
|
+
scheduleRuntimeDrain(ref, isMain, runtimeGeneration);
|
|
210
233
|
}
|
|
211
234
|
}
|
|
212
235
|
|
|
213
|
-
static void scheduleRuntimeDrain(
|
|
236
|
+
static void scheduleRuntimeDrain(
|
|
237
|
+
const JavaObjectRef &ref,
|
|
238
|
+
bool isMain,
|
|
239
|
+
int64_t runtimeGeneration) {
|
|
214
240
|
int64_t workId;
|
|
215
241
|
size_t queued = 0;
|
|
216
242
|
{
|
|
217
243
|
std::lock_guard<std::mutex> lock(gWorkMutex);
|
|
244
|
+
if (!isMain && runtimeGeneration != gBgRuntimeGeneration) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
218
247
|
auto &queue = getRuntimeWorkQueue(isMain);
|
|
219
248
|
// Stale-id guard: drainRuntimeWorkQueue observes remaining>0, drops the
|
|
220
249
|
// lock, then calls us — but a concurrent nativeInvalidateSharedRpc can
|
|
@@ -233,17 +262,21 @@ static void scheduleRuntimeDrain(const JavaObjectRef &ref, bool isMain) {
|
|
|
233
262
|
// Track the outstanding drain's workId so a teardown path can erase its
|
|
234
263
|
// orphaned gPendingWork entry if the post is dropped during reload.
|
|
235
264
|
queue.scheduledDrainWorkId = workId;
|
|
236
|
-
gPendingWork[workId] = [ref, isMain](jsi::Runtime &rt) {
|
|
237
|
-
drainRuntimeWorkQueue(rt, ref, isMain);
|
|
265
|
+
gPendingWork[workId] = [ref, isMain, runtimeGeneration](jsi::Runtime &rt) {
|
|
266
|
+
drainRuntimeWorkQueue(rt, ref, isMain, runtimeGeneration);
|
|
238
267
|
};
|
|
239
268
|
}
|
|
240
269
|
|
|
241
|
-
bool scheduled = callScheduleOnJSThread(ref, isMain, workId);
|
|
270
|
+
bool scheduled = callScheduleOnJSThread(ref, isMain, workId, runtimeGeneration);
|
|
242
271
|
if (!scheduled) {
|
|
272
|
+
bool shouldDrainBgEvals = false;
|
|
243
273
|
{
|
|
244
274
|
std::lock_guard<std::mutex> lock(gWorkMutex);
|
|
245
275
|
gPendingWork.erase(workId);
|
|
246
|
-
|
|
276
|
+
if (isMain || runtimeGeneration == gBgRuntimeGeneration) {
|
|
277
|
+
leakAndClearRuntimeQueue(getRuntimeWorkQueue(isMain));
|
|
278
|
+
shouldDrainBgEvals = !isMain;
|
|
279
|
+
}
|
|
247
280
|
LOGE("executor: failed to schedule runtime drain isMain=%d, workId=%ld, queued=%zu",
|
|
248
281
|
isMain, (long)workId, queued);
|
|
249
282
|
}
|
|
@@ -254,17 +287,25 @@ static void scheduleRuntimeDrain(const JavaObjectRef &ref, bool isMain) {
|
|
|
254
287
|
// schedule hiccup must never falsely reject healthy bg evals. Called
|
|
255
288
|
// outside gWorkMutex: drainPendingBgEvals takes gBgEvalMutex and does a
|
|
256
289
|
// Java upcall, so it must not run under a native lock.
|
|
257
|
-
if (
|
|
290
|
+
if (shouldDrainBgEvals) {
|
|
258
291
|
drainPendingBgEvals("Background JS thread unreachable when scheduling segment eval");
|
|
259
292
|
}
|
|
260
293
|
}
|
|
261
294
|
}
|
|
262
295
|
|
|
263
|
-
static void enqueueRuntimeWork(
|
|
296
|
+
static void enqueueRuntimeWork(
|
|
297
|
+
JavaObjectRef ref,
|
|
298
|
+
bool isMain,
|
|
299
|
+
int64_t runtimeGeneration,
|
|
300
|
+
std::function<void(jsi::Runtime &)> work) {
|
|
264
301
|
bool shouldSchedule = false;
|
|
265
302
|
size_t queued = 0;
|
|
266
303
|
{
|
|
267
304
|
std::lock_guard<std::mutex> lock(gWorkMutex);
|
|
305
|
+
if (!isMain && runtimeGeneration != gBgRuntimeGeneration) {
|
|
306
|
+
new std::function<void(jsi::Runtime &)>(std::move(work));
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
268
309
|
auto &queue = getRuntimeWorkQueue(isMain);
|
|
269
310
|
queue.items.push_back(std::move(work));
|
|
270
311
|
queued = queue.items.size();
|
|
@@ -279,26 +320,39 @@ static void enqueueRuntimeWork(JavaObjectRef ref, bool isMain, std::function<voi
|
|
|
279
320
|
}
|
|
280
321
|
|
|
281
322
|
if (shouldSchedule) {
|
|
282
|
-
scheduleRuntimeDrain(ref, isMain);
|
|
323
|
+
scheduleRuntimeDrain(ref, isMain, runtimeGeneration);
|
|
283
324
|
}
|
|
284
325
|
}
|
|
285
326
|
|
|
286
327
|
// Called from Kotlin after runOnJSQueueThread dispatches to the correct thread.
|
|
287
328
|
extern "C" JNIEXPORT void JNICALL
|
|
288
329
|
Java_com_backgroundthread_BackgroundThreadManager_nativeExecuteWork(
|
|
289
|
-
JNIEnv *env,
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
330
|
+
JNIEnv *env,
|
|
331
|
+
jobject thiz,
|
|
332
|
+
jlong runtimePtr,
|
|
333
|
+
jlong workId,
|
|
334
|
+
jboolean isMain,
|
|
335
|
+
jlong runtimeGeneration) {
|
|
336
|
+
LOGI("nativeExecuteWork: runtimePtr=%ld, workId=%ld, generation=%ld",
|
|
337
|
+
(long)runtimePtr, (long)workId, (long)runtimeGeneration);
|
|
293
338
|
|
|
294
339
|
std::function<void(jsi::Runtime &)> work;
|
|
295
340
|
{
|
|
296
341
|
std::lock_guard<std::mutex> lock(gWorkMutex);
|
|
297
342
|
auto it = gPendingWork.find(workId);
|
|
298
343
|
if (it == gPendingWork.end()) return;
|
|
344
|
+
if (!isMain && runtimeGeneration != gBgRuntimeGeneration) {
|
|
345
|
+
new std::function<void(jsi::Runtime &)>(std::move(it->second));
|
|
346
|
+
gPendingWork.erase(it);
|
|
347
|
+
LOGI("nativeExecuteWork: dropped stale background workId=%ld generation=%ld active=%ld",
|
|
348
|
+
(long)workId, (long)runtimeGeneration, (long)gBgRuntimeGeneration);
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
299
351
|
work = std::move(it->second);
|
|
300
352
|
gPendingWork.erase(it);
|
|
301
353
|
}
|
|
354
|
+
auto *rt = reinterpret_cast<jsi::Runtime *>(runtimePtr);
|
|
355
|
+
if (!rt) return;
|
|
302
356
|
try {
|
|
303
357
|
work(*rt);
|
|
304
358
|
} catch (const jsi::JSError &e) {
|
|
@@ -989,18 +1043,34 @@ Java_com_backgroundthread_BackgroundThreadManager_nativeEvaluateSegmentInBackgro
|
|
|
989
1043
|
// DOES run stale work (it can't — we erased it) would be a harmless no-op.
|
|
990
1044
|
extern "C" JNIEXPORT void JNICALL
|
|
991
1045
|
Java_com_backgroundthread_BackgroundThreadManager_nativeDropScheduledWork(
|
|
992
|
-
JNIEnv * /* env */,
|
|
1046
|
+
JNIEnv * /* env */,
|
|
1047
|
+
jobject /* thiz */,
|
|
1048
|
+
jboolean isMain,
|
|
1049
|
+
jlong workId,
|
|
1050
|
+
jlong runtimeGeneration) {
|
|
1051
|
+
bool shouldDrainBgEvals = false;
|
|
993
1052
|
{
|
|
994
1053
|
std::lock_guard<std::mutex> lock(gWorkMutex);
|
|
995
|
-
gPendingWork.
|
|
1054
|
+
auto pending = gPendingWork.find(static_cast<int64_t>(workId));
|
|
1055
|
+
if (!isMain && runtimeGeneration != gBgRuntimeGeneration) {
|
|
1056
|
+
if (pending != gPendingWork.end()) {
|
|
1057
|
+
new std::function<void(jsi::Runtime &)>(std::move(pending->second));
|
|
1058
|
+
gPendingWork.erase(pending);
|
|
1059
|
+
}
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
if (pending != gPendingWork.end()) {
|
|
1063
|
+
gPendingWork.erase(pending);
|
|
1064
|
+
}
|
|
996
1065
|
// TRANSIENT ptr==0 (reload in flight): do NOT abandon queue.items — the
|
|
997
1066
|
// recovered runtime still needs them (main has no JS retry net). Only
|
|
998
1067
|
// disarm the drain latch so the next enqueue re-arms a fresh drain.
|
|
999
1068
|
auto &queue = getRuntimeWorkQueue(static_cast<bool>(isMain));
|
|
1000
1069
|
queue.drainScheduled = false;
|
|
1001
1070
|
queue.scheduledDrainWorkId = -1;
|
|
1071
|
+
shouldDrainBgEvals = !isMain;
|
|
1002
1072
|
}
|
|
1003
|
-
if (
|
|
1073
|
+
if (shouldDrainBgEvals) {
|
|
1004
1074
|
drainPendingBgEvals("Background runtime unreachable when scheduling segment eval");
|
|
1005
1075
|
}
|
|
1006
1076
|
}
|
|
@@ -1009,11 +1079,32 @@ Java_com_backgroundthread_BackgroundThreadManager_nativeDropScheduledWork(
|
|
|
1009
1079
|
// Install SharedStore and SharedRPC into a runtime.
|
|
1010
1080
|
extern "C" JNIEXPORT void JNICALL
|
|
1011
1081
|
Java_com_backgroundthread_BackgroundThreadManager_nativeInstallSharedBridge(
|
|
1012
|
-
JNIEnv *env,
|
|
1082
|
+
JNIEnv *env,
|
|
1083
|
+
jobject thiz,
|
|
1084
|
+
jlong runtimePtr,
|
|
1085
|
+
jboolean isMain,
|
|
1086
|
+
jlong runtimeGeneration) {
|
|
1013
1087
|
|
|
1014
1088
|
auto *rt = reinterpret_cast<jsi::Runtime *>(runtimePtr);
|
|
1015
1089
|
if (!rt) return;
|
|
1016
1090
|
|
|
1091
|
+
if (!isMain) {
|
|
1092
|
+
std::lock_guard<std::mutex> lock(gWorkMutex);
|
|
1093
|
+
if (runtimeGeneration < gBgRuntimeGeneration) {
|
|
1094
|
+
LOGI("nativeInstallSharedBridge: ignored stale background generation=%ld active=%ld",
|
|
1095
|
+
(long)runtimeGeneration, (long)gBgRuntimeGeneration);
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
if (runtimeGeneration != gBgRuntimeGeneration) {
|
|
1099
|
+
auto &queue = gBgRuntimeWorkQueue;
|
|
1100
|
+
if (queue.scheduledDrainWorkId >= 0) {
|
|
1101
|
+
gPendingWork.erase(queue.scheduledDrainWorkId);
|
|
1102
|
+
}
|
|
1103
|
+
leakAndClearRuntimeQueue(queue);
|
|
1104
|
+
gBgRuntimeGeneration = runtimeGeneration;
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1017
1108
|
SharedStore::install(*rt);
|
|
1018
1109
|
|
|
1019
1110
|
// Create executor that schedules work on this runtime's JS thread via Kotlin.
|
|
@@ -1027,13 +1118,19 @@ Java_com_backgroundthread_BackgroundThreadManager_nativeInstallSharedBridge(
|
|
|
1027
1118
|
});
|
|
1028
1119
|
bool capturedIsMain = static_cast<bool>(isMain);
|
|
1029
1120
|
|
|
1030
|
-
|
|
1121
|
+
int64_t capturedRuntimeGeneration = static_cast<int64_t>(runtimeGeneration);
|
|
1122
|
+
RPCRuntimeExecutor executor = [ref, capturedIsMain, capturedRuntimeGeneration](
|
|
1123
|
+
std::function<void(jsi::Runtime &)> work) {
|
|
1031
1124
|
// Coalesce per-runtime work into a single batched drain (see
|
|
1032
1125
|
// enqueueRuntimeWork) instead of one Kotlin scheduleOnJSThread hop per
|
|
1033
1126
|
// item. The bg-eval failure-drain that previously lived inline here now
|
|
1034
1127
|
// runs in scheduleRuntimeDrain's schedule-failure path — under the
|
|
1035
1128
|
// coalesced model that is the single place a schedule can fail.
|
|
1036
|
-
enqueueRuntimeWork(
|
|
1129
|
+
enqueueRuntimeWork(
|
|
1130
|
+
ref,
|
|
1131
|
+
capturedIsMain,
|
|
1132
|
+
capturedRuntimeGeneration,
|
|
1133
|
+
std::move(work));
|
|
1037
1134
|
};
|
|
1038
1135
|
|
|
1039
1136
|
std::string runtimeId = isMain ? "main" : "background";
|
|
@@ -1082,13 +1179,16 @@ Java_com_backgroundthread_BackgroundThreadManager_nativeInstallSharedBridge(
|
|
|
1082
1179
|
{
|
|
1083
1180
|
std::lock_guard<std::mutex> lock(gWorkMutex);
|
|
1084
1181
|
auto &queue = getRuntimeWorkQueue(capturedIsMain);
|
|
1085
|
-
if (
|
|
1182
|
+
if (
|
|
1183
|
+
!queue.items.empty() &&
|
|
1184
|
+
(capturedIsMain || capturedRuntimeGeneration == gBgRuntimeGeneration)
|
|
1185
|
+
) {
|
|
1086
1186
|
queue.drainScheduled = true;
|
|
1087
1187
|
shouldRecoverDrain = true;
|
|
1088
1188
|
}
|
|
1089
1189
|
}
|
|
1090
1190
|
if (shouldRecoverDrain) {
|
|
1091
|
-
scheduleRuntimeDrain(ref, capturedIsMain);
|
|
1191
|
+
scheduleRuntimeDrain(ref, capturedIsMain, capturedRuntimeGeneration);
|
|
1092
1192
|
}
|
|
1093
1193
|
}
|
|
1094
1194
|
|
|
@@ -1164,7 +1264,10 @@ Java_com_backgroundthread_BackgroundThreadManager_nativeSetupErrorHandler(
|
|
|
1164
1264
|
// instead of dispatching onto a dying runtime (parity with iOS).
|
|
1165
1265
|
extern "C" JNIEXPORT jboolean JNICALL
|
|
1166
1266
|
Java_com_backgroundthread_BackgroundThreadManager_nativeInvalidateSharedRpc(
|
|
1167
|
-
JNIEnv *env,
|
|
1267
|
+
JNIEnv *env,
|
|
1268
|
+
jobject thiz,
|
|
1269
|
+
jstring runtimeId,
|
|
1270
|
+
jlong runtimeGeneration) {
|
|
1168
1271
|
|
|
1169
1272
|
const char *idChars = env->GetStringUTFChars(runtimeId, nullptr);
|
|
1170
1273
|
if (!idChars) {
|
|
@@ -1173,8 +1276,6 @@ Java_com_backgroundthread_BackgroundThreadManager_nativeInvalidateSharedRpc(
|
|
|
1173
1276
|
std::string id(idChars);
|
|
1174
1277
|
env->ReleaseStringUTFChars(runtimeId, idChars);
|
|
1175
1278
|
|
|
1176
|
-
bool found = SharedRPC::invalidate(id);
|
|
1177
|
-
|
|
1178
1279
|
// The runtime named by `id` is being torn down (restart → host.reload).
|
|
1179
1280
|
// Its coalesced work queue must be fully quiesced: if a drain was posted
|
|
1180
1281
|
// (drainScheduled==true) but is dropped during reload, the latch would
|
|
@@ -1188,13 +1289,96 @@ Java_com_backgroundthread_BackgroundThreadManager_nativeInvalidateSharedRpc(
|
|
|
1188
1289
|
std::lock_guard<std::mutex> lock(gWorkMutex);
|
|
1189
1290
|
bool isMain = (id == "main");
|
|
1190
1291
|
auto &queue = getRuntimeWorkQueue(isMain);
|
|
1292
|
+
if (!isMain) {
|
|
1293
|
+
gBgRuntimeGeneration = static_cast<int64_t>(runtimeGeneration);
|
|
1294
|
+
}
|
|
1191
1295
|
if (queue.scheduledDrainWorkId >= 0) {
|
|
1192
1296
|
gPendingWork.erase(queue.scheduledDrainWorkId);
|
|
1193
1297
|
}
|
|
1194
1298
|
leakAndClearRuntimeQueue(queue);
|
|
1195
1299
|
}
|
|
1196
1300
|
|
|
1197
|
-
|
|
1301
|
+
bool found = SharedRPC::invalidate(id);
|
|
1302
|
+
|
|
1303
|
+
if (id == "background") {
|
|
1304
|
+
// Stop old timers from producing more work after the generation was
|
|
1305
|
+
// invalidated. Callback wrappers are intentionally leaked because
|
|
1306
|
+
// destroying a jsi::Function away from its outgoing JS thread is unsafe.
|
|
1307
|
+
std::lock_guard<std::mutex> lock(gTimerMutex);
|
|
1308
|
+
for (auto &entry : gTimers) {
|
|
1309
|
+
if (entry.second.callback) {
|
|
1310
|
+
new std::shared_ptr<jsi::Function>(std::move(entry.second.callback));
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
gTimers.clear();
|
|
1314
|
+
gBgTimerExecutor = nullptr;
|
|
1315
|
+
gTimerCv.notify_all();
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
if (id == "background") {
|
|
1319
|
+
drainPendingBgEvals("Background runtime invalidated before segment eval ran");
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
LOGI("nativeInvalidateSharedRpc: id=%s generation=%ld found=%d",
|
|
1323
|
+
id.c_str(), (long)runtimeGeneration, found ? 1 : 0);
|
|
1324
|
+
return found ? JNI_TRUE : JNI_FALSE;
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
// Invalidate the outgoing background runtime while executing on that runtime's
|
|
1328
|
+
// JS thread. This is the normal background-HMR path: it can reclaim timer and
|
|
1329
|
+
// queued-work callbacks instead of leaking their jsi::Function wrappers to
|
|
1330
|
+
// avoid an unsafe off-thread destructor.
|
|
1331
|
+
extern "C" JNIEXPORT jboolean JNICALL
|
|
1332
|
+
Java_com_backgroundthread_BackgroundThreadManager_nativeInvalidateBackgroundRuntimeOnJSThread(
|
|
1333
|
+
JNIEnv *env,
|
|
1334
|
+
jobject thiz,
|
|
1335
|
+
jlong runtimeGeneration) {
|
|
1336
|
+
|
|
1337
|
+
bool found = SharedRPC::invalidate("background");
|
|
1338
|
+
|
|
1339
|
+
// Prevent the timer worker from capturing or enqueueing another callback
|
|
1340
|
+
// while the outgoing JS thread drains the callback containers below.
|
|
1341
|
+
gTimerWorkerStop.store(true);
|
|
1342
|
+
gTimerCv.notify_all();
|
|
1343
|
+
if (gTimerWorkerThread.joinable()) {
|
|
1344
|
+
gTimerWorkerThread.join();
|
|
1345
|
+
}
|
|
1346
|
+
gTimerWorkerStarted.store(false);
|
|
1347
|
+
|
|
1348
|
+
std::vector<std::shared_ptr<jsi::Function>> timerCallbacks;
|
|
1349
|
+
{
|
|
1350
|
+
std::lock_guard<std::mutex> lock(gTimerMutex);
|
|
1351
|
+
timerCallbacks.reserve(gTimers.size());
|
|
1352
|
+
for (auto &entry : gTimers) {
|
|
1353
|
+
if (entry.second.callback) {
|
|
1354
|
+
timerCallbacks.push_back(std::move(entry.second.callback));
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
gTimers.clear();
|
|
1358
|
+
gBgTimerExecutor = nullptr;
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
std::deque<std::function<void(jsi::Runtime &)>> queuedWork;
|
|
1362
|
+
{
|
|
1363
|
+
std::lock_guard<std::mutex> lock(gWorkMutex);
|
|
1364
|
+
gBgRuntimeGeneration = static_cast<int64_t>(runtimeGeneration);
|
|
1365
|
+
auto &queue = gBgRuntimeWorkQueue;
|
|
1366
|
+
if (queue.scheduledDrainWorkId >= 0) {
|
|
1367
|
+
gPendingWork.erase(queue.scheduledDrainWorkId);
|
|
1368
|
+
}
|
|
1369
|
+
queuedWork.swap(queue.items);
|
|
1370
|
+
queue.drainScheduled = false;
|
|
1371
|
+
queue.scheduledDrainWorkId = -1;
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
drainPendingBgEvals(
|
|
1375
|
+
"Background runtime invalidated before segment eval ran");
|
|
1376
|
+
|
|
1377
|
+
// timerCallbacks and queuedWork intentionally fall out of scope here, on
|
|
1378
|
+
// the outgoing runtime's JS thread, before ReactHost.destroy() begins.
|
|
1379
|
+
LOGI("nativeInvalidateBackgroundRuntimeOnJSThread: generation=%ld found=%d timers=%zu work=%zu",
|
|
1380
|
+
(long)runtimeGeneration, found ? 1 : 0,
|
|
1381
|
+
timerCallbacks.size(), queuedWork.size());
|
|
1198
1382
|
return found ? JNI_TRUE : JNI_FALSE;
|
|
1199
1383
|
}
|
|
1200
1384
|
|