From 9ba8299071e7eee1be8fd6a6607c1916e43d7acd Mon Sep 17 00:00:00 2001 From: Leon Date: Fri, 12 Jun 2026 08:18:56 +0800 Subject: [PATCH 1/5] fix: coalesce background thread runtime work --- .../android/src/main/cpp/cpp-adapter.cpp | 289 +++++++++++++----- .../BackgroundThreadManager.kt | 88 +++--- .../package.json | 2 +- .../src/SharedRPC.ts | 2 +- 4 files changed, 270 insertions(+), 111 deletions(-) diff --git a/native-modules/react-native-background-thread/android/src/main/cpp/cpp-adapter.cpp b/native-modules/react-native-background-thread/android/src/main/cpp/cpp-adapter.cpp index 714fb6df..6a647494 100644 --- a/native-modules/react-native-background-thread/android/src/main/cpp/cpp-adapter.cpp +++ b/native-modules/react-native-background-thread/android/src/main/cpp/cpp-adapter.cpp @@ -71,6 +71,193 @@ static std::mutex gWorkMutex; static std::unordered_map> gPendingWork; static int64_t gNextWorkId = 0; +using JavaObjectRef = std::shared_ptr<_jobject>; + +static constexpr size_t kRuntimeDrainBatchSize = 64; +static constexpr size_t kRuntimeQueueWarnThreshold = 128; +static constexpr size_t kRuntimeQueueWarnInterval = 128; + +struct RuntimeWorkQueue { + std::deque> items; + bool drainScheduled = false; +}; + +static RuntimeWorkQueue gMainRuntimeWorkQueue; +static RuntimeWorkQueue gBgRuntimeWorkQueue; + +static RuntimeWorkQueue &getRuntimeWorkQueue(bool isMain) { + return isMain ? gMainRuntimeWorkQueue : gBgRuntimeWorkQueue; +} + +// Caller MUST hold gWorkMutex. Intentionally leak (abandon) each queued functor +// — its ~jsi::Function must not run off the JS thread / on a dead runtime — then +// clear the queue and disarm the drain latch so a recovered runtime re-arms a +// fresh drain on the next enqueue instead of stranding work behind a stale +// drainScheduled==true. +static void leakAndClearRuntimeQueue(RuntimeWorkQueue &queue) { + for (auto &work : queue.items) { + new std::function(std::move(work)); + } + queue.items.clear(); + queue.drainScheduled = false; +} + +static bool callScheduleOnJSThread(const JavaObjectRef &ref, bool isMain, int64_t workId) { + JNIEnv *env = getJNIEnv(); + if (!env || !ref) { + LOGE("executor: env=%p, ref=%p — aborting", env, ref.get()); + return false; + } + + jclass cls = env->GetObjectClass(ref.get()); + if (!cls) { + LOGE("executor: GetObjectClass failed"); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + } + return false; + } + + jmethodID mid = env->GetMethodID(cls, "scheduleOnJSThread", "(ZJ)Z"); + if (!mid) { + LOGE("executor: scheduleOnJSThread method not found!"); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + } + env->DeleteLocalRef(cls); + return false; + } + + LOGI("executor: calling scheduleOnJSThread(isMain=%d, workId=%ld)", isMain, (long)workId); + jboolean scheduled = env->CallBooleanMethod( + ref.get(), + mid, + static_cast(isMain), + static_cast(workId)); + if (env->ExceptionCheck()) { + LOGE("executor: JNI exception after scheduleOnJSThread"); + env->ExceptionDescribe(); + env->ExceptionClear(); + env->DeleteLocalRef(cls); + return false; + } + env->DeleteLocalRef(cls); + return scheduled == JNI_TRUE; +} + +static void drainPendingBgEvals(const std::string &reason); + +static void scheduleRuntimeDrain(const JavaObjectRef &ref, bool isMain); + +static void drainRuntimeWorkQueue(jsi::Runtime &rt, JavaObjectRef ref, bool isMain) { + size_t drained = 0; + + while (drained < kRuntimeDrainBatchSize) { + std::function work; + { + std::lock_guard lock(gWorkMutex); + auto &queue = getRuntimeWorkQueue(isMain); + if (queue.items.empty()) { + break; + } + work = std::move(queue.items.front()); + queue.items.pop_front(); + } + + try { + work(rt); + } catch (const jsi::JSError &e) { + LOGE("JSError in runtime drain work: %s", e.getMessage().c_str()); + } catch (const std::exception &e) { + LOGE("Error in runtime drain work: %s", e.what()); + } catch (...) { + LOGE("Unknown error in runtime drain work"); + } + drained += 1; + } + + bool shouldReschedule = false; + size_t remaining = 0; + { + std::lock_guard lock(gWorkMutex); + auto &queue = getRuntimeWorkQueue(isMain); + remaining = queue.items.size(); + if (remaining == 0) { + queue.drainScheduled = false; + } else { + shouldReschedule = true; + } + } + + if (drained > 1 || remaining > 0) { + LOGI("executor: drained runtime queue isMain=%d, drained=%zu, remaining=%zu", + isMain, drained, remaining); + } + + if (shouldReschedule) { + scheduleRuntimeDrain(ref, isMain); + } +} + +static void scheduleRuntimeDrain(const JavaObjectRef &ref, bool isMain) { + int64_t workId; + size_t queued = 0; + { + std::lock_guard lock(gWorkMutex); + workId = gNextWorkId++; + queued = getRuntimeWorkQueue(isMain).items.size(); + gPendingWork[workId] = [ref, isMain](jsi::Runtime &rt) { + drainRuntimeWorkQueue(rt, ref, isMain); + }; + } + + bool scheduled = callScheduleOnJSThread(ref, isMain, workId); + if (!scheduled) { + { + std::lock_guard lock(gWorkMutex); + gPendingWork.erase(workId); + leakAndClearRuntimeQueue(getRuntimeWorkQueue(isMain)); + LOGE("executor: failed to schedule runtime drain isMain=%d, workId=%ld, queued=%zu", + isMain, (long)workId, queued); + } + // The bg JS thread is unreachable, so the queued bg-eval lambdas will + // never run. Settle any in-flight bg eval (retryable NO_RUNTIME) so its + // JNI global ref is released and the JS promise resolves now instead of + // hanging on the Kotlin 30s watchdog. Gated on !isMain — a main-thread + // schedule hiccup must never falsely reject healthy bg evals. Called + // outside gWorkMutex: drainPendingBgEvals takes gBgEvalMutex and does a + // Java upcall, so it must not run under a native lock. + if (!isMain) { + drainPendingBgEvals("Background JS thread unreachable when scheduling segment eval"); + } + } +} + +static void enqueueRuntimeWork(JavaObjectRef ref, bool isMain, std::function work) { + bool shouldSchedule = false; + size_t queued = 0; + { + std::lock_guard lock(gWorkMutex); + auto &queue = getRuntimeWorkQueue(isMain); + queue.items.push_back(std::move(work)); + queued = queue.items.size(); + if (!queue.drainScheduled) { + queue.drainScheduled = true; + shouldSchedule = true; + } + } + + if (queued >= kRuntimeQueueWarnThreshold && queued % kRuntimeQueueWarnInterval == 0) { + LOGE("executor: runtime queue backlog isMain=%d, queued=%zu", isMain, queued); + } + + if (shouldSchedule) { + scheduleRuntimeDrain(ref, isMain); + } +} + // Called from Kotlin after runOnJSQueueThread dispatches to the correct thread. extern "C" JNIEXPORT void JNICALL Java_com_backgroundthread_BackgroundThreadManager_nativeExecuteWork( @@ -743,26 +930,36 @@ Java_com_backgroundthread_BackgroundThreadManager_nativeEvaluateSegmentInBackgro } // nativeDropScheduledWork: clean up after a scheduleOnJSThread drop path where -// CallVoidMethod itself SUCCEEDED but Kotlin then found the bg runtime -// unreachable (context==null / ptr==0) and returned WITHOUT calling -// nativeExecuteWork for this `workId`. Two things must be released: +// CallVoidMethod itself SUCCEEDED but Kotlin then found the runtime unreachable +// (context==null / ptr==0) and returned WITHOUT calling nativeExecuteWork for +// this `workId`. Several things must be released for the given runtime: // 1. gPendingWork[workId] — the stored work lambda (holding the segment // SOURCE BUFFER). nativeExecuteWork is the only other eraser and it will // never run for this id, so without this it leaks until nativeDestroy. -// 2. The in-flight bg eval(s) — settle as retryable NO_RUNTIME so the JNI -// global ref is released and the JS promise resolves now instead of +// 2. The coalesced RuntimeWorkQueue for this runtime — under the coalesced +// model a successful post (scheduled==true) that never reaches the JS +// thread leaves the queue stranded with drainScheduled==true, so a +// recovered runtime would never re-arm a drain. Leak+clear the queued +// functors (their ~jsi::Function must not run on a dead runtime) and reset +// the drain latch so the next enqueue re-arms a fresh drain. Applies to +// BOTH runtimes (isMain selects which queue). +// 3. (bg only) The in-flight bg eval(s) — settle as retryable NO_RUNTIME so +// the JNI global ref is released and the JS promise resolves now instead of // hanging on the 30s watchdog. drain-all is sound: an unreachable bg JS // thread dooms every enqueued bg eval equally. // Exactly-once via the shared `settled` flag, so a recovered runtime that later // DOES run stale work (it can't — we erased it) would be a harmless no-op. extern "C" JNIEXPORT void JNICALL Java_com_backgroundthread_BackgroundThreadManager_nativeDropScheduledWork( - JNIEnv * /* env */, jobject /* thiz */, jlong workId) { + JNIEnv * /* env */, jobject /* thiz */, jboolean isMain, jlong workId) { { std::lock_guard lock(gWorkMutex); gPendingWork.erase(static_cast(workId)); + leakAndClearRuntimeQueue(getRuntimeWorkQueue(static_cast(isMain))); + } + if (!isMain) { + drainPendingBgEvals("Background runtime unreachable when scheduling segment eval"); } - drainPendingBgEvals("Background runtime unreachable when scheduling segment eval"); } // ── nativeInstallSharedBridge ─────────────────────────────────────────── @@ -788,71 +985,12 @@ Java_com_backgroundthread_BackgroundThreadManager_nativeInstallSharedBridge( bool capturedIsMain = static_cast(isMain); RPCRuntimeExecutor executor = [ref, capturedIsMain](std::function work) { - JNIEnv *env = getJNIEnv(); - - // Settle any enqueued-but-now-unrunnable bg eval when this work will NOT - // reach nativeExecuteWork. Bg eval lambdas are only ever dispatched via - // the bg executor, so this is gated on !capturedIsMain — a main-thread - // schedule hiccup must never falsely reject healthy bg evals. This - // mirrors the existing context==null / ptr==0 (nativeDropScheduledWork) - // and nativeDestroy drop paths: drain-all is sound because a failed bg - // schedule means the bg JS thread is unreachable, so every enqueued bg - // eval is equally doomed. NO_RUNTIME is retryable, so JS re-attempts. - auto drainBgEvalsIfBg = [capturedIsMain](const char *reason) { - if (!capturedIsMain) { - drainPendingBgEvals(reason); - } - }; - - if (!env || !ref) { - LOGE("executor: env=%p, ref=%p — aborting", env, ref.get()); - drainBgEvalsIfBg("Background executor env/ref unavailable when scheduling segment eval"); - return; - } - - int64_t workId; - { - std::lock_guard lock(gWorkMutex); - workId = gNextWorkId++; - gPendingWork[workId] = std::move(work); - } - - jclass cls = env->GetObjectClass(ref.get()); - jmethodID mid = env->GetMethodID(cls, "scheduleOnJSThread", "(ZJ)V"); - bool scheduled = false; - if (mid) { - LOGI("executor: calling scheduleOnJSThread(isMain=%d, workId=%ld)", capturedIsMain, (long)workId); - env->CallVoidMethod(ref.get(), mid, static_cast(capturedIsMain), static_cast(workId)); - if (env->ExceptionCheck()) { - LOGE("executor: JNI exception after scheduleOnJSThread"); - env->ExceptionDescribe(); - env->ExceptionClear(); - } else { - scheduled = true; - } - } else { - LOGE("executor: scheduleOnJSThread method not found!"); - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - env->ExceptionClear(); - } - } - env->DeleteLocalRef(cls); - - // Schedule failed (JNI exception or missing method): nativeExecuteWork - // will never run this workId, so erase it now to free the stored work - // (and its captured segment source buffer) instead of leaking it until - // nativeDestroy. Then settle the corresponding bg eval(s) so the JNI - // global ref is released and the JS promise resolves immediately rather - // than hanging on the Kotlin 30s watchdog. Erasing also makes a late - // Kotlin enqueue (if scheduleOnJSThread threw AFTER posting) a no-op. - if (!scheduled) { - { - std::lock_guard lock(gWorkMutex); - gPendingWork.erase(workId); - } - drainBgEvalsIfBg("Background JS thread unreachable when scheduling segment eval"); - } + // Coalesce per-runtime work into a single batched drain (see + // enqueueRuntimeWork) instead of one Kotlin scheduleOnJSThread hop per + // item. The bg-eval failure-drain that previously lived inline here now + // runs in scheduleRuntimeDrain's schedule-failure path — under the + // coalesced model that is the single place a schedule can fail. + enqueueRuntimeWork(ref, capturedIsMain, std::move(work)); }; std::string runtimeId = isMain ? "main" : "background"; @@ -1009,6 +1147,13 @@ Java_com_backgroundthread_BackgroundThreadManager_nativeDestroy( new std::function(std::move(entry.second)); } gPendingWork.clear(); + for (auto *queue : {&gMainRuntimeWorkQueue, &gBgRuntimeWorkQueue}) { + for (auto &work : queue->items) { + new std::function(std::move(work)); + } + queue->items.clear(); + queue->drainScheduled = false; + } } // Drain pending bg-eval callbacks: the bg runtime is gone, so any eval that diff --git a/native-modules/react-native-background-thread/android/src/main/java/com/backgroundthread/BackgroundThreadManager.kt b/native-modules/react-native-background-thread/android/src/main/java/com/backgroundthread/BackgroundThreadManager.kt index fe169ad1..ac7448dd 100644 --- a/native-modules/react-native-background-thread/android/src/main/java/com/backgroundthread/BackgroundThreadManager.kt +++ b/native-modules/react-native-background-thread/android/src/main/java/com/backgroundthread/BackgroundThreadManager.kt @@ -124,15 +124,19 @@ class BackgroundThreadManager private constructor() { ) /** - * Settle every in-flight bg segment eval as a retryable NO_RUNTIME failure - * without tearing the runtime down. Called from [scheduleOnJSThread] when - * the bg runtime is momentarily unreachable (context == null / ptr == 0) so - * any eval enqueued onto the native pending-work map — which will never be - * drained on the JS thread in that state — releases its JNI global ref and - * settles the JS promise immediately, instead of leaking until the next - * teardown or relying on the bg watchdog. Exactly-once on the native side. + * Clean up after a SUCCESSFUL scheduleOnJSThread post that nonetheless can + * never run: called from [scheduleOnJSThread]'s ptr == 0 branch (we are + * already on the stale/torn-down JS thread, so C++ saw scheduled == true and + * will not clean up on its own). For the given runtime ([isMain]) the native + * side erases gPendingWork[workId] (frees the captured segment source + * buffer), leak+clears the coalesced RuntimeWorkQueue, and resets + * drainScheduled so a recovered runtime re-arms a fresh drain on the next + * enqueue. For the bg runtime only, it also settles every in-flight bg + * segment eval as a retryable NO_RUNTIME failure so each JNI global ref is + * released and the JS promise resolves immediately instead of leaking until + * teardown or the bg watchdog. Exactly-once on the native side. */ - private external fun nativeDropScheduledWork(workId: Long) + private external fun nativeDropScheduledWork(isMain: Boolean, workId: Long) /** * Synchronously mark the SharedRPC listener for `runtimeId` as dead @@ -456,42 +460,52 @@ class BackgroundThreadManager private constructor() { * Routes to main or background runtime's JS queue thread, then calls nativeExecuteWork. */ @DoNotStrip - fun scheduleOnJSThread(isMain: Boolean, workId: Long) { + fun scheduleOnJSThread(isMain: Boolean, workId: Long): Boolean { val context = if (isMain) mainReactContext else bgReactHost?.currentReactContext BTLogger.info("scheduleOnJSThread: isMain=$isMain, workId=$workId, context=${context != null}") if (context == null) { BTLogger.error("scheduleOnJSThread: context is null! isMain=$isMain, mainCtx=${mainReactContext != null}, bgHost=${bgReactHost != null}, bgCtx=${bgReactHost?.currentReactContext != null}") - // The just-enqueued native work will never reach the bg JS thread. - // Drop it now: erase gPendingWork[workId] (frees the captured segment - // source buffer) and, if it was a bg segment eval, settle it - // (retryable NO_RUNTIME) so its JNI global ref is released and the JS - // promise resolves instead of leaking until teardown / the bg watchdog. - if (!isMain) { - nativeDropScheduledWork(workId) - } - return + // The just-enqueued native work will never reach the JS thread. + // Return false so the C++ coalesced scheduler + // (callScheduleOnJSThread → scheduleRuntimeDrain's !scheduled branch) + // does the FULL cleanup itself: erase gPendingWork[workId], leak+clear + // the coalesced RuntimeWorkQueue, reset drainScheduled, and (bg only) + // settle any in-flight bg eval as retryable NO_RUNTIME. No + // nativeDropScheduledWork call is needed here. + return false } - context.runOnJSQueueThread { - // Re-read ptr inside the block — if a reload happened between - // scheduling and execution, the old ptr may be stale. - val ptr = if (isMain) mainRuntimePtr else bgRuntimePtr - BTLogger.info("scheduleOnJSThread runOnJSQueueThread: isMain=$isMain, workId=$workId, ptr=$ptr") - if (ptr != 0L) { - try { - nativeExecuteWork(ptr, workId) - } catch (e: Exception) { - BTLogger.error("Error executing work on JS thread: ${e.message}") - } - } else { - BTLogger.error("scheduleOnJSThread: ptr is 0! isMain=$isMain") - // Same as the null-context case: the work won't run on this - // (stale/torn-down) bg runtime. Drop gPendingWork[workId] (frees - // the source buffer) and settle any pending bg eval so it doesn't - // leak its global ref / hang the JS promise. - if (!isMain) { - nativeDropScheduledWork(workId) + return try { + val posted = context.runOnJSQueueThread { + // Re-read ptr inside the block — if a reload happened between + // scheduling and execution, the old ptr may be stale. + val ptr = if (isMain) mainRuntimePtr else bgRuntimePtr + BTLogger.info("scheduleOnJSThread runOnJSQueueThread: isMain=$isMain, workId=$workId, ptr=$ptr") + if (ptr != 0L) { + try { + nativeExecuteWork(ptr, workId) + } catch (e: Exception) { + BTLogger.error("Error executing work on JS thread: ${e.message}") + } + } else { + BTLogger.error("scheduleOnJSThread: ptr is 0! isMain=$isMain") + // We are already on the (stale/torn-down) JS thread after a + // SUCCESSFUL post (C++ saw scheduled==true), so the work won't + // run and C++ won't clean up on its own. Drop it for THIS + // runtime (main or bg): erase gPendingWork[workId] (frees the + // source buffer), leak+clear the coalesced RuntimeWorkQueue, + // and reset drainScheduled so a recovered runtime re-arms a + // fresh drain. drainPendingBgEvals inside the native fn is + // gated to !isMain, so settling bg evals only happens for bg. + nativeDropScheduledWork(isMain, workId) } } + if (!posted) { + BTLogger.error("scheduleOnJSThread: runOnJSQueueThread rejected workId=$workId isMain=$isMain") + } + posted + } catch (e: Exception) { + BTLogger.error("scheduleOnJSThread: failed to post workId=$workId isMain=$isMain error=${e.message}") + false } } diff --git a/native-modules/react-native-background-thread/package.json b/native-modules/react-native-background-thread/package.json index f102ba0b..7832ca03 100644 --- a/native-modules/react-native-background-thread/package.json +++ b/native-modules/react-native-background-thread/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-background-thread", - "version": "3.0.61", + "version": "3.0.62", "description": "react-native-background-thread", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-background-thread/src/SharedRPC.ts b/native-modules/react-native-background-thread/src/SharedRPC.ts index 432e9f47..2988d2b4 100644 --- a/native-modules/react-native-background-thread/src/SharedRPC.ts +++ b/native-modules/react-native-background-thread/src/SharedRPC.ts @@ -4,7 +4,7 @@ export interface ISharedRPC { // the payload value, so there is no read-back. `read`/`has`/`pendingCount` // (the old slot-map introspection) are gone. onWrite( - callback: (callId: string, value: string | number | boolean) => void, + callback: (callId: string, value: string | number | boolean) => void ): void; // The calling runtime declares which SharedStore key holds its readiness // payload, so the native invalidate path clears exactly that key on teardown From 09d8e34788073c51728aa80ff1f58493d5504f98 Mon Sep 17 00:00:00 2001 From: Leon Date: Fri, 12 Jun 2026 08:54:23 +0800 Subject: [PATCH 2/5] chore: bump version to 3.0.63 --- native-modules/native-logger/package.json | 2 +- native-modules/react-native-aes-crypto/package.json | 2 +- native-modules/react-native-app-update/package.json | 2 +- native-modules/react-native-async-storage/package.json | 2 +- native-modules/react-native-background-thread/package.json | 2 +- native-modules/react-native-bundle-crypto/package.json | 2 +- native-modules/react-native-bundle-update/package.json | 2 +- .../react-native-check-biometric-auth-changed/package.json | 2 +- native-modules/react-native-cloud-fs/package.json | 2 +- native-modules/react-native-cloud-kit-module/package.json | 2 +- native-modules/react-native-device-utils/package.json | 2 +- native-modules/react-native-dns-lookup/package.json | 2 +- native-modules/react-native-get-random-values/package.json | 2 +- native-modules/react-native-keychain-module/package.json | 2 +- native-modules/react-native-lite-card/package.json | 2 +- native-modules/react-native-network-info/package.json | 2 +- native-modules/react-native-pbkdf2/package.json | 2 +- native-modules/react-native-perf-memory/package.json | 2 +- native-modules/react-native-perf-stats/package.json | 2 +- native-modules/react-native-ping/package.json | 2 +- native-modules/react-native-range-downloader/package.json | 2 +- native-modules/react-native-splash-screen/package.json | 2 +- native-modules/react-native-split-bundle-loader/package.json | 2 +- native-modules/react-native-tcp-socket/package.json | 2 +- native-modules/react-native-zip-archive/package.json | 2 +- native-views/react-native-auto-size-input/package.json | 2 +- native-views/react-native-chart-webview/package.json | 2 +- native-views/react-native-pager-view/package.json | 2 +- native-views/react-native-perp-depth-bar/package.json | 2 +- native-views/react-native-scroll-guard/package.json | 2 +- native-views/react-native-segment-slider/package.json | 2 +- native-views/react-native-skeleton/package.json | 2 +- native-views/react-native-tab-view/package.json | 2 +- 33 files changed, 33 insertions(+), 33 deletions(-) diff --git a/native-modules/native-logger/package.json b/native-modules/native-logger/package.json index 829a17b1..8e279866 100644 --- a/native-modules/native-logger/package.json +++ b/native-modules/native-logger/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-native-logger", - "version": "3.0.61", + "version": "3.0.63", "description": "react-native-native-logger", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-aes-crypto/package.json b/native-modules/react-native-aes-crypto/package.json index 3ecadb73..4931854e 100644 --- a/native-modules/react-native-aes-crypto/package.json +++ b/native-modules/react-native-aes-crypto/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-aes-crypto", - "version": "3.0.61", + "version": "3.0.63", "description": "react-native-aes-crypto", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-app-update/package.json b/native-modules/react-native-app-update/package.json index fb8481cb..57ee5189 100644 --- a/native-modules/react-native-app-update/package.json +++ b/native-modules/react-native-app-update/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-app-update", - "version": "3.0.61", + "version": "3.0.63", "description": "react-native-app-update", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-async-storage/package.json b/native-modules/react-native-async-storage/package.json index b7f5c320..9c2632cb 100644 --- a/native-modules/react-native-async-storage/package.json +++ b/native-modules/react-native-async-storage/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-async-storage", - "version": "3.0.61", + "version": "3.0.63", "description": "react-native-async-storage", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-background-thread/package.json b/native-modules/react-native-background-thread/package.json index 7832ca03..aae9b597 100644 --- a/native-modules/react-native-background-thread/package.json +++ b/native-modules/react-native-background-thread/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-background-thread", - "version": "3.0.62", + "version": "3.0.63", "description": "react-native-background-thread", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-bundle-crypto/package.json b/native-modules/react-native-bundle-crypto/package.json index 7b20b693..fd49e3e5 100644 --- a/native-modules/react-native-bundle-crypto/package.json +++ b/native-modules/react-native-bundle-crypto/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-bundle-crypto", - "version": "3.0.61", + "version": "3.0.63", "description": "react-native-bundle-crypto", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-bundle-update/package.json b/native-modules/react-native-bundle-update/package.json index e3c180a6..088a10fb 100644 --- a/native-modules/react-native-bundle-update/package.json +++ b/native-modules/react-native-bundle-update/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-bundle-update", - "version": "3.0.61", + "version": "3.0.63", "description": "react-native-bundle-update", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-check-biometric-auth-changed/package.json b/native-modules/react-native-check-biometric-auth-changed/package.json index 37368903..7023f671 100644 --- a/native-modules/react-native-check-biometric-auth-changed/package.json +++ b/native-modules/react-native-check-biometric-auth-changed/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-check-biometric-auth-changed", - "version": "3.0.61", + "version": "3.0.63", "description": "react-native-check-biometric-auth-changed", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-cloud-fs/package.json b/native-modules/react-native-cloud-fs/package.json index fe366058..899ee890 100644 --- a/native-modules/react-native-cloud-fs/package.json +++ b/native-modules/react-native-cloud-fs/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-cloud-fs", - "version": "3.0.61", + "version": "3.0.63", "description": "react-native-cloud-fs TurboModule for OneKey", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-cloud-kit-module/package.json b/native-modules/react-native-cloud-kit-module/package.json index 5eef4bae..f5139738 100644 --- a/native-modules/react-native-cloud-kit-module/package.json +++ b/native-modules/react-native-cloud-kit-module/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-cloud-kit-module", - "version": "3.0.61", + "version": "3.0.63", "description": "react-native-cloud-kit-module", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-device-utils/package.json b/native-modules/react-native-device-utils/package.json index f56f6b35..1062b98f 100644 --- a/native-modules/react-native-device-utils/package.json +++ b/native-modules/react-native-device-utils/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-device-utils", - "version": "3.0.61", + "version": "3.0.63", "description": "react-native-device-utils", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-dns-lookup/package.json b/native-modules/react-native-dns-lookup/package.json index 46041c3c..ac757a09 100644 --- a/native-modules/react-native-dns-lookup/package.json +++ b/native-modules/react-native-dns-lookup/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-dns-lookup", - "version": "3.0.61", + "version": "3.0.63", "description": "react-native-dns-lookup", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-get-random-values/package.json b/native-modules/react-native-get-random-values/package.json index f5554c1f..62ade93d 100644 --- a/native-modules/react-native-get-random-values/package.json +++ b/native-modules/react-native-get-random-values/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-get-random-values", - "version": "3.0.61", + "version": "3.0.63", "description": "react-native-get-random-values", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-keychain-module/package.json b/native-modules/react-native-keychain-module/package.json index d66c7089..6d9c5d27 100644 --- a/native-modules/react-native-keychain-module/package.json +++ b/native-modules/react-native-keychain-module/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-keychain-module", - "version": "3.0.61", + "version": "3.0.63", "description": "react-native-keychain-module", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-lite-card/package.json b/native-modules/react-native-lite-card/package.json index 95c8cc32..4e1373bf 100644 --- a/native-modules/react-native-lite-card/package.json +++ b/native-modules/react-native-lite-card/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-lite-card", - "version": "3.0.61", + "version": "3.0.63", "description": "lite card", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-network-info/package.json b/native-modules/react-native-network-info/package.json index 223f5ac8..b8de2ee8 100644 --- a/native-modules/react-native-network-info/package.json +++ b/native-modules/react-native-network-info/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-network-info", - "version": "3.0.61", + "version": "3.0.63", "description": "react-native-network-info", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-pbkdf2/package.json b/native-modules/react-native-pbkdf2/package.json index ea76506f..5c3b3e8a 100644 --- a/native-modules/react-native-pbkdf2/package.json +++ b/native-modules/react-native-pbkdf2/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-pbkdf2", - "version": "3.0.61", + "version": "3.0.63", "description": "react-native-pbkdf2", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-perf-memory/package.json b/native-modules/react-native-perf-memory/package.json index f853e720..97a7d252 100644 --- a/native-modules/react-native-perf-memory/package.json +++ b/native-modules/react-native-perf-memory/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-perf-memory", - "version": "3.0.61", + "version": "3.0.63", "description": "react-native-perf-memory", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-perf-stats/package.json b/native-modules/react-native-perf-stats/package.json index e9d75418..00c67c51 100644 --- a/native-modules/react-native-perf-stats/package.json +++ b/native-modules/react-native-perf-stats/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-perf-stats", - "version": "3.0.61", + "version": "3.0.63", "description": "react-native-perf-stats", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-ping/package.json b/native-modules/react-native-ping/package.json index fd7d1480..59830ca3 100644 --- a/native-modules/react-native-ping/package.json +++ b/native-modules/react-native-ping/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-ping", - "version": "3.0.61", + "version": "3.0.63", "description": "react-native-ping TurboModule for OneKey", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-range-downloader/package.json b/native-modules/react-native-range-downloader/package.json index a255201d..72e7e6a7 100644 --- a/native-modules/react-native-range-downloader/package.json +++ b/native-modules/react-native-range-downloader/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-range-downloader", - "version": "3.0.61", + "version": "3.0.63", "description": "react-native-range-downloader", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-splash-screen/package.json b/native-modules/react-native-splash-screen/package.json index 8618e6ea..4b42d414 100644 --- a/native-modules/react-native-splash-screen/package.json +++ b/native-modules/react-native-splash-screen/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-splash-screen", - "version": "3.0.61", + "version": "3.0.63", "description": "react-native-splash-screen", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-split-bundle-loader/package.json b/native-modules/react-native-split-bundle-loader/package.json index 86ed11b7..7182f893 100644 --- a/native-modules/react-native-split-bundle-loader/package.json +++ b/native-modules/react-native-split-bundle-loader/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-split-bundle-loader", - "version": "3.0.61", + "version": "3.0.63", "description": "react-native-split-bundle-loader", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-tcp-socket/package.json b/native-modules/react-native-tcp-socket/package.json index dae95ac7..4d337584 100644 --- a/native-modules/react-native-tcp-socket/package.json +++ b/native-modules/react-native-tcp-socket/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-tcp-socket", - "version": "3.0.61", + "version": "3.0.63", "description": "react-native-tcp-socket", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-zip-archive/package.json b/native-modules/react-native-zip-archive/package.json index 74477da6..08256f78 100644 --- a/native-modules/react-native-zip-archive/package.json +++ b/native-modules/react-native-zip-archive/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-zip-archive", - "version": "3.0.61", + "version": "3.0.63", "description": "react-native-zip-archive Nitro HybridObject for OneKey", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-views/react-native-auto-size-input/package.json b/native-views/react-native-auto-size-input/package.json index aa043e6f..6743e36e 100644 --- a/native-views/react-native-auto-size-input/package.json +++ b/native-views/react-native-auto-size-input/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-auto-size-input", - "version": "3.0.61", + "version": "3.0.63", "description": "Auto-sizing text input with font scaling, prefix and suffix support", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-views/react-native-chart-webview/package.json b/native-views/react-native-chart-webview/package.json index 611537f3..3b8a8af2 100644 --- a/native-views/react-native-chart-webview/package.json +++ b/native-views/react-native-chart-webview/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-chart-webview", - "version": "3.0.61", + "version": "3.0.63", "description": "react-native-chart-webview", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-views/react-native-pager-view/package.json b/native-views/react-native-pager-view/package.json index d5af0151..61b5a341 100644 --- a/native-views/react-native-pager-view/package.json +++ b/native-views/react-native-pager-view/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-pager-view", - "version": "3.0.61", + "version": "3.0.63", "description": "React Native wrapper for Android and iOS ViewPager", "source": "./src/index.tsx", "main": "./lib/module/index.js", diff --git a/native-views/react-native-perp-depth-bar/package.json b/native-views/react-native-perp-depth-bar/package.json index 709ff6fe..1dba85e5 100644 --- a/native-views/react-native-perp-depth-bar/package.json +++ b/native-views/react-native-perp-depth-bar/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-perp-depth-bar", - "version": "3.0.61", + "version": "3.0.63", "description": "react-native-perp-depth-bar", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-views/react-native-scroll-guard/package.json b/native-views/react-native-scroll-guard/package.json index 31461c65..13c7a6c0 100644 --- a/native-views/react-native-scroll-guard/package.json +++ b/native-views/react-native-scroll-guard/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-scroll-guard", - "version": "3.0.61", + "version": "3.0.63", "description": "A native view wrapper that prevents parent scrollable containers (PagerView/ViewPager2) from intercepting child scroll gestures", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-views/react-native-segment-slider/package.json b/native-views/react-native-segment-slider/package.json index 19c43ebe..0b378347 100644 --- a/native-views/react-native-segment-slider/package.json +++ b/native-views/react-native-segment-slider/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-segment-slider", - "version": "3.0.61", + "version": "3.0.63", "description": "react-native-segment-slider", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-views/react-native-skeleton/package.json b/native-views/react-native-skeleton/package.json index 6070bbaf..e7f03b31 100644 --- a/native-views/react-native-skeleton/package.json +++ b/native-views/react-native-skeleton/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-skeleton", - "version": "3.0.61", + "version": "3.0.63", "description": "react-native-skeleton", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-views/react-native-tab-view/package.json b/native-views/react-native-tab-view/package.json index 54afdf4d..b55e78e6 100644 --- a/native-views/react-native-tab-view/package.json +++ b/native-views/react-native-tab-view/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-tab-view", - "version": "3.0.61", + "version": "3.0.63", "description": "Native Bottom Tabs for React Native (UIKit implementation)", "source": "./src/index.tsx", "main": "./lib/module/index.js", From 8fd75242a8f9061907f7a17e475d08e26f7960bd Mon Sep 17 00:00:00 2001 From: huhuanming Date: Fri, 12 Jun 2026 11:26:49 +0800 Subject: [PATCH 3/5] fix: harden background-thread coalesced drain against threading bugs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Audit of the coalesced-drain change surfaced five concurrency issues, all addressed here: - gBgTimerExecutor was written without gTimerMutex while every reader holds it — a data race (UB) on std::function. Guard the assignment. - A transient ptr==0 drain leak-cleared the ENTIRE coalesced queue, permanently dropping main-runtime SharedRPC deliveries (no JS retry net). Keep queue.items intact; only disarm the drain latch. - nativeInvalidateSharedRpc left drainScheduled==true, stranding all future work after reload. Track the outstanding drain via scheduledDrainWorkId and clear the orphaned gPendingWork entry. - Recovery relied on a later enqueue to re-arm the drain, so items carried over a ptr==0 reload could strand until nativeDestroy. Make recovery structural: re-arm a drain in nativeInstallSharedBridge when the freshly installed runtime's queue is non-empty. - Closed a stale-id race where a concurrent invalidate empties the queue between drainRuntimeWorkQueue's remaining>0 check and the reschedule, by guarding scheduleRuntimeDrain against an empty queue. --- .../android/src/main/cpp/cpp-adapter.cpp | 102 ++++++++++++++++-- .../BackgroundThreadManager.kt | 17 +-- 2 files changed, 104 insertions(+), 15 deletions(-) diff --git a/native-modules/react-native-background-thread/android/src/main/cpp/cpp-adapter.cpp b/native-modules/react-native-background-thread/android/src/main/cpp/cpp-adapter.cpp index 6a647494..5b217a55 100644 --- a/native-modules/react-native-background-thread/android/src/main/cpp/cpp-adapter.cpp +++ b/native-modules/react-native-background-thread/android/src/main/cpp/cpp-adapter.cpp @@ -80,6 +80,11 @@ static constexpr size_t kRuntimeQueueWarnInterval = 128; struct RuntimeWorkQueue { std::deque> items; bool drainScheduled = false; + // workId of the drain currently posted to gPendingWork (valid only while + // drainScheduled == true). Tracked so a teardown path (nativeInvalidate + // SharedRpc) can erase the orphaned gPendingWork entry if its posted drain + // is dropped during reload. -1 means "none outstanding". + int64_t scheduledDrainWorkId = -1; }; static RuntimeWorkQueue gMainRuntimeWorkQueue; @@ -100,6 +105,7 @@ static void leakAndClearRuntimeQueue(RuntimeWorkQueue &queue) { } queue.items.clear(); queue.drainScheduled = false; + queue.scheduledDrainWorkId = -1; } static bool callScheduleOnJSThread(const JavaObjectRef &ref, bool isMain, int64_t workId) { @@ -186,6 +192,9 @@ static void drainRuntimeWorkQueue(jsi::Runtime &rt, JavaObjectRef ref, bool isMa remaining = queue.items.size(); if (remaining == 0) { queue.drainScheduled = false; + // This drain's gPendingWork entry was already erased by + // nativeExecuteWork before it ran; its workId is now stale. + queue.scheduledDrainWorkId = -1; } else { shouldReschedule = true; } @@ -206,8 +215,24 @@ static void scheduleRuntimeDrain(const JavaObjectRef &ref, bool isMain) { size_t queued = 0; { std::lock_guard lock(gWorkMutex); + auto &queue = getRuntimeWorkQueue(isMain); + // Stale-id guard: drainRuntimeWorkQueue observes remaining>0, drops the + // lock, then calls us — but a concurrent nativeInvalidateSharedRpc can + // clear the queue + latch in between. If the queue is now empty there is + // nothing to drain: do NOT post a gPendingWork entry / scheduleOnJSThread + // for an already-drained/invalidated queue. Just disarm the latch and + // return. The normal enqueue→schedule path always has items.size()>=1, so + // this never short-circuits it. + if (queue.items.empty()) { + queue.drainScheduled = false; + queue.scheduledDrainWorkId = -1; + return; + } workId = gNextWorkId++; - queued = getRuntimeWorkQueue(isMain).items.size(); + queued = queue.items.size(); + // Track the outstanding drain's workId so a teardown path can erase its + // orphaned gPendingWork entry if the post is dropped during reload. + queue.scheduledDrainWorkId = workId; gPendingWork[workId] = [ref, isMain](jsi::Runtime &rt) { drainRuntimeWorkQueue(rt, ref, isMain); }; @@ -936,13 +961,17 @@ Java_com_backgroundthread_BackgroundThreadManager_nativeEvaluateSegmentInBackgro // 1. gPendingWork[workId] — the stored work lambda (holding the segment // SOURCE BUFFER). nativeExecuteWork is the only other eraser and it will // never run for this id, so without this it leaks until nativeDestroy. -// 2. The coalesced RuntimeWorkQueue for this runtime — under the coalesced -// model a successful post (scheduled==true) that never reaches the JS -// thread leaves the queue stranded with drainScheduled==true, so a -// recovered runtime would never re-arm a drain. Leak+clear the queued -// functors (their ~jsi::Function must not run on a dead runtime) and reset -// the drain latch so the next enqueue re-arms a fresh drain. Applies to -// BOTH runtimes (isMain selects which queue). +// 2. The coalesced RuntimeWorkQueue's drain latch for this runtime — under +// the coalesced model a successful post (scheduled==true) that never +// reaches the JS thread leaves the queue stranded with drainScheduled== +// true, so a recovered runtime would never re-arm a drain. This is a +// TRANSIENT condition: ptr==0 happens during reload, and the SAME runtime +// recovers with a fresh ptr. We therefore must NOT leak+clear queue.items +// — the main runtime has no JS-side retry net, so abandoning its queued +// SharedRPC deliveries (notifyOtherRuntime) loses them forever. Instead we +// only reset the drain latch (drainScheduled / scheduledDrainWorkId) so the +// next enqueue re-arms a fresh drain, leaving queue.items intact for the +// recovered runtime to drain. Applies to BOTH runtimes (isMain selects). // 3. (bg only) The in-flight bg eval(s) — settle as retryable NO_RUNTIME so // the JNI global ref is released and the JS promise resolves now instead of // hanging on the 30s watchdog. drain-all is sound: an unreachable bg JS @@ -955,7 +984,12 @@ Java_com_backgroundthread_BackgroundThreadManager_nativeDropScheduledWork( { std::lock_guard lock(gWorkMutex); gPendingWork.erase(static_cast(workId)); - leakAndClearRuntimeQueue(getRuntimeWorkQueue(static_cast(isMain))); + // TRANSIENT ptr==0 (reload in flight): do NOT abandon queue.items — the + // recovered runtime still needs them (main has no JS retry net). Only + // disarm the drain latch so the next enqueue re-arms a fresh drain. + auto &queue = getRuntimeWorkQueue(static_cast(isMain)); + queue.drainScheduled = false; + queue.scheduledDrainWorkId = -1; } if (!isMain) { drainPendingBgEvals("Background runtime unreachable when scheduling segment eval"); @@ -998,6 +1032,12 @@ Java_com_backgroundthread_BackgroundThreadManager_nativeInstallSharedBridge( // back to the bg JS queue. We must do this BEFORE moving `executor` into // SharedRPC::install (which will std::move it out). if (!capturedIsMain) { + // gBgTimerExecutor is read/cleared under gTimerMutex (timer worker + // snapshot ~L857, nativeDestroy ~L1133); this write must take the same + // lock or it races those readers (UB on std::function). nativeInstall + // SharedBridge holds no other lock here, so a narrow guard scoped to + // just the assignment is correct and cannot double-lock. + std::lock_guard lock(gTimerMutex); gBgTimerExecutor = executor; } SharedRPC::install(*rt, std::move(executor), runtimeId); @@ -1010,6 +1050,29 @@ Java_com_backgroundthread_BackgroundThreadManager_nativeInstallSharedBridge( installTimersOnRuntime(*rt); invokeOptionalGlobalFunction(*rt, "__setupBackgroundRPCHandler"); } + + // Recover items stranded by a ptr==0 drop during reload. nativeDropScheduled + // Work deliberately KEEPS queue.items (main has no JS retry net) and only + // disarms the drain latch, relying on a LATER enqueue to re-arm a drain. But + // if no further enqueue arrives after this runtime recovers, those carried- + // over items (e.g. main-runtime notifyOtherRuntime deliveries) would sit + // until nativeDestroy and be lost. Make recovery structural: if the freshly + // installed runtime's queue has leftover items and no drain is armed, re-arm + // one now on this runtime's executor (`ref`). Mirror enqueueRuntimeWork's + // lock discipline: set the latch under gWorkMutex, call scheduleRuntimeDrain + // OUTSIDE the lock. Applies to BOTH runtimes (capturedIsMain selects). + bool shouldRecoverDrain = false; + { + std::lock_guard lock(gWorkMutex); + auto &queue = getRuntimeWorkQueue(capturedIsMain); + if (!queue.items.empty() && !queue.drainScheduled) { + queue.drainScheduled = true; + shouldRecoverDrain = true; + } + } + if (shouldRecoverDrain) { + scheduleRuntimeDrain(ref, capturedIsMain); + } } // ── nativeSetupErrorHandler ───────────────────────────────────────────── @@ -1094,6 +1157,26 @@ Java_com_backgroundthread_BackgroundThreadManager_nativeInvalidateSharedRpc( env->ReleaseStringUTFChars(runtimeId, idChars); bool found = SharedRPC::invalidate(id); + + // The runtime named by `id` is being torn down (restart → host.reload). + // Its coalesced work queue must be fully quiesced: if a drain was posted + // (drainScheduled==true) but is dropped during reload, the latch would + // survive the reinstall and every future enqueue would see a stale + // drainScheduled==true → shouldSchedule stays false and new work enqueues + // but never schedules a drain again (work stranded forever). Leak+clear the + // queue (it's being destroyed anyway — the queued ~jsi::Function must not + // run on the dying runtime), reset the drain latch, and erase the orphaned + // gPendingWork entry for the outstanding drain. runtimeId "main" → isMain. + { + std::lock_guard lock(gWorkMutex); + bool isMain = (id == "main"); + auto &queue = getRuntimeWorkQueue(isMain); + if (queue.scheduledDrainWorkId >= 0) { + gPendingWork.erase(queue.scheduledDrainWorkId); + } + leakAndClearRuntimeQueue(queue); + } + LOGI("nativeInvalidateSharedRpc: id=%s found=%d", id.c_str(), found ? 1 : 0); return found ? JNI_TRUE : JNI_FALSE; } @@ -1153,6 +1236,7 @@ Java_com_backgroundthread_BackgroundThreadManager_nativeDestroy( } queue->items.clear(); queue->drainScheduled = false; + queue->scheduledDrainWorkId = -1; } } diff --git a/native-modules/react-native-background-thread/android/src/main/java/com/backgroundthread/BackgroundThreadManager.kt b/native-modules/react-native-background-thread/android/src/main/java/com/backgroundthread/BackgroundThreadManager.kt index ac7448dd..63c295be 100644 --- a/native-modules/react-native-background-thread/android/src/main/java/com/backgroundthread/BackgroundThreadManager.kt +++ b/native-modules/react-native-background-thread/android/src/main/java/com/backgroundthread/BackgroundThreadManager.kt @@ -129,9 +129,12 @@ class BackgroundThreadManager private constructor() { * already on the stale/torn-down JS thread, so C++ saw scheduled == true and * will not clean up on its own). For the given runtime ([isMain]) the native * side erases gPendingWork[workId] (frees the captured segment source - * buffer), leak+clears the coalesced RuntimeWorkQueue, and resets - * drainScheduled so a recovered runtime re-arms a fresh drain on the next - * enqueue. For the bg runtime only, it also settles every in-flight bg + * buffer) and resets drainScheduled so a recovered runtime re-arms a fresh + * drain on the next enqueue. This is a TRANSIENT reload condition, so the + * native side deliberately leaves the coalesced RuntimeWorkQueue's items + * INTACT — the same runtime recovers and the main runtime has no JS-side + * retry net, so abandoning its queued SharedRPC deliveries would lose them. + * For the bg runtime only, it also settles every in-flight bg * segment eval as a retryable NO_RUNTIME failure so each JNI global ref is * released and the JS promise resolves immediately instead of leaking until * teardown or the bg watchdog. Exactly-once on the native side. @@ -492,9 +495,11 @@ class BackgroundThreadManager private constructor() { // SUCCESSFUL post (C++ saw scheduled==true), so the work won't // run and C++ won't clean up on its own. Drop it for THIS // runtime (main or bg): erase gPendingWork[workId] (frees the - // source buffer), leak+clear the coalesced RuntimeWorkQueue, - // and reset drainScheduled so a recovered runtime re-arms a - // fresh drain. drainPendingBgEvals inside the native fn is + // source buffer) and reset drainScheduled so a recovered + // runtime re-arms a fresh drain. The coalesced RuntimeWork + // Queue items are left intact (transient reload; the same + // runtime recovers and main has no JS retry net). + // drainPendingBgEvals inside the native fn is // gated to !isMain, so settling bg evals only happens for bg. nativeDropScheduledWork(isMain, workId) } From dee178b505a8c3075e00d0e1dd2f0c367da291c3 Mon Sep 17 00:00:00 2001 From: huhuanming Date: Fri, 12 Jun 2026 11:38:31 +0800 Subject: [PATCH 4/5] fix: prevent bg-eval replay and latch-stall across reload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to the coalesced-drain hardening, addressing two reload-window issues found by a second audit pass: - Keeping queue.items across a ptr==0 reload (so the recovered runtime can drain them) let a background segment-eval lambda be replayed by install-recover AFTER drainPendingBgEvals had already settled it as retryable NO_RUNTIME — re-running evaluateJavaScript on the segment a second time. The eval lambda now treats an already-erased gPendingBgEvals entry as 'a drain claimed me, JS will retry' and skips the re-evaluation. - A drain posted just before reload can be silently discarded by the dead pre-reload JS thread, so its nativeDropScheduledWork never fires and drainScheduled stays stuck true. install-recover gated on !drainScheduled would then skip recovery and strand the items. It now force-arms a drain on the freshly installed runtime whenever the queue is non-empty; a still-live stale drain self-cancels via the empty-queue guard in scheduleRuntimeDrain. --- .../android/src/main/cpp/cpp-adapter.cpp | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/native-modules/react-native-background-thread/android/src/main/cpp/cpp-adapter.cpp b/native-modules/react-native-background-thread/android/src/main/cpp/cpp-adapter.cpp index 5b217a55..f19e02f1 100644 --- a/native-modules/react-native-background-thread/android/src/main/cpp/cpp-adapter.cpp +++ b/native-modules/react-native-background-thread/android/src/main/cpp/cpp-adapter.cpp @@ -917,10 +917,19 @@ Java_com_backgroundthread_BackgroundThreadManager_nativeEvaluateSegmentInBackgro executor([globalCallback, settled, bgEvalId, source = std::move(source), url = std::move(url)](jsi::Runtime &rt) { // We are running now → claim ownership and remove our registry entry so - // a concurrent nativeDestroy drain can't also touch this eval. + // a concurrent nativeDestroy drain can't also touch this eval. If the + // entry is ALREADY gone, a drop/destroy drain (drainPendingBgEvals) + // already settled this eval as retryable NO_RUNTIME and JS will retry — + // so we must NOT evaluate the segment again. This is reachable because a + // ptr==0 reload keeps this lambda in the coalesced queue (it only + // disarms the drain latch, preserving queue.items) and the recovered + // runtime's install-recover replays it; without this guard the segment + // would be evaluated twice on the recovered runtime. { std::lock_guard lock(gBgEvalMutex); - gPendingBgEvals.erase(bgEvalId); + if (gPendingBgEvals.erase(bgEvalId) == 0) { + return; + } } std::string error; try { @@ -1056,16 +1065,24 @@ Java_com_backgroundthread_BackgroundThreadManager_nativeInstallSharedBridge( // disarms the drain latch, relying on a LATER enqueue to re-arm a drain. But // if no further enqueue arrives after this runtime recovers, those carried- // over items (e.g. main-runtime notifyOtherRuntime deliveries) would sit - // until nativeDestroy and be lost. Make recovery structural: if the freshly - // installed runtime's queue has leftover items and no drain is armed, re-arm - // one now on this runtime's executor (`ref`). Mirror enqueueRuntimeWork's - // lock discipline: set the latch under gWorkMutex, call scheduleRuntimeDrain - // OUTSIDE the lock. Applies to BOTH runtimes (capturedIsMain selects). + // until nativeDestroy and be lost. Make recovery structural. + // + // Force a fresh drain on the freshly installed runtime whenever the queue is + // non-empty, REGARDLESS of the drainScheduled latch: a drain posted before + // reload may have been queued on the now-dead pre-reload JS thread and + // silently discarded (its runnable never runs, so its nativeDropScheduledWork + // never fires and the latch is stuck drainScheduled==true). Gating recovery on + // !drainScheduled would then skip it and strand the items forever. Re-arming + // here on this runtime's executor (`ref`) guarantees they drain; if a stale + // drain is in fact still live, it self-cancels via the empty-queue guard in + // scheduleRuntimeDrain (at worst one benign extra drain hop). Mirror + // enqueueRuntimeWork's lock discipline: set the latch under gWorkMutex, call + // scheduleRuntimeDrain OUTSIDE the lock. Applies to BOTH runtimes. bool shouldRecoverDrain = false; { std::lock_guard lock(gWorkMutex); auto &queue = getRuntimeWorkQueue(capturedIsMain); - if (!queue.items.empty() && !queue.drainScheduled) { + if (!queue.items.empty()) { queue.drainScheduled = true; shouldRecoverDrain = true; } From bb1a4c06683fbd94651a9da969b69a715f030ca9 Mon Sep 17 00:00:00 2001 From: huhuanming Date: Fri, 12 Jun 2026 12:23:40 +0800 Subject: [PATCH 5/5] 3.0.64 --- native-modules/native-logger/package.json | 2 +- native-modules/react-native-aes-crypto/package.json | 2 +- native-modules/react-native-app-update/package.json | 2 +- native-modules/react-native-async-storage/package.json | 2 +- native-modules/react-native-background-thread/package.json | 2 +- native-modules/react-native-bundle-crypto/package.json | 2 +- native-modules/react-native-bundle-update/package.json | 2 +- .../react-native-check-biometric-auth-changed/package.json | 2 +- native-modules/react-native-cloud-fs/package.json | 2 +- native-modules/react-native-cloud-kit-module/package.json | 2 +- native-modules/react-native-device-utils/package.json | 2 +- native-modules/react-native-dns-lookup/package.json | 2 +- native-modules/react-native-get-random-values/package.json | 2 +- native-modules/react-native-keychain-module/package.json | 2 +- native-modules/react-native-lite-card/package.json | 2 +- native-modules/react-native-network-info/package.json | 2 +- native-modules/react-native-pbkdf2/package.json | 2 +- native-modules/react-native-perf-memory/package.json | 2 +- native-modules/react-native-perf-stats/package.json | 2 +- native-modules/react-native-ping/package.json | 2 +- native-modules/react-native-range-downloader/package.json | 2 +- native-modules/react-native-splash-screen/package.json | 2 +- native-modules/react-native-split-bundle-loader/package.json | 2 +- native-modules/react-native-tcp-socket/package.json | 2 +- native-modules/react-native-zip-archive/package.json | 2 +- native-views/react-native-auto-size-input/package.json | 2 +- native-views/react-native-chart-webview/package.json | 2 +- native-views/react-native-pager-view/package.json | 2 +- native-views/react-native-perp-depth-bar/package.json | 2 +- native-views/react-native-scroll-guard/package.json | 2 +- native-views/react-native-segment-slider/package.json | 2 +- native-views/react-native-skeleton/package.json | 2 +- native-views/react-native-tab-view/package.json | 2 +- 33 files changed, 33 insertions(+), 33 deletions(-) diff --git a/native-modules/native-logger/package.json b/native-modules/native-logger/package.json index 8e279866..e933e36e 100644 --- a/native-modules/native-logger/package.json +++ b/native-modules/native-logger/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-native-logger", - "version": "3.0.63", + "version": "3.0.64", "description": "react-native-native-logger", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-aes-crypto/package.json b/native-modules/react-native-aes-crypto/package.json index 4931854e..5a2edba0 100644 --- a/native-modules/react-native-aes-crypto/package.json +++ b/native-modules/react-native-aes-crypto/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-aes-crypto", - "version": "3.0.63", + "version": "3.0.64", "description": "react-native-aes-crypto", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-app-update/package.json b/native-modules/react-native-app-update/package.json index 57ee5189..4f89fad3 100644 --- a/native-modules/react-native-app-update/package.json +++ b/native-modules/react-native-app-update/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-app-update", - "version": "3.0.63", + "version": "3.0.64", "description": "react-native-app-update", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-async-storage/package.json b/native-modules/react-native-async-storage/package.json index 9c2632cb..2826e7a4 100644 --- a/native-modules/react-native-async-storage/package.json +++ b/native-modules/react-native-async-storage/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-async-storage", - "version": "3.0.63", + "version": "3.0.64", "description": "react-native-async-storage", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-background-thread/package.json b/native-modules/react-native-background-thread/package.json index aae9b597..c0ca476f 100644 --- a/native-modules/react-native-background-thread/package.json +++ b/native-modules/react-native-background-thread/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-background-thread", - "version": "3.0.63", + "version": "3.0.64", "description": "react-native-background-thread", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-bundle-crypto/package.json b/native-modules/react-native-bundle-crypto/package.json index fd49e3e5..563f81f5 100644 --- a/native-modules/react-native-bundle-crypto/package.json +++ b/native-modules/react-native-bundle-crypto/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-bundle-crypto", - "version": "3.0.63", + "version": "3.0.64", "description": "react-native-bundle-crypto", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-bundle-update/package.json b/native-modules/react-native-bundle-update/package.json index 088a10fb..6abde5dc 100644 --- a/native-modules/react-native-bundle-update/package.json +++ b/native-modules/react-native-bundle-update/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-bundle-update", - "version": "3.0.63", + "version": "3.0.64", "description": "react-native-bundle-update", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-check-biometric-auth-changed/package.json b/native-modules/react-native-check-biometric-auth-changed/package.json index 7023f671..58d01eb4 100644 --- a/native-modules/react-native-check-biometric-auth-changed/package.json +++ b/native-modules/react-native-check-biometric-auth-changed/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-check-biometric-auth-changed", - "version": "3.0.63", + "version": "3.0.64", "description": "react-native-check-biometric-auth-changed", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-cloud-fs/package.json b/native-modules/react-native-cloud-fs/package.json index 899ee890..7563e06d 100644 --- a/native-modules/react-native-cloud-fs/package.json +++ b/native-modules/react-native-cloud-fs/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-cloud-fs", - "version": "3.0.63", + "version": "3.0.64", "description": "react-native-cloud-fs TurboModule for OneKey", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-cloud-kit-module/package.json b/native-modules/react-native-cloud-kit-module/package.json index f5139738..c4b77eea 100644 --- a/native-modules/react-native-cloud-kit-module/package.json +++ b/native-modules/react-native-cloud-kit-module/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-cloud-kit-module", - "version": "3.0.63", + "version": "3.0.64", "description": "react-native-cloud-kit-module", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-device-utils/package.json b/native-modules/react-native-device-utils/package.json index 1062b98f..ce2f1657 100644 --- a/native-modules/react-native-device-utils/package.json +++ b/native-modules/react-native-device-utils/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-device-utils", - "version": "3.0.63", + "version": "3.0.64", "description": "react-native-device-utils", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-dns-lookup/package.json b/native-modules/react-native-dns-lookup/package.json index ac757a09..66aaa59c 100644 --- a/native-modules/react-native-dns-lookup/package.json +++ b/native-modules/react-native-dns-lookup/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-dns-lookup", - "version": "3.0.63", + "version": "3.0.64", "description": "react-native-dns-lookup", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-get-random-values/package.json b/native-modules/react-native-get-random-values/package.json index 62ade93d..e12f8f5c 100644 --- a/native-modules/react-native-get-random-values/package.json +++ b/native-modules/react-native-get-random-values/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-get-random-values", - "version": "3.0.63", + "version": "3.0.64", "description": "react-native-get-random-values", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-keychain-module/package.json b/native-modules/react-native-keychain-module/package.json index 6d9c5d27..dc4b7a37 100644 --- a/native-modules/react-native-keychain-module/package.json +++ b/native-modules/react-native-keychain-module/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-keychain-module", - "version": "3.0.63", + "version": "3.0.64", "description": "react-native-keychain-module", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-lite-card/package.json b/native-modules/react-native-lite-card/package.json index 4e1373bf..f8434a5a 100644 --- a/native-modules/react-native-lite-card/package.json +++ b/native-modules/react-native-lite-card/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-lite-card", - "version": "3.0.63", + "version": "3.0.64", "description": "lite card", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-network-info/package.json b/native-modules/react-native-network-info/package.json index b8de2ee8..0da99833 100644 --- a/native-modules/react-native-network-info/package.json +++ b/native-modules/react-native-network-info/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-network-info", - "version": "3.0.63", + "version": "3.0.64", "description": "react-native-network-info", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-pbkdf2/package.json b/native-modules/react-native-pbkdf2/package.json index 5c3b3e8a..45052487 100644 --- a/native-modules/react-native-pbkdf2/package.json +++ b/native-modules/react-native-pbkdf2/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-pbkdf2", - "version": "3.0.63", + "version": "3.0.64", "description": "react-native-pbkdf2", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-perf-memory/package.json b/native-modules/react-native-perf-memory/package.json index 97a7d252..e778a733 100644 --- a/native-modules/react-native-perf-memory/package.json +++ b/native-modules/react-native-perf-memory/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-perf-memory", - "version": "3.0.63", + "version": "3.0.64", "description": "react-native-perf-memory", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-perf-stats/package.json b/native-modules/react-native-perf-stats/package.json index 00c67c51..3e310c7d 100644 --- a/native-modules/react-native-perf-stats/package.json +++ b/native-modules/react-native-perf-stats/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-perf-stats", - "version": "3.0.63", + "version": "3.0.64", "description": "react-native-perf-stats", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-ping/package.json b/native-modules/react-native-ping/package.json index 59830ca3..69017dca 100644 --- a/native-modules/react-native-ping/package.json +++ b/native-modules/react-native-ping/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-ping", - "version": "3.0.63", + "version": "3.0.64", "description": "react-native-ping TurboModule for OneKey", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-range-downloader/package.json b/native-modules/react-native-range-downloader/package.json index 72e7e6a7..56f4e2aa 100644 --- a/native-modules/react-native-range-downloader/package.json +++ b/native-modules/react-native-range-downloader/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-range-downloader", - "version": "3.0.63", + "version": "3.0.64", "description": "react-native-range-downloader", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-splash-screen/package.json b/native-modules/react-native-splash-screen/package.json index 4b42d414..7401211c 100644 --- a/native-modules/react-native-splash-screen/package.json +++ b/native-modules/react-native-splash-screen/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-splash-screen", - "version": "3.0.63", + "version": "3.0.64", "description": "react-native-splash-screen", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-split-bundle-loader/package.json b/native-modules/react-native-split-bundle-loader/package.json index 7182f893..f0dd9aa6 100644 --- a/native-modules/react-native-split-bundle-loader/package.json +++ b/native-modules/react-native-split-bundle-loader/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-split-bundle-loader", - "version": "3.0.63", + "version": "3.0.64", "description": "react-native-split-bundle-loader", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-tcp-socket/package.json b/native-modules/react-native-tcp-socket/package.json index 4d337584..1ba862e0 100644 --- a/native-modules/react-native-tcp-socket/package.json +++ b/native-modules/react-native-tcp-socket/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-tcp-socket", - "version": "3.0.63", + "version": "3.0.64", "description": "react-native-tcp-socket", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-zip-archive/package.json b/native-modules/react-native-zip-archive/package.json index 08256f78..bcc9e5e7 100644 --- a/native-modules/react-native-zip-archive/package.json +++ b/native-modules/react-native-zip-archive/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-zip-archive", - "version": "3.0.63", + "version": "3.0.64", "description": "react-native-zip-archive Nitro HybridObject for OneKey", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-views/react-native-auto-size-input/package.json b/native-views/react-native-auto-size-input/package.json index 6743e36e..1b23a376 100644 --- a/native-views/react-native-auto-size-input/package.json +++ b/native-views/react-native-auto-size-input/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-auto-size-input", - "version": "3.0.63", + "version": "3.0.64", "description": "Auto-sizing text input with font scaling, prefix and suffix support", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-views/react-native-chart-webview/package.json b/native-views/react-native-chart-webview/package.json index 3b8a8af2..fbfa2c93 100644 --- a/native-views/react-native-chart-webview/package.json +++ b/native-views/react-native-chart-webview/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-chart-webview", - "version": "3.0.63", + "version": "3.0.64", "description": "react-native-chart-webview", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-views/react-native-pager-view/package.json b/native-views/react-native-pager-view/package.json index 61b5a341..bbb3cede 100644 --- a/native-views/react-native-pager-view/package.json +++ b/native-views/react-native-pager-view/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-pager-view", - "version": "3.0.63", + "version": "3.0.64", "description": "React Native wrapper for Android and iOS ViewPager", "source": "./src/index.tsx", "main": "./lib/module/index.js", diff --git a/native-views/react-native-perp-depth-bar/package.json b/native-views/react-native-perp-depth-bar/package.json index 1dba85e5..b73a1c9d 100644 --- a/native-views/react-native-perp-depth-bar/package.json +++ b/native-views/react-native-perp-depth-bar/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-perp-depth-bar", - "version": "3.0.63", + "version": "3.0.64", "description": "react-native-perp-depth-bar", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-views/react-native-scroll-guard/package.json b/native-views/react-native-scroll-guard/package.json index 13c7a6c0..613842f4 100644 --- a/native-views/react-native-scroll-guard/package.json +++ b/native-views/react-native-scroll-guard/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-scroll-guard", - "version": "3.0.63", + "version": "3.0.64", "description": "A native view wrapper that prevents parent scrollable containers (PagerView/ViewPager2) from intercepting child scroll gestures", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-views/react-native-segment-slider/package.json b/native-views/react-native-segment-slider/package.json index 0b378347..67f32742 100644 --- a/native-views/react-native-segment-slider/package.json +++ b/native-views/react-native-segment-slider/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-segment-slider", - "version": "3.0.63", + "version": "3.0.64", "description": "react-native-segment-slider", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-views/react-native-skeleton/package.json b/native-views/react-native-skeleton/package.json index e7f03b31..579c2e46 100644 --- a/native-views/react-native-skeleton/package.json +++ b/native-views/react-native-skeleton/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-skeleton", - "version": "3.0.63", + "version": "3.0.64", "description": "react-native-skeleton", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-views/react-native-tab-view/package.json b/native-views/react-native-tab-view/package.json index b55e78e6..4bdf8731 100644 --- a/native-views/react-native-tab-view/package.json +++ b/native-views/react-native-tab-view/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-tab-view", - "version": "3.0.63", + "version": "3.0.64", "description": "Native Bottom Tabs for React Native (UIKit implementation)", "source": "./src/index.tsx", "main": "./lib/module/index.js",