From 9fb792a00897386088e4df690fb6ea13051502dc Mon Sep 17 00:00:00 2001 From: Carlos Enumo Date: Wed, 15 Jul 2026 20:38:10 +0100 Subject: [PATCH] fix(requestIdleCallback): timeout not assigned at NativeIdleCallbacks --- gradle.properties | 2 +- .../idlecallbacks/NativeIdleCallbacks.cpp | 1 + .../examples/Playground/RNTesterPlayground.js | 56 ++++++++++++++++++- 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/gradle.properties b/gradle.properties index 1028b5c5238e..54deb91be797 100644 --- a/gradle.properties +++ b/gradle.properties @@ -13,7 +13,7 @@ reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 # Controls whether to use Hermes from stable builds. This will force hermes version # set in the sdks/hermes-engine/version.properties file to be used. This has a higher # priority than react.internal.useHermesNightly. -react.internal.useHermesStable=false +react.internal.useHermesStable=true # Controls whether to use Hermes from nightly builds. This will speed up builds # but should NOT be turned on for CI or release builds. diff --git a/packages/react-native/ReactCommon/react/nativemodule/idlecallbacks/NativeIdleCallbacks.cpp b/packages/react-native/ReactCommon/react/nativemodule/idlecallbacks/NativeIdleCallbacks.cpp index d3f299395723..099a53f7fb4e 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/idlecallbacks/NativeIdleCallbacks.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/idlecallbacks/NativeIdleCallbacks.cpp @@ -87,6 +87,7 @@ CallbackHandle NativeIdleCallbacks::requestIdleCallback( if (options.has_value() && options.value().timeout.has_value()) { HighResDuration userTimeout = options.value().timeout.value(); if (userTimeout > HighResDuration::zero()) { + timeout = userTimeout; expirationTime = runtimeScheduler->now() + userTimeout; } } diff --git a/packages/rn-tester/js/examples/Playground/RNTesterPlayground.js b/packages/rn-tester/js/examples/Playground/RNTesterPlayground.js index 8e6b10479de7..e8039d6dc418 100644 --- a/packages/rn-tester/js/examples/Playground/RNTesterPlayground.js +++ b/packages/rn-tester/js/examples/Playground/RNTesterPlayground.js @@ -13,13 +13,63 @@ import type {RNTesterModuleExample} from '../../types/RNTesterTypes'; import RNTesterText from '../../components/RNTesterText'; import * as React from 'react'; import {StyleSheet, View} from 'react-native'; +import {useState, useEffect} from 'react'; +/** + * Proves NativeIdleCallbacks forwards options.timeout into scheduleIdleTask. + * + * Strategy: keep scheduling LowPriority work (10s expiry each). That always + * beats an idle task with the default 5min expiry (bug), but after the first + * LowPriority tick a patched idle task (LowPriority + timeout ≈ 10s) becomes + * the earliest-expiring task and runs. + * + * Expect with the patch: fire within ~1s. + * Expect without (`timeout = userTimeout` removed): stuck on "starving…" for minutes. + */ function Playground() { + const [label, setLabel] = useState('starving LowPriority queue…'); + + useEffect(() => { + const scheduler = global.nativeRuntimeScheduler; + const {unstable_scheduleCallback, unstable_LowPriority} = scheduler; + + const start = performance.now(); + let keepStarving = true; + + const starve = () => { + if (!keepStarving) { + return; + } + unstable_scheduleCallback(unstable_LowPriority, () => { + const blockStart = performance.now(); + while (performance.now() - blockStart < 30) {} + starve(); + }); + }; + + requestIdleCallback( + deadline => { + keepStarving = false; + const elapsedMs = Math.round(performance.now() - start); + setLabel( + `✅ PATCHED: fired after ${elapsedMs}ms under LowPriority load, didTimeout=${String( + deadline.didTimeout, + )}`, + ); + }, + {timeout: 10}, + ); + + starve(); + + return () => { + keepStarving = false; + }; + }, []); + return ( - - Edit "RNTesterPlayground.js" to change this file - + {label} ); }