From 4a0e313948140fa2c1f3cfaca38c5bb90d25522d Mon Sep 17 00:00:00 2001 From: ivagin Date: Thu, 31 Jul 2025 15:18:40 +0300 Subject: [PATCH 1/3] [1.2] Use 1.2 timer by default Issue: ICQ79V Change-Id: I92e233a536640626b2ec022f74b29eb6c4b52b21 Signed-off-by: ivagin --- .../ets/runtime/ets_libbase_runtime.yaml | 10 ++ static_core/plugins/ets/runtime/ets_vm.cpp | 2 + .../ets/runtime/interop_js/code_scopes-inl.h | 2 + .../ets/runtime/intrinsics/std_core.cpp | 5 + static_core/plugins/ets/runtime_options.yaml | 4 + .../plugins/ets/stdlib/escompat/Global.ets | 4 +- .../std/debug/concurrency/CoroutineExtras.ets | 2 + .../interop_js/tests/timer/CMakeLists.txt | 120 +++++++----------- .../interop_js/tests/timer/run_timer_test.js | 13 +- .../interop_js/tests/timer/timer_tests.ets | 61 ++++++--- .../runtime/coroutines/coroutine_manager.h | 7 + .../runtime/coroutines/coroutine_worker.cpp | 12 +- .../runtime/coroutines/coroutine_worker.h | 2 + .../coroutines/stackful_coroutine_manager.cpp | 1 + .../coroutines/stackful_coroutine_manager.h | 5 + 15 files changed, 143 insertions(+), 107 deletions(-) diff --git a/static_core/plugins/ets/runtime/ets_libbase_runtime.yaml b/static_core/plugins/ets/runtime/ets_libbase_runtime.yaml index fbdf4c442e..a63b51168f 100644 --- a/static_core/plugins/ets/runtime/ets_libbase_runtime.yaml +++ b/static_core/plugins/ets/runtime/ets_libbase_runtime.yaml @@ -5820,6 +5820,16 @@ intrinsics: args: [] impl: ark::ets::intrinsics::taskpool::GetTaskPoolWorkersLimit + - name: StdCoroutineIsExternalTimerEnabled + space: ets + class_name: std.debug.concurrency.CoroutineExtras + method_name: isExternalTimerEnabled + static: true + signature: + ret: u1 + args: [] + impl: ark::ets::intrinsics::StdSystemIsExternalTimerEnabled + #################################### # std.debug.concurrency.AtomicFlag # #################################### diff --git a/static_core/plugins/ets/runtime/ets_vm.cpp b/static_core/plugins/ets/runtime/ets_vm.cpp index 5dd0a54563..f665e50ed6 100644 --- a/static_core/plugins/ets/runtime/ets_vm.cpp +++ b/static_core/plugins/ets/runtime/ets_vm.cpp @@ -146,6 +146,8 @@ Expected PandaEtsVM::Create(Runtime *runtime, const R options.GetCoroutineEWorkersLimit(plugins::LangToRuntimeType(panda_file::SourceLang::ETS)), // enable perf stats options.IsCoroutineDumpStats(plugins::LangToRuntimeType(panda_file::SourceLang::ETS)), + // enable external timer implementation + options.IsCoroutineEnableFeaturesEnableExternalTimer(plugins::LangToRuntimeType(panda_file::SourceLang::ETS))}; // enable taskpool eaworker mode options.GetTaskpoolMode(plugins::LangToRuntimeType(panda_file::SourceLang::ETS)) == ets::intrinsics::taskpool::TASKPOOL_EAWORKER_MODE diff --git a/static_core/plugins/ets/runtime/interop_js/code_scopes-inl.h b/static_core/plugins/ets/runtime/interop_js/code_scopes-inl.h index 5c9f9726e9..7b9bba50d3 100644 --- a/static_core/plugins/ets/runtime/interop_js/code_scopes-inl.h +++ b/static_core/plugins/ets/runtime/interop_js/code_scopes-inl.h @@ -17,6 +17,7 @@ #define PANDA_PLUGINS_ETS_RUNTIME_INTEROP_JS_CODE_SCOPES_INL_H #include "plugins/ets/runtime/interop_js/interop_context.h" +#include "runtime/coroutines/coroutine_worker.h" namespace ark::ets::interop::js { @@ -44,6 +45,7 @@ inline bool CloseInteropCodeScope(EtsCoroutine *coro) auto *ctx = InteropCtx::Current(coro); if constexpr (ETS_TO_JS) { + coro->GetWorker()->TriggerSchedulerExternally(coro); ctx->UpdateInteropStackInfoIfNeeded(); } ctx->CallStack().PopRecord(); diff --git a/static_core/plugins/ets/runtime/intrinsics/std_core.cpp b/static_core/plugins/ets/runtime/intrinsics/std_core.cpp index 55c5f55f24..71a2fd1e83 100644 --- a/static_core/plugins/ets/runtime/intrinsics/std_core.cpp +++ b/static_core/plugins/ets/runtime/intrinsics/std_core.cpp @@ -358,4 +358,9 @@ extern "C" EtsInt EtsEscompatUint8ClampedArrayToUint8Clamped(EtsDouble val) return std::lrint(val); } +extern "C" EtsBoolean StdSystemIsExternalTimerEnabled() +{ + return ark::ets::ToEtsBoolean(EtsCoroutine::GetCurrent()->GetManager()->IsExternalTimerEnabled()); +} + } // namespace ark::ets::intrinsics diff --git a/static_core/plugins/ets/runtime_options.yaml b/static_core/plugins/ets/runtime_options.yaml index a5702c4ef7..c2f11a2ef7 100644 --- a/static_core/plugins/ets/runtime_options.yaml +++ b/static_core/plugins/ets/runtime_options.yaml @@ -215,6 +215,10 @@ options: type: bool default: false description: "migrate awakened coroutines" + - name: enable-external-timer + type: bool + default: false + description: Enable/disable external timer implementation - name: coroutine-dump-stats lang: diff --git a/static_core/plugins/ets/stdlib/escompat/Global.ets b/static_core/plugins/ets/stdlib/escompat/Global.ets index a033d06788..8f27ce0ffa 100644 --- a/static_core/plugins/ets/stdlib/escompat/Global.ets +++ b/static_core/plugins/ets/stdlib/escompat/Global.ets @@ -404,7 +404,7 @@ export function clearInterval(timerId?: int | null): void { function startTimer(func: () => void, delayMs: int, repeat: boolean): int { const realDelayMs = delayMs; - if (CoroutineExtras.workerHasExternalScheduler()) { + if (CoroutineExtras.isExternalTimerEnabled() && CoroutineExtras.workerHasExternalScheduler()) { return startTimerImpl(func as Object, realDelayMs < 0 ? 0 : realDelayMs, repeat); } return registerTimer(func, realDelayMs < 0 ? 0 : realDelayMs, repeat); @@ -412,7 +412,7 @@ function startTimer(func: () => void, delayMs: int, repeat: boolean): int function stopTimer(timerId: int): void { - if (CoroutineExtras.workerHasExternalScheduler()) { + if (CoroutineExtras.isExternalTimerEnabled() && CoroutineExtras.workerHasExternalScheduler()) { stopTimerImpl(timerId); return; } diff --git a/static_core/plugins/ets/stdlib/std/debug/concurrency/CoroutineExtras.ets b/static_core/plugins/ets/stdlib/std/debug/concurrency/CoroutineExtras.ets index 6dd117ee42..176b98acc1 100644 --- a/static_core/plugins/ets/stdlib/std/debug/concurrency/CoroutineExtras.ets +++ b/static_core/plugins/ets/stdlib/std/debug/concurrency/CoroutineExtras.ets @@ -41,6 +41,8 @@ export final class CoroutineExtras { public static native increaseTaskpoolWorkersToN(workersNum: int): void; // checks if worker has external scheduler public static native workerHasExternalScheduler(): boolean; + // is external timer enabled + public static native isExternalTimerEnabled(): boolean; // is taskpool using launch mode public static native isTaskpoolUsingLaunchMode(): boolean; // is taskpool supporting interop diff --git a/static_core/plugins/ets/tests/interop_js/tests/timer/CMakeLists.txt b/static_core/plugins/ets/tests/interop_js/tests/timer/CMakeLists.txt index 99e5d5f92d..460650c0f5 100644 --- a/static_core/plugins/ets/tests/interop_js/tests/timer/CMakeLists.txt +++ b/static_core/plugins/ets/tests/interop_js/tests/timer/CMakeLists.txt @@ -11,80 +11,46 @@ # See the License for the specific language governing permissions and # limitations under the License. -panda_ets_interop_js_test(ets_interop_js_test_settimeout - ETS_SOURCES ${CMAKE_CURRENT_LIST_DIR}/timer_tests.ets - JS_LAUNCHER ${CMAKE_CURRENT_LIST_DIR}/run_timer_test.js - LAUNCHER_ARGS "testSetTimeout" -) - -panda_ets_interop_js_test(ets_interop_js_test_cleartimeout - ETS_SOURCES ${CMAKE_CURRENT_LIST_DIR}/timer_tests.ets - JS_LAUNCHER ${CMAKE_CURRENT_LIST_DIR}/run_timer_test.js - LAUNCHER_ARGS "testClearTimeout" -) - -panda_ets_interop_js_test(ets_interop_js_test_setinterval - ETS_SOURCES ${CMAKE_CURRENT_LIST_DIR}/timer_tests.ets - JS_LAUNCHER ${CMAKE_CURRENT_LIST_DIR}/run_timer_test.js - LAUNCHER_ARGS "testSetInterval" -) - -panda_ets_interop_js_test(ets_interop_js_test_settimeout_execute_order - ETS_SOURCES ${CMAKE_CURRENT_LIST_DIR}/timer_tests.ets - JS_LAUNCHER ${CMAKE_CURRENT_LIST_DIR}/run_timer_test.js - LAUNCHER_ARGS "testSetTimeoutExecuteOrder" -) - -panda_ets_interop_js_test(ets_interop_js_test_settimeout_invoke_callback_with_specified_parameter - ETS_SOURCES ${CMAKE_CURRENT_LIST_DIR}/timer_tests.ets - JS_LAUNCHER ${CMAKE_CURRENT_LIST_DIR}/run_timer_test.js - LAUNCHER_ARGS "testSetTimeoutInvokeCallbackWithSpecifiedParameter" -) - -panda_ets_interop_js_test(ets_interop_js_test_settimeout_invoke_callback_with_4_specified_parameters - ETS_SOURCES ${CMAKE_CURRENT_LIST_DIR}/timer_tests.ets - JS_LAUNCHER ${CMAKE_CURRENT_LIST_DIR}/run_timer_test.js - LAUNCHER_ARGS "testSetTimeoutInvokeCallbackWith4SpecifiedParameters" -) - -panda_ets_interop_js_test(ets_interop_js_test_setinterval_invoke_callback_with_specified_parameter - ETS_SOURCES ${CMAKE_CURRENT_LIST_DIR}/timer_tests.ets - JS_LAUNCHER ${CMAKE_CURRENT_LIST_DIR}/run_timer_test.js - LAUNCHER_ARGS "testSetIntervalInvokeCallbacWithSpecifiedParameter" -) - -panda_ets_interop_js_test(ets_interop_js_test_setinterval_invoke_callback_with_4_specified_parameters - ETS_SOURCES ${CMAKE_CURRENT_LIST_DIR}/timer_tests.ets - JS_LAUNCHER ${CMAKE_CURRENT_LIST_DIR}/run_timer_test.js - LAUNCHER_ARGS "testSetIntervalInvokeCallbacWith4SpecifiedParameters" -) - -panda_ets_interop_js_test(ets_interop_js_test_settimeout_with_negitive_delay - ETS_SOURCES ${CMAKE_CURRENT_LIST_DIR}/timer_tests.ets - JS_LAUNCHER ${CMAKE_CURRENT_LIST_DIR}/run_timer_test.js - LAUNCHER_ARGS "testSetTimeoutWithNegitiveDelay" -) - -panda_ets_interop_js_test(ets_interop_js_test_setinterval_with_negitive_delay - ETS_SOURCES ${CMAKE_CURRENT_LIST_DIR}/timer_tests.ets - JS_LAUNCHER ${CMAKE_CURRENT_LIST_DIR}/run_timer_test.js - LAUNCHER_ARGS "testSetIntervalWithNegitiveDelay" -) - -panda_ets_interop_js_test(ets_interop_js_test_settimeout_with_string - ETS_SOURCES ${CMAKE_CURRENT_LIST_DIR}/timer_tests.ets - JS_LAUNCHER ${CMAKE_CURRENT_LIST_DIR}/run_timer_test.js - LAUNCHER_ARGS "testSetTimeoutWithString" -) - -panda_ets_interop_js_test(ets_interop_js_test_setinterval_with_string - ETS_SOURCES ${CMAKE_CURRENT_LIST_DIR}/timer_tests.ets - JS_LAUNCHER ${CMAKE_CURRENT_LIST_DIR}/run_timer_test.js - LAUNCHER_ARGS "testSetIntervalWithString" -) - -panda_ets_interop_js_test(ets_interop_js_test_clear_timer_cross_worker - ETS_SOURCES ${CMAKE_CURRENT_LIST_DIR}/timer_tests.ets - JS_LAUNCHER ${CMAKE_CURRENT_LIST_DIR}/run_timer_test.js - LAUNCHER_ARGS "testClearTimerCrossWorker" -) +function(panda_ets_interop_js_test_timer) + list(GET ARGV 0 TEST_NAME) + list(GET ARGV 1 ENTRY_FUNC) + + panda_ets_interop_js_test(${TEST_NAME}_internal_timer + ETS_SOURCES ${CMAKE_CURRENT_LIST_DIR}/timer_tests.ets + JS_LAUNCHER ${CMAKE_CURRENT_LIST_DIR}/run_timer_test.js + LAUNCHER_ARGS ${ENTRY_FUNC} "false" + ) + + panda_ets_interop_js_test(${TEST_NAME}_external_timer + ETS_SOURCES ${CMAKE_CURRENT_LIST_DIR}/timer_tests.ets + JS_LAUNCHER ${CMAKE_CURRENT_LIST_DIR}/run_timer_test.js + LAUNCHER_ARGS ${ENTRY_FUNC} "true" + ) + +endfunction() + +panda_ets_interop_js_test_timer(ets_interop_js_test_settimeout "testSetTimeout") + +panda_ets_interop_js_test_timer(ets_interop_js_test_cleartimeout "testClearTimeout") + +panda_ets_interop_js_test_timer(ets_interop_js_test_setinterval "testSetInterval") + +panda_ets_interop_js_test_timer(ets_interop_js_test_settimeout_execute_order "testSetTimeoutExecuteOrder") + +panda_ets_interop_js_test_timer(ets_interop_js_test_settimeout_invoke_callback_with_specified_parameter "testSetTimeoutInvokeCallbackWithSpecifiedParameter") + +panda_ets_interop_js_test_timer(ets_interop_js_test_settimeout_invoke_callback_with_4_specified_parameters "testSetTimeoutInvokeCallbackWith4SpecifiedParameters") + +panda_ets_interop_js_test_timer(ets_interop_js_test_setinterval_invoke_callback_with_specified_parameter "testSetIntervalInvokeCallbacWithSpecifiedParameter") + +panda_ets_interop_js_test_timer(ets_interop_js_test_setinterval_invoke_callback_with_4_specified_parameters "testSetIntervalInvokeCallbacWith4SpecifiedParameters") + +panda_ets_interop_js_test_timer(ets_interop_js_test_settimeout_with_negitive_delay "testSetTimeoutWithNegitiveDelay") + +panda_ets_interop_js_test_timer(ets_interop_js_test_setinterval_with_negitive_delay "testSetIntervalWithNegitiveDelay") + +panda_ets_interop_js_test_timer(ets_interop_js_test_settimeout_with_string "testSetTimeoutWithString") + +panda_ets_interop_js_test_timer(ets_interop_js_test_setinterval_with_string "testSetIntervalWithString") + +panda_ets_interop_js_test_timer(ets_interop_js_test_clear_timer_cross_worker "testClearTimerCrossWorker") diff --git a/static_core/plugins/ets/tests/interop_js/tests/timer/run_timer_test.js b/static_core/plugins/ets/tests/interop_js/tests/timer/run_timer_test.js index 9358af10cb..026ae465e1 100644 --- a/static_core/plugins/ets/tests/interop_js/tests/timer/run_timer_test.js +++ b/static_core/plugins/ets/tests/interop_js/tests/timer/run_timer_test.js @@ -15,7 +15,7 @@ const helper = requireNapiPreview('libinterop_test_helper.so', false); -function runTest(test) { +function runTest(test, isExternalTimer) { print('Running test ' + test); const gtestAbcPath = helper.getEnvironmentVar('ARK_ETS_INTEROP_JS_GTEST_ABC_PATH'); const stdlibPath = helper.getEnvironmentVar('ARK_ETS_STDLIB_PATH'); @@ -30,19 +30,22 @@ function runTest(test) { 'panda-files': gtestAbcPath, 'boot-panda-files': `${stdlibPath}:${gtestAbcPath}`, }; + if (isExternalTimer) { + etsOpts['coroutine-enable-features:enable-external-timer'] = 'true' + } if (!etsVm.createRuntime(etsOpts)) { throw Error('Cannot create ETS runtime'); } const runTestImpl = etsVm.getFunction(globalName, test); - runTestImpl(); + runTestImpl(isExternalTimer); let counter = 0; const maxCounter = 5; const checkDelay = 1000; let tId = 0; let checkCallback = () => { ++counter; - if (counter === maxCounter) { + if (counter > maxCounter) { throw new Error('Test failed: timeout.'); } const check = etsVm.getFunction(globalName, 'check'); @@ -55,7 +58,7 @@ function runTest(test) { } let args = helper.getArgv(); -if (args.length !== 6) { +if (args.length < 6 || args.length > 7) { throw Error('Expected test name'); } -runTest(args[5]); +runTest(args[5], args[6] === 'true'); diff --git a/static_core/plugins/ets/tests/interop_js/tests/timer/timer_tests.ets b/static_core/plugins/ets/tests/interop_js/tests/timer/timer_tests.ets index 5d646da690..a78839b644 100644 --- a/static_core/plugins/ets/tests/interop_js/tests/timer/timer_tests.ets +++ b/static_core/plugins/ets/tests/interop_js/tests/timer/timer_tests.ets @@ -20,7 +20,7 @@ class Test { } check(): boolean { - if (this.result == Test.RESULT_FAILED) { + if (this.result == Test.RESULT_FAILED || this.result == Test.RESULT_UNSET) { return false; } if (this.sequence.length != this.numCheckpoints) { @@ -43,6 +43,11 @@ class Test { console.log(message); } + setPassed(): void { + arktest.assertEQ(this.result, Test.RESULT_UNSET) + this.result = Test.RESULT_PASSED; + } + checkpoint(value: int) { this.sequence.push(value); } @@ -63,7 +68,7 @@ function check(): boolean { let globalTest: Test | null = null; -function testSetTimeout(): void { +function testSetTimeout(isExternalTimer: boolean): void { globalTest = new Test(3); let sequence = new Array(); let delay = 100; @@ -79,11 +84,12 @@ function testSetTimeout(): void { globalTest!.fail("The callback is called after " + spentTime + "ms. Expected to be called after " + delay + "ms at least."); } globalTest!.checkpoint(2); + globalTest!.setPassed(); }, delay); globalTest!.checkpoint(1); } -function testClearTimeout(): void { +function testClearTimeout(isExternalTimer: boolean): void { globalTest = new Test(2); let sequence = new Array(); globalTest!.checkpoint(0); @@ -92,9 +98,10 @@ function testClearTimeout(): void { }, 0); clearTimeout(timerId); globalTest!.checkpoint(1); + globalTest!.setPassed(); } -function testSetInterval(): void { +function testSetInterval(isExternalTimer: boolean): void { globalTest = new Test(6); let sequence = new Array(); let delay = 100; @@ -113,6 +120,7 @@ function testSetInterval(): void { } if (checkpoint == 5) { clearInterval(timerId); + globalTest!.setPassed(); } globalTest!.checkpoint(checkpoint); ++checkpoint; @@ -120,7 +128,7 @@ function testSetInterval(): void { globalTest!.checkpoint(1); } -function testSetTimeoutExecuteOrder() : void { +function testSetTimeoutExecuteOrder(isExternalTimer: boolean) : void { globalTest = new Test(6); globalTest!.checkpoint(0); setTimeout((): void => { @@ -136,11 +144,12 @@ function testSetTimeoutExecuteOrder() : void { }, 0); setTimeout((): void => { globalTest!.checkpoint(5); + globalTest!.setPassed(); }, 0); globalTest!.checkpoint(1); } -function testSetTimeoutInvokeCallbackWithSpecifiedParameter(): void { +function testSetTimeoutInvokeCallbackWithSpecifiedParameter(isExternalTimer: boolean): void { globalTest = new Test(3); let delay = 100; globalTest!.checkpoint(0); @@ -159,11 +168,12 @@ function testSetTimeoutInvokeCallbackWithSpecifiedParameter(): void { globalTest!.fail("The parameter passed to the callback is " + p + ". Expected to be " + para + "."); } globalTest!.checkpoint(2); + globalTest!.setPassed(); }, delay, para); globalTest!.checkpoint(1); } -function testSetTimeoutInvokeCallbackWith4SpecifiedParameters(): void { +function testSetTimeoutInvokeCallbackWith4SpecifiedParameters(isExternalTimer: boolean): void { globalTest = new Test(3); let delay = 100; globalTest!.checkpoint(0); @@ -195,11 +205,12 @@ function testSetTimeoutInvokeCallbackWith4SpecifiedParameters(): void { globalTest!.fail("The parameter passed to the callback is " + p4 + ". Expected to be " + para4 + "."); } globalTest!.checkpoint(2); + globalTest!.setPassed(); }, delay, para1, para2, para3, para4); globalTest!.checkpoint(1); } -function testSetIntervalInvokeCallbacWithSpecifiedParameter(): void { +function testSetIntervalInvokeCallbacWithSpecifiedParameter(isExternalTimer: boolean): void { globalTest = new Test(6); let sequence = new Array(); let delay = 100; @@ -222,6 +233,7 @@ function testSetIntervalInvokeCallbacWithSpecifiedParameter(): void { } if (checkpoint == 5) { clearInterval(timerId); + globalTest!.setPassed(); } globalTest!.checkpoint(checkpoint); ++checkpoint; @@ -229,7 +241,7 @@ function testSetIntervalInvokeCallbacWithSpecifiedParameter(): void { globalTest!.checkpoint(1); } -function testSetIntervalInvokeCallbacWith4SpecifiedParameters(): void { +function testSetIntervalInvokeCallbacWith4SpecifiedParameters(isExternalTimer: boolean): void { globalTest = new Test(6); let sequence = new Array(); let delay = 100; @@ -264,6 +276,7 @@ function testSetIntervalInvokeCallbacWith4SpecifiedParameters(): void { } if (checkpoint == 5) { clearInterval(timerId); + globalTest!.setPassed(); } globalTest!.checkpoint(checkpoint); ++checkpoint; @@ -271,7 +284,7 @@ function testSetIntervalInvokeCallbacWith4SpecifiedParameters(): void { globalTest!.checkpoint(1); } -function testSetTimeoutWithNegitiveDelay(): void { +function testSetTimeoutWithNegitiveDelay(isExternalTimer: boolean): void { globalTest = new Test(3); let sequence = new Array(); let delay = -10; @@ -287,11 +300,12 @@ function testSetTimeoutWithNegitiveDelay(): void { globalTest!.fail("The callback is called after " + spentTime + "ms. Expected to be called after 0 ms at least."); } globalTest!.checkpoint(2); + globalTest!.setPassed(); }, delay); globalTest!.checkpoint(1); } -function testSetIntervalWithNegitiveDelay(): void { +function testSetIntervalWithNegitiveDelay(isExternalTimer: boolean): void { globalTest = new Test(6); let sequence = new Array(); let delay = -10; @@ -310,6 +324,7 @@ function testSetIntervalWithNegitiveDelay(): void { } if (checkpoint == 5) { clearInterval(timerId); + globalTest!.setPassed(); } globalTest!.checkpoint(checkpoint); ++checkpoint; @@ -317,15 +332,16 @@ function testSetIntervalWithNegitiveDelay(): void { globalTest!.checkpoint(1); } -function testSetTimeoutWithString(): void { +function testSetTimeoutWithString(isExternalTimer: boolean): void { globalTest = new Test(2); let delay = 100; globalTest!.checkpoint(0); setTimeout("test", delay); globalTest!.checkpoint(1); + globalTest!.setPassed(); } -function testSetIntervalWithString(): void { +function testSetIntervalWithString(isExternalTimer: boolean): void { globalTest = new Test(2); let delay = 100; globalTest!.checkpoint(0); @@ -336,13 +352,14 @@ function testSetIntervalWithString(): void { if (checkpoint == 5) { clearInterval(id1); clearInterval(id2); + globalTest!.setPassed(); } checkpoint ++ ; }, delay) globalTest!.checkpoint(1); } -function testClearTimerCrossWorker() +function testClearTimerCrossWorker(isExternalTimer: boolean) { globalTest = new Test(0); CoroutineExtras.setSchedulingPolicy(CoroutineExtras.POLICY_NON_MAIN); @@ -362,14 +379,20 @@ function testClearTimerCrossWorker() /// Clear main timer from worker { - let triedToClear = false; - let id = setTimeout(() => { arktest.assertTrue(triedToClear); }, delay); + let isCleared = new AtomicFlag(false); + let errorThrown = false; + let id = setTimeout(() => { arktest.assertTrue(isCleared.get()); }, delay); try { - launch void>(clearTimeout, id).Await(); + launch void>((): void => { + isCleared.set(true); + clearTimeout(id); + }).Await(); } catch (e) { arktest.assertEQ(e.toString(), "Error: Failed to clear timer. Unable to clear interop timer from non-interop worker"); - triedToClear = true; + errorThrown = true; } - arktest.assertTrue(triedToClear); + arktest.assertEQ(isExternalTimer, errorThrown); + arktest.assertTrue(isCleared.get()); } + globalTest!.setPassed(); } diff --git a/static_core/runtime/coroutines/coroutine_manager.h b/static_core/runtime/coroutines/coroutine_manager.h index 66bcd850f8..4d4c4a89be 100644 --- a/static_core/runtime/coroutines/coroutine_manager.h +++ b/static_core/runtime/coroutines/coroutine_manager.h @@ -41,6 +41,8 @@ struct CoroutineManagerConfig { uint32_t exclusiveWorkersLimit = 0; /// Collection of performance statistics bool enablePerfStats = false; + /// Enable external timer implementation + bool enableExternalTimer = false; // number of exclusive workers created for runtime needs uint32_t preallocatedExclusiveWorkersCount = 0; }; @@ -320,6 +322,11 @@ public: virtual void PreZygoteFork() = 0; /// Called after Zygote fork to reinitialize and restart worker threads. virtual void PostZygoteFork() = 0; + /// NOTE(ivagin): all config-related stuff should be moved to some special class member + virtual bool IsExternalTimerEnabled() + { + return false; + }; protected: using EntrypointInfo = Coroutine::EntrypointInfo; diff --git a/static_core/runtime/coroutines/coroutine_worker.cpp b/static_core/runtime/coroutines/coroutine_worker.cpp index 76959a383a..367619c5f1 100644 --- a/static_core/runtime/coroutines/coroutine_worker.cpp +++ b/static_core/runtime/coroutines/coroutine_worker.cpp @@ -13,16 +13,15 @@ * limitations under the License. */ -#include "coroutines/coroutine.h" #include "coroutines/coroutine_manager.h" #include "coroutines/coroutine_worker.h" namespace ark { -void CoroutineWorker::OnCoroBecameActive(Coroutine *co) +void CoroutineWorker::TriggerSchedulerExternally(Coroutine *requester) { - if (co->GetType() == Coroutine::Type::MUTATOR && IsExternalSchedulingEnabled()) { - auto *coroMan = co->GetManager(); + if (requester->GetType() == Coroutine::Type::MUTATOR && IsExternalSchedulingEnabled()) { + auto *coroMan = requester->GetManager(); // Note: Currently passing lambda to PostExternalCallback will cause a crash in Arm32 // detail infomation can be seen at #24085 #ifdef PANDA_TARGET_ARM32 @@ -34,4 +33,9 @@ void CoroutineWorker::OnCoroBecameActive(Coroutine *co) } } +void CoroutineWorker::OnCoroBecameActive(Coroutine *co) +{ + TriggerSchedulerExternally(co); +} + } // namespace ark diff --git a/static_core/runtime/coroutines/coroutine_worker.h b/static_core/runtime/coroutines/coroutine_worker.h index b5eb22e647..b30efbe845 100644 --- a/static_core/runtime/coroutines/coroutine_worker.h +++ b/static_core/runtime/coroutines/coroutine_worker.h @@ -117,6 +117,8 @@ public: void OnCoroBecameActive(Coroutine *co); + void TriggerSchedulerExternally(Coroutine *requester); + private: Runtime *runtime_ = nullptr; PandaVM *vm_ = nullptr; diff --git a/static_core/runtime/coroutines/stackful_coroutine_manager.cpp b/static_core/runtime/coroutines/stackful_coroutine_manager.cpp index 0603504966..b2faed553d 100644 --- a/static_core/runtime/coroutines/stackful_coroutine_manager.cpp +++ b/static_core/runtime/coroutines/stackful_coroutine_manager.cpp @@ -247,6 +247,7 @@ void StackfulCoroutineManager::Initialize(CoroutineManagerConfig config, Runtime enableDrainQueueIface_ = config.enableDrainQueueIface; enableMigration_ = config.enableMigration; migrateAwakenedCoros_ = config.migrateAwakenedCoros; + externalTimerEnabled_ = config.enableExternalTimer; // set limits coroStackSizeBytes_ = Runtime::GetCurrent()->GetOptions().GetCoroutineStackSizePages() * os::mem::GetPageSize(); diff --git a/static_core/runtime/coroutines/stackful_coroutine_manager.h b/static_core/runtime/coroutines/stackful_coroutine_manager.h index 4e702de8b2..21e4c75825 100644 --- a/static_core/runtime/coroutines/stackful_coroutine_manager.h +++ b/static_core/runtime/coroutines/stackful_coroutine_manager.h @@ -123,6 +123,10 @@ public: { return migrateAwakenedCoros_; } + bool IsExternalTimerEnabled() override + { + return externalTimerEnabled_; + } /* profiling tools */ CoroutineStats &GetPerfStats() @@ -313,6 +317,7 @@ private: // coroutine migration feature bool enableMigration_ = false; bool migrateAwakenedCoros_ = false; + bool externalTimerEnabled_ = false; // the number of migration triggers std::atomic_uint32_t migrateCount_ = 0; -- Gitee From 38e06903840039de0368f08e5e2ba98fbcecf331 Mon Sep 17 00:00:00 2001 From: Sarychev Konstantin Date: Mon, 28 Jul 2025 14:59:12 +0300 Subject: [PATCH 2/3] Fix eaworker async event handling Issue:[Bug]:#ICPD0M Testing:'ninja all tests', 'ark.py x64.debug hybrid hybrid_tests Change-Id: I81c27f0724532245dd9881facede1c2edfae7dfe Signed-off-by: Sarychev Konstantin --- static_core/irtoc/lang/options.rb | 4 +- static_core/plugins/ets/runtime/ets_vm.h | 16 +++--- .../runtime/interop_js/event_loop_module.cpp | 53 ++++++++++++++++--- .../runtime/interop_js/event_loop_module.h | 18 +++++-- .../runtime/interop_js/interop_context.cpp | 8 ++- .../runtime/interop_js/timer_helper/timer.cpp | 7 +-- .../runtime/interop_js/timer_helper/timer.h | 3 -- .../ets/runtime/interop_js/timer_module.cpp | 5 +- .../intrinsics/std_core_ExclusiveLauncher.cpp | 28 ++++------ .../tests/interop_js/eacoro/eaworker_test.ets | 45 ++++++++++++++++ .../tests/interop_js/eacoro/eaworker_test.js | 5 ++ .../coroutines/stackful_coroutine_worker.cpp | 32 +++++------ .../include/external_callback_poster.h | 5 +- static_core/runtime/include/panda_vm.h | 10 ++++ static_core/tools/ark_js_napi_cli/BUILD.gn | 2 - .../ark_js_napi_cli/ark_hz/ark_js_runtime.cpp | 8 --- 16 files changed, 174 insertions(+), 75 deletions(-) diff --git a/static_core/irtoc/lang/options.rb b/static_core/irtoc/lang/options.rb index d9d0f5356b..ac59ac3515 100755 --- a/static_core/irtoc/lang/options.rb +++ b/static_core/irtoc/lang/options.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -# Copyright (c) 2021-2024 Huawei Device Co., Ltd. +# Copyright (c) 2021-2025 Huawei Device Co., Ltd. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -39,7 +39,7 @@ class << Options end.parse! if Options.working_dir - Dir.mkdir(Options.working_dir) unless File.exists?(Options.working_dir) + Dir.mkdir(Options.working_dir) unless File.exist?(Options.working_dir) Dir.chdir(Options.working_dir) end diff --git a/static_core/plugins/ets/runtime/ets_vm.h b/static_core/plugins/ets/runtime/ets_vm.h index ac559a5fe6..5fa8353921 100644 --- a/static_core/plugins/ets/runtime/ets_vm.h +++ b/static_core/plugins/ets/runtime/ets_vm.h @@ -78,8 +78,6 @@ class EtsFinalizableWeakRef; using WalkEventLoopCallback = std::function; -enum class EventLoopRunMode : int { RUN_DEFAULT = 0, RUN_ONCE, RUN_NOWAIT }; - class PandaEtsVM final : public PandaVM, public EtsVM, public ani_vm { // NOLINT(fuchsia-multiple-inheritance) public: static Expected Create(Runtime *runtime, const RuntimeOptions &options); @@ -356,15 +354,15 @@ public: } /// @brief Method creates CallBackPoster using factory. - PandaUniquePtr CreateCallbackPoster() + PandaUniquePtr CreateCallbackPoster(CallbackPoster::DestroyCallback onDestroy = nullptr) { if (callbackPosterFactory_ == nullptr) { return nullptr; } - return callbackPosterFactory_->CreatePoster(); + return callbackPosterFactory_->CreatePoster(std::move(onDestroy)); } - using RunEventLoopFunction = std::function; + using RunEventLoopFunction = std::function; void SetRunEventLoopFunction(RunEventLoopFunction &&cb) { @@ -372,11 +370,13 @@ public: runEventLoop_ = std::move(cb); } - void RunEventLoop(EventLoopRunMode mode) + bool RunEventLoop(ark::EventLoopRunMode mode) override { - if (runEventLoop_) { - runEventLoop_(mode); + if (!runEventLoop_) { + return false; } + + return runEventLoop_(mode); } using WalkEventLoopFunction = std::function; diff --git a/static_core/plugins/ets/runtime/interop_js/event_loop_module.cpp b/static_core/plugins/ets/runtime/interop_js/event_loop_module.cpp index 81f1d9d789..4b0599fbd3 100644 --- a/static_core/plugins/ets/runtime/interop_js/event_loop_module.cpp +++ b/static_core/plugins/ets/runtime/interop_js/event_loop_module.cpp @@ -32,6 +32,8 @@ napi_get_uv_event_loop([[maybe_unused]] napi_env env, [[maybe_unused]] struct uv namespace ark::ets::interop::js { +std::atomic_uint32_t EventLoop::eventCount_ {0}; + /*static*/ uv_loop_t *EventLoop::GetEventLoop() { @@ -45,8 +47,12 @@ uv_loop_t *EventLoop::GetEventLoop() return loop; } -void EventLoop::RunEventLoop(EventLoopRunMode mode) +bool EventLoop::RunEventLoop(EventLoopRunMode mode) { + if (ark::ets::interop::js::InteropCtx::Current() == nullptr) { + return false; + } + ark::ets::interop::js::InteropCtx::Current(EtsCoroutine::GetCurrent())->UpdateInteropStackInfoIfNeeded(); auto *loop = GetEventLoop(); switch (mode) { @@ -57,11 +63,16 @@ void EventLoop::RunEventLoop(EventLoopRunMode mode) uv_run(loop, UV_RUN_ONCE); break; case EventLoopRunMode::RUN_NOWAIT: + // Atomic with acquire order reason: to allow event loop processing code see the latest value + if (eventCount_.load(std::memory_order_acquire) == 0) { + return false; + } uv_run(loop, UV_RUN_NOWAIT); break; default: UNREACHABLE(); }; + return true; } void EventLoop::WalkEventLoop(WalkEventLoopCallback &callback, void *args) @@ -82,7 +93,30 @@ void EventLoop::WalkEventLoop(WalkEventLoopCallback &callback, void *args) uv_walk(loop, uvCalback, &parsedArgs); } -EventLoopCallbackPoster::EventLoopCallbackPoster() +uv_timer_t *EventLoop::CreateTimer() +{ + auto *timer = new uv_timer_t(); + // Atomic with release order reason: to allow event loop processing code see the latest value + eventCount_.fetch_add(1, std::memory_order_release); + return timer; +} + +void EventLoop::CloseTimer(uv_timer_t *timer) +{ + uv_timer_stop(timer); + uv_close(reinterpret_cast(timer), [](uv_handle_t *handle) { + delete handle; + // Atomic with release order reason: to allow event loop processing code see the latest value + eventCount_.fetch_sub(1, std::memory_order_release); + }); +} + +EventLoopCallbackPoster::EventLoopCallbackPoster(DestroyCallback onDestroy) : onDestroy_(std::move(onDestroy)) +{ + Init(); +} + +void EventLoopCallbackPoster::Init() { auto loop = EventLoop::GetEventLoop(); // These resources will be deleted in the event loop callback after Runtime destruction, @@ -97,13 +131,16 @@ EventLoopCallbackPoster::EventLoopCallbackPoster() EventLoopCallbackPoster::~EventLoopCallbackPoster() { ASSERT(async_ != nullptr); - auto destroyCb = [async = this->async_]() { + auto destroyCb = [async = this->async_, onDestroy = this->onDestroy_]() { auto deleter = [](uv_handle_t *handle) { auto *poster = reinterpret_cast(handle->data); delete poster; delete handle; }; uv_close(reinterpret_cast(async), deleter); + if (onDestroy) { + onDestroy(); + } }; if (NeedDestroyInPlace()) { destroyCb(); @@ -167,13 +204,13 @@ bool EventLoopCallbackPoster::ThreadSafeCallbackQueue::IsEmpty() return callbackQueue_.empty(); } -PandaUniquePtr EventLoopCallbackPosterFactoryImpl::CreatePoster() +// NOLINTNEXTLINE(google-default-arguments) +PandaUniquePtr EventLoopCallbackPosterFactoryImpl::CreatePoster( + CallbackPoster::DestroyCallback onDestroy) { - auto *coro = Coroutine::GetCurrent(); - ASSERT(coro != nullptr); - [[maybe_unused]] auto *w = coro->GetContext()->GetWorker(); + [[maybe_unused]] auto *w = Coroutine::GetCurrent()->GetContext()->GetWorker(); ASSERT(w->IsMainWorker() || w->InExclusiveMode()); - auto poster = MakePandaUnique(); + auto poster = MakePandaUnique(onDestroy); ASSERT(poster != nullptr); return poster; } diff --git a/static_core/plugins/ets/runtime/interop_js/event_loop_module.h b/static_core/plugins/ets/runtime/interop_js/event_loop_module.h index cdd577adc7..34f8e687cd 100644 --- a/static_core/plugins/ets/runtime/interop_js/event_loop_module.h +++ b/static_core/plugins/ets/runtime/interop_js/event_loop_module.h @@ -51,12 +51,14 @@ class EventLoopCallbackPoster : public CallbackPoster { public: static_assert(PANDA_ETS_INTEROP_JS); - explicit EventLoopCallbackPoster(); + explicit EventLoopCallbackPoster(DestroyCallback onDestroy = nullptr); ~EventLoopCallbackPoster() override; NO_COPY_SEMANTIC(EventLoopCallbackPoster); NO_MOVE_SEMANTIC(EventLoopCallbackPoster); private: + void Init(); + void PostImpl(WrappedCallback &&callback) override; void PostToEventLoop(WrappedCallback &&callback); @@ -64,7 +66,8 @@ private: static void AsyncEventToExecuteCallbacks(uv_async_t *async); uv_async_t *async_ = nullptr; - ThreadSafeCallbackQueue *callbackQueue_; + ThreadSafeCallbackQueue *callbackQueue_ = nullptr; + WrappedCallback onDestroy_; }; class EventLoopCallbackPosterFactoryImpl : public CallbackPosterFactoryIface { @@ -78,16 +81,23 @@ public: * @brief Creates callback poster to perform async work in EventLoop. * NOTE: This method can only be called from threads that have napi_env (e.g. Main, Exclusive Workers). */ - PandaUniquePtr CreatePoster() override; + // NOLINTNEXTLINE(google-default-arguments) + PandaUniquePtr CreatePoster(CallbackPoster::DestroyCallback onDestroy = nullptr) override; }; class EventLoop { public: static uv_loop_t *GetEventLoop(); - static void RunEventLoop(EventLoopRunMode mode = EventLoopRunMode::RUN_DEFAULT); + static bool RunEventLoop(EventLoopRunMode mode = EventLoopRunMode::RUN_DEFAULT); static void WalkEventLoop(WalkEventLoopCallback &callback, void *args); + + static uv_timer_t *CreateTimer(); + + static void CloseTimer(uv_timer_t *timer); + + static std::atomic_uint32_t eventCount_; }; } // namespace ark::ets::interop::js diff --git a/static_core/plugins/ets/runtime/interop_js/interop_context.cpp b/static_core/plugins/ets/runtime/interop_js/interop_context.cpp index 3a233e38bc..ae2be704d6 100644 --- a/static_core/plugins/ets/runtime/interop_js/interop_context.cpp +++ b/static_core/plugins/ets/runtime/interop_js/interop_context.cpp @@ -133,6 +133,12 @@ static bool RegisterTimerModule() ani_env *aniEnv = nullptr; status = vm->GetEnv(ANI_VERSION_1, &aniEnv); ASSERT(status == ANI_OK); +#ifndef PANDA_TARGET_OHOS + auto jsEnv = InteropCtx::Current()->GetJSEnv(); + napi_value global = nullptr; + napi_get_global(jsEnv, &global); + ark::ets::interop::js::helper::Init(jsEnv, global); +#endif return TimerModule::Init(aniEnv); } @@ -142,7 +148,7 @@ static void RegisterEventLoopModule(EtsCoroutine *coro) ASSERT(coro == coro->GetPandaVM()->GetCoroutineManager()->GetMainThread()); coro->GetPandaVM()->CreateCallbackPosterFactory(); coro->GetPandaVM()->SetRunEventLoopFunction( - [](EventLoopRunMode mode) { EventLoop::RunEventLoop(static_cast(mode)); }); + [](EventLoopRunMode mode) { return EventLoop::RunEventLoop(static_cast(mode)); }); coro->GetPandaVM()->SetWalkEventLoopFunction( [](WalkEventLoopCallback &cb, void *args) { EventLoop::WalkEventLoop(cb, args); }); } diff --git a/static_core/plugins/ets/runtime/interop_js/timer_helper/timer.cpp b/static_core/plugins/ets/runtime/interop_js/timer_helper/timer.cpp index ae92373b2a..57ecf5186b 100644 --- a/static_core/plugins/ets/runtime/interop_js/timer_helper/timer.cpp +++ b/static_core/plugins/ets/runtime/interop_js/timer_helper/timer.cpp @@ -19,6 +19,8 @@ #include #include +#include "plugins/ets/runtime/interop_js/event_loop_module.h" + #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wshadow" @@ -43,7 +45,7 @@ static std::unordered_map g_timers; // NOLINTEND(fuchsia-statically-constructed-objects) TimerInfo::TimerInfo(napi_env env, napi_ref cb, std::vector cbArgs, bool repeat) - : env(env), cb(cb), cbArgs(std::move(cbArgs)), repeat(repeat), timer(new uv_timer_t()) + : env(env), cb(cb), cbArgs(std::move(cbArgs)), repeat(repeat), timer(EventLoop::CreateTimer()) { timerId = g_nextTimerId++; g_timers[timerId] = this; @@ -60,8 +62,7 @@ TimerInfo::~TimerInfo() napi_delete_reference(env, ref); } g_timers.erase(timerId); - uv_timer_stop(timer); - uv_close(reinterpret_cast(timer), TIMER_CLOSE_CALLBACK); + EventLoop::CloseTimer(timer); } static napi_value RegisterTimer(napi_env env, napi_ref cb, std::vector cbArgs, int32_t timeout, bool repeat) diff --git a/static_core/plugins/ets/runtime/interop_js/timer_helper/timer.h b/static_core/plugins/ets/runtime/interop_js/timer_helper/timer.h index d20a1d3cdd..f2809954ad 100644 --- a/static_core/plugins/ets/runtime/interop_js/timer_helper/timer.h +++ b/static_core/plugins/ets/runtime/interop_js/timer_helper/timer.h @@ -37,9 +37,6 @@ struct TimerInfo { bool repeat; uv_timer_t *timer; uint32_t timerId; - static constexpr uv_close_cb TIMER_CLOSE_CALLBACK = [](uv_handle_t *handle) { - delete reinterpret_cast(handle); - }; // NOLINTEND(misc-non-private-member-variables-in-classes) }; diff --git a/static_core/plugins/ets/runtime/interop_js/timer_module.cpp b/static_core/plugins/ets/runtime/interop_js/timer_module.cpp index e0c8f38b35..3e4ee1f2fb 100644 --- a/static_core/plugins/ets/runtime/interop_js/timer_module.cpp +++ b/static_core/plugins/ets/runtime/interop_js/timer_module.cpp @@ -102,7 +102,7 @@ ets_int TimerModule::StartTimer(ani_env *env, ani_object funcObject, ani_int del { ASSERT(ark::ets::interop::js::InteropCtx::Current() != nullptr); - auto *timer = new uv_timer_t(); + auto *timer = ark::ets::interop::js::EventLoop::CreateTimer(); auto timerTable = GetTimerTable(env); ani_ref timerInfoRef {}; @@ -194,8 +194,7 @@ void TimerModule::RepeatTimer(uv_timer_t *timer, uint64_t timerId) void TimerModule::DisarmTimer(uv_timer_t *timer) { - uv_timer_stop(timer); - uv_close(reinterpret_cast(timer), [](uv_handle_t *handle) { delete handle; }); + ark::ets::interop::js::EventLoop::CloseTimer(timer); } ani_object TimerModule::GetTimerTable(ani_env *env) diff --git a/static_core/plugins/ets/runtime/intrinsics/std_core_ExclusiveLauncher.cpp b/static_core/plugins/ets/runtime/intrinsics/std_core_ExclusiveLauncher.cpp index c3fd8f93f8..b399607b8e 100644 --- a/static_core/plugins/ets/runtime/intrinsics/std_core_ExclusiveLauncher.cpp +++ b/static_core/plugins/ets/runtime/intrinsics/std_core_ExclusiveLauncher.cpp @@ -31,6 +31,10 @@ namespace ark::ets::intrinsics { +enum TaskPosterState : uint64_t { NOT_DESTROYED, DESTROYED }; + +thread_local static TaskPosterState g_posterState = NOT_DESTROYED; + static thread_local EtsInt g_currentEAWorkerNum = 0; static std::atomic g_eaworkerCount = 1; static constexpr EtsInt INVALID_WORKER_ID = -1; @@ -113,22 +117,9 @@ void RunTaskOnEACoroutine(PandaEtsVM *etsVM, bool needInterop, mem::Reference *t auto poster = etsVM->CreateCallbackPoster(); ASSERT(poster != nullptr); poster->Post(RunExclusiveTask, taskRef, refStorage); - // 2 NativeEngine async_t and 1 async_t for each of the two instances of CallbackPoster - // CC-OFFNXT(G.NAM.03-CPP) project code style - static constexpr uint32_t MANUALLY_HANDLED_ASYNC_COUNT = 4U; - WalkEventLoopCallback cntHandles = []([[maybe_unused]] void *handle, void *arg) { - auto *cnt = reinterpret_cast(arg); - (*cnt)++; - }; - // CC-OFFNXT(G.CTL.03) implementation feature - while (true) { - // NOTE(ksarychev, #25367): change to handle corner cases - etsVM->RunEventLoop(EventLoopRunMode::RUN_ONCE); - uint32_t handleCount = 0; - etsVM->WalkEventLoop(cntHandles, &handleCount); - if (handleCount <= MANUALLY_HANDLED_ASYNC_COUNT) { - break; - } + + while (g_posterState != TaskPosterState::DESTROYED) { + etsVM->RunEventLoop(ark::EventLoopRunMode::RUN_ONCE); } } else { RunExclusiveTask(taskRef, refStorage); @@ -205,7 +196,10 @@ int64_t TaskPosterCreate() { auto *coro = EtsCoroutine::GetCurrent(); ASSERT(coro != nullptr); - auto poster = coro->GetPandaVM()->CreateCallbackPoster(); + + std::function onDestroy = []() { g_posterState = DESTROYED; }; + + auto poster = coro->GetPandaVM()->CreateCallbackPoster(onDestroy); ASSERT(poster != nullptr); return reinterpret_cast(poster.release()); } diff --git a/static_core/plugins/ets/tests/interop_js/eacoro/eaworker_test.ets b/static_core/plugins/ets/tests/interop_js/eacoro/eaworker_test.ets index 47cbcf5258..0ce3e701d0 100644 --- a/static_core/plugins/ets/tests/interop_js/eacoro/eaworker_test.ets +++ b/static_core/plugins/ets/tests/interop_js/eacoro/eaworker_test.ets @@ -109,3 +109,48 @@ function CreateEAWorkerWithoutInterop(): void { } arktest.assertEQ(res, true); } + +async function asyncCall(task: () => void) { + task(); +} + +function RunSetTimeoutTest(): void { + let job = new CompletableJob(); + + let w = new EAWorker(true); + w.start(); + let p = w.run(()=>{ + let p1 = asyncCall(() => {}); + + let p2 = p1.then((value: NullishType) => { + setTimeout(() => { + job.finish(); + }, 100); + + }); + }); + w.join(); + + job.Await(); +} + +function RunNewCoroInSetTimeoutTest(): void { + let job = new CompletableJob(); + + let w = new EAWorker(true); + w.start(); + let p = w.run(()=>{ + let p1 = asyncCall(() => { + }); + let p2 = p1.then((value: NullishType) => { + setTimeout(() => { + let p3 = asyncCall(()=>{ + job.finish(); + }); + }, 100); + }); + }); + w.join(); + + job.Await(); +} diff --git a/static_core/plugins/ets/tests/interop_js/eacoro/eaworker_test.js b/static_core/plugins/ets/tests/interop_js/eacoro/eaworker_test.js index 74672068c0..99a0887364 100644 --- a/static_core/plugins/ets/tests/interop_js/eacoro/eaworker_test.js +++ b/static_core/plugins/ets/tests/interop_js/eacoro/eaworker_test.js @@ -35,6 +35,11 @@ function runTest() { RunTasksWithJsCallTest(); let CreateEAWorkerWithoutInterop = etsVm.getFunction('Leaworker_test/ETSGLOBAL;', 'CreateEAWorkerWithoutInterop'); CreateEAWorkerWithoutInterop(); + + let RunSetTimeoutTest = etsVm.getFunction('Leaworker_test/ETSGLOBAL;', 'RunSetTimeoutTest'); + RunSetTimeoutTest(); + let RunNewCoroInSetTimeoutTest = etsVm.getFunction('Leaworker_test/ETSGLOBAL;', 'RunNewCoroInSetTimeoutTest'); + RunNewCoroInSetTimeoutTest(); } runTest(); diff --git a/static_core/runtime/coroutines/stackful_coroutine_worker.cpp b/static_core/runtime/coroutines/stackful_coroutine_worker.cpp index f0fcdb3f40..9d4db2112c 100644 --- a/static_core/runtime/coroutines/stackful_coroutine_worker.cpp +++ b/static_core/runtime/coroutines/stackful_coroutine_worker.cpp @@ -189,22 +189,24 @@ void StackfulCoroutineWorker::CompleteAllAffinedCoroutines() // CC-OFFNXT(G.FMT.04-CPP): project code style auto unlock = [](auto &&...locks) { ([&]() NO_THREAD_SAFETY_ANALYSIS { locks.Unlock(); }(), ...); }; - // CC-OFFNXT(G.CTL.03): false positive - while (true) { - lock(waitersLock_, runnablesLock_); - if (runnables_.Size() > 1) { - unlock(waitersLock_, runnablesLock_); - coroManager_->Schedule(); - } else if (!waiters_.empty()) { - workerCompletionEvent_.SetNotHappened(); - workerCompletionEvent_.Lock(); - unlock(waitersLock_, runnablesLock_); - coroManager_->Await(&workerCompletionEvent_); - } else { - unlock(waitersLock_, runnablesLock_); - break; + do { + // CC-OFFNXT(G.CTL.03): false positive + while (true) { + lock(waitersLock_, runnablesLock_); + if (runnables_.Size() > 1) { + unlock(waitersLock_, runnablesLock_); + coroManager_->Schedule(); + } else if (!waiters_.empty()) { + workerCompletionEvent_.SetNotHappened(); + workerCompletionEvent_.Lock(); + unlock(waitersLock_, runnablesLock_); + coroManager_->Await(&workerCompletionEvent_); + } else { + unlock(waitersLock_, runnablesLock_); + break; + } } - } + } while (GetPandaVM()->RunEventLoop(ark::EventLoopRunMode::RUN_NOWAIT)); } void StackfulCoroutineWorker::DisableCoroutineSwitch() diff --git a/static_core/runtime/include/external_callback_poster.h b/static_core/runtime/include/external_callback_poster.h index 73f7e331b9..24004b5178 100644 --- a/static_core/runtime/include/external_callback_poster.h +++ b/static_core/runtime/include/external_callback_poster.h @@ -29,6 +29,9 @@ class Coroutine; class CallbackPoster { public: CallbackPoster() = default; + // NOLINTNEXTLINE(performance-unnecessary-value-param) + using DestroyCallback = std::function; + explicit CallbackPoster([[maybe_unused]] DestroyCallback onDestroy) {}; virtual ~CallbackPoster() = default; NO_COPY_SEMANTIC(CallbackPoster); NO_MOVE_SEMANTIC(CallbackPoster); @@ -69,7 +72,7 @@ public: NO_COPY_SEMANTIC(CallbackPosterFactoryIface); NO_MOVE_SEMANTIC(CallbackPosterFactoryIface); - virtual PandaUniquePtr CreatePoster() = 0; + virtual PandaUniquePtr CreatePoster(CallbackPoster::DestroyCallback onDestroy) = 0; }; } // namespace ark diff --git a/static_core/runtime/include/panda_vm.h b/static_core/runtime/include/panda_vm.h index 85272d5409..ce34d61847 100644 --- a/static_core/runtime/include/panda_vm.h +++ b/static_core/runtime/include/panda_vm.h @@ -46,6 +46,8 @@ class ReferenceProcessor; enum class PandaVMType : size_t { ECMA_VM }; // Deprecated. Only for Compability with js_runtime. +enum class EventLoopRunMode : int { RUN_DEFAULT = 0, RUN_ONCE, RUN_NOWAIT }; + class PandaVM { public: static PandaVM *Create(Runtime *runtime, const RuntimeOptions &options, std::string_view runtimeType); @@ -245,6 +247,14 @@ public: virtual void FreeInternalResources(); + // Current runtime implementation requires event loop support + // in a core non-language-specific runtime component, + // particularly StackfulCoroutineManager. + virtual bool RunEventLoop([[maybe_unused]] EventLoopRunMode mode) + { + return false; + } + NO_MOVE_SEMANTIC(PandaVM); NO_COPY_SEMANTIC(PandaVM); diff --git a/static_core/tools/ark_js_napi_cli/BUILD.gn b/static_core/tools/ark_js_napi_cli/BUILD.gn index 781a310970..4a134c9bed 100755 --- a/static_core/tools/ark_js_napi_cli/BUILD.gn +++ b/static_core/tools/ark_js_napi_cli/BUILD.gn @@ -17,8 +17,6 @@ if (!ark_standalone_build) { } ark_js_napi_cli_sources = [ - "$ark_root/static_core/plugins/ets/runtime/interop_js/timer_helper/timer.cpp", - "$ark_root/static_core/plugins/ets/runtime/interop_js/timer_helper/interop_timer_helper.cpp", "ark_hz/ark_js_runtime.cpp", "js_runtime.cpp", "main.cpp", diff --git a/static_core/tools/ark_js_napi_cli/ark_hz/ark_js_runtime.cpp b/static_core/tools/ark_js_napi_cli/ark_hz/ark_js_runtime.cpp index ca86b0e27d..7c4182863f 100644 --- a/static_core/tools/ark_js_napi_cli/ark_hz/ark_js_runtime.cpp +++ b/static_core/tools/ark_js_napi_cli/ark_hz/ark_js_runtime.cpp @@ -71,11 +71,6 @@ bool ArkJsRuntime::ProcessOptions(int argc, const char **argv, arg_list_t *filen return true; } -static napi_value JsValueFromLocalValue(panda::Local local) -{ - return reinterpret_cast(*local); -} - bool ArkJsRuntime::Init() { if (vm_ != nullptr) { @@ -93,9 +88,6 @@ bool ArkJsRuntime::Init() engine_->SetGetAssetFunc(utils::GetAsset); engine_->SetCleanEnv([this] { JSNApi::DestroyJSVM(vm_); }); - auto *engine = GetNativeEngine(); - Local global = panda::JSNApi::GetGlobalObject(vm_); - ark::ets::interop::js::helper::Init(reinterpret_cast(engine), JsValueFromLocalValue(global)); return true; } -- Gitee From 0cedfb81ce3e8b362d70997cc093e361ec1aabf4 Mon Sep 17 00:00:00 2001 From: Panferov Ivan Date: Wed, 20 Aug 2025 20:15:05 +0800 Subject: [PATCH 3/3] add recursive ext-scheduling Issue: #ICX6X7 Signed-off-by: Panferov Ivan --- static_core/plugins/ets/runtime/ets_vm.cpp | 14 ++++--- .../runtime/interop_js/event_loop_module.cpp | 38 +++++++++++++++++++ .../runtime/interop_js/event_loop_module.h | 18 +++++++++ .../runtime/interop_js/interop_context.cpp | 21 ++++++++-- .../runtime/coroutines/coroutine_worker.cpp | 13 ++----- .../runtime/coroutines/coroutine_worker.h | 22 ++++------- .../include/external_callback_poster.h | 15 ++++++-- 7 files changed, 105 insertions(+), 36 deletions(-) diff --git a/static_core/plugins/ets/runtime/ets_vm.cpp b/static_core/plugins/ets/runtime/ets_vm.cpp index f665e50ed6..edfbc8e5f7 100644 --- a/static_core/plugins/ets/runtime/ets_vm.cpp +++ b/static_core/plugins/ets/runtime/ets_vm.cpp @@ -133,6 +133,11 @@ Expected PandaEtsVM::Create(Runtime *runtime, const R u_setDataDirectory(icuPath.c_str()); } + auto workersPoolNum = options.GetTaskpoolMode(plugins::LangToRuntimeType(panda_file::SourceLang::ETS)) == + ets::intrinsics::taskpool::TASKPOOL_EAWORKER_MODE + ? ets::intrinsics::taskpool::TASKPOOL_EAWORKER_INIT_NUM + : 0; + CoroutineManagerConfig cfg { // enable drain queue interface options.IsCoroutineEnableFeaturesAniDrainQueue(plugins::LangToRuntimeType(panda_file::SourceLang::ETS)), @@ -147,12 +152,9 @@ Expected PandaEtsVM::Create(Runtime *runtime, const R // enable perf stats options.IsCoroutineDumpStats(plugins::LangToRuntimeType(panda_file::SourceLang::ETS)), // enable external timer implementation - options.IsCoroutineEnableFeaturesEnableExternalTimer(plugins::LangToRuntimeType(panda_file::SourceLang::ETS))}; - // enable taskpool eaworker mode - options.GetTaskpoolMode(plugins::LangToRuntimeType(panda_file::SourceLang::ETS)) == - ets::intrinsics::taskpool::TASKPOOL_EAWORKER_MODE - ? ets::intrinsics::taskpool::TASKPOOL_EAWORKER_INIT_NUM - : 0}; + options.IsCoroutineEnableFeaturesEnableExternalTimer(plugins::LangToRuntimeType(panda_file::SourceLang::ETS)), + // number of reserved workers for taskpool + workersPoolNum}; vm->coroutineManager_->Initialize(cfg, runtime, vm); return vm; diff --git a/static_core/plugins/ets/runtime/interop_js/event_loop_module.cpp b/static_core/plugins/ets/runtime/interop_js/event_loop_module.cpp index 4b0599fbd3..21a9fee267 100644 --- a/static_core/plugins/ets/runtime/interop_js/event_loop_module.cpp +++ b/static_core/plugins/ets/runtime/interop_js/event_loop_module.cpp @@ -171,6 +171,44 @@ void EventLoopCallbackPoster::AsyncEventToExecuteCallbacks(uv_async_t *async) callbackQueue->ExecuteAllCallbacks(); } +SingleEventPoster::SingleEventPoster(WrappedCallback &&callback) : callback_(std::move(callback)) +{ + auto loop = EventLoop::GetEventLoop(); + // These resources will be deleted in the event loop callback after Runtime destruction, + // so we need to use a standard allocator + async_ = new uv_async_t(); + [[maybe_unused]] auto uvstatus = uv_async_init(loop, async_, CallbackExecutor); + ASSERT(uvstatus == 0); + async_->data = this; +} + +SingleEventPoster::~SingleEventPoster() +{ + ASSERT(async_ != nullptr); + if (NeedDestroyInPlace()) { + uv_close(reinterpret_cast(async_), [](auto *handle) { delete handle; }); + return; + } + async_->data = nullptr; + uv_async_send(async_); +} + +/* static */ +void SingleEventPoster::CallbackExecutor(uv_async_t *async) +{ + auto *eventPoster = static_cast(async->data); + if (eventPoster == nullptr) { + uv_close(reinterpret_cast(async), [](auto *handle) { delete handle; }); + return; + } + eventPoster->callback_(); +} + +void SingleEventPoster::PostImpl() +{ + uv_async_send(async_); +} + void EventLoopCallbackPoster::ThreadSafeCallbackQueue::PushCallback(WrappedCallback &&callback, uv_async_t *async) { { diff --git a/static_core/plugins/ets/runtime/interop_js/event_loop_module.h b/static_core/plugins/ets/runtime/interop_js/event_loop_module.h index 34f8e687cd..cbc1b97986 100644 --- a/static_core/plugins/ets/runtime/interop_js/event_loop_module.h +++ b/static_core/plugins/ets/runtime/interop_js/event_loop_module.h @@ -70,6 +70,24 @@ private: WrappedCallback onDestroy_; }; +class SingleEventPoster : public CallbackPoster { +public: + explicit SingleEventPoster(WrappedCallback &&callback); + ~SingleEventPoster() override; + NO_COPY_SEMANTIC(SingleEventPoster); + NO_MOVE_SEMANTIC(SingleEventPoster); + +private: + void PostImpl([[maybe_unused]] WrappedCallback &&callback) override {}; + + void PostImpl() override; + + static void CallbackExecutor(uv_async_t *async); + + uv_async_t *async_ = nullptr; + WrappedCallback callback_; +}; + class EventLoopCallbackPosterFactoryImpl : public CallbackPosterFactoryIface { public: EventLoopCallbackPosterFactoryImpl() = default; diff --git a/static_core/plugins/ets/runtime/interop_js/interop_context.cpp b/static_core/plugins/ets/runtime/interop_js/interop_context.cpp index ae2be704d6..80e5319747 100644 --- a/static_core/plugins/ets/runtime/interop_js/interop_context.cpp +++ b/static_core/plugins/ets/runtime/interop_js/interop_context.cpp @@ -153,6 +153,21 @@ static void RegisterEventLoopModule(EtsCoroutine *coro) [](WalkEventLoopCallback &cb, void *args) { EventLoop::WalkEventLoop(cb, args); }); } +#ifdef PANDA_JS_ETS_HYBRID_MODE +static PandaUniquePtr CreateExtSchedulingPoster() +{ + auto schedulingFunc = [] { + auto *coro = Coroutine::GetCurrent(); + auto *w = coro->GetContext()->GetWorker(); + if (w->GetRunnablesCount(Coroutine::Type::MUTATOR) > 0) { + coro->GetManager()->Schedule(); + } + w->TriggerSchedulerExternally(coro); + }; + return MakePandaUnique(std::move(schedulingFunc)); +} +#endif // PANDA_JS_ETS_HYBRID_MODE + std::atomic_uint32_t ConstStringStorage::qnameBufferSize_ {0U}; void ConstStringStorage::LoadDynamicCallClass(Class *klass) @@ -799,9 +814,9 @@ void InteropCtx::Init(EtsCoroutine *coro, napi_env env) #ifdef PANDA_JS_ETS_HYBRID_MODE Handshake::VmHandshake(env, ctx); XGC::GetInstance()->OnAttach(ctx); - auto workerPoster = coro->GetPandaVM()->CreateCallbackPoster(); - ASSERT(workerPoster != nullptr); - worker->SetCallbackPoster(std::move(workerPoster)); + auto extSchPoster = CreateExtSchedulingPoster(); + ASSERT(extSchPoster != nullptr); + worker->SetCallbackPoster(std::move(extSchPoster)); #endif // PANDA_JS_ETS_HYBRID_MODE } diff --git a/static_core/runtime/coroutines/coroutine_worker.cpp b/static_core/runtime/coroutines/coroutine_worker.cpp index 367619c5f1..4c5eeafe13 100644 --- a/static_core/runtime/coroutines/coroutine_worker.cpp +++ b/static_core/runtime/coroutines/coroutine_worker.cpp @@ -21,15 +21,10 @@ namespace ark { void CoroutineWorker::TriggerSchedulerExternally(Coroutine *requester) { if (requester->GetType() == Coroutine::Type::MUTATOR && IsExternalSchedulingEnabled()) { - auto *coroMan = requester->GetManager(); -// Note: Currently passing lambda to PostExternalCallback will cause a crash in Arm32 -// detail infomation can be seen at #24085 -#ifdef PANDA_TARGET_ARM32 - std::function callback = [coroMan]() { coroMan->Schedule(); }; - PostExternalCallback(std::move(callback)); -#else - PostExternalCallback([coroMan]() { coroMan->Schedule(); }); -#endif + os::memory::LockHolder l(posterLock_); + if (extSchedulingPoster_ != nullptr) { + extSchedulingPoster_->Post(); + } } } diff --git a/static_core/runtime/coroutines/coroutine_worker.h b/static_core/runtime/coroutines/coroutine_worker.h index b30efbe845..9cfae6cf44 100644 --- a/static_core/runtime/coroutines/coroutine_worker.h +++ b/static_core/runtime/coroutines/coroutine_worker.h @@ -55,8 +55,9 @@ public: virtual ~CoroutineWorker() { os::memory::LockHolder l(posterLock_); - if (callbackPoster_ != nullptr) { - callbackPoster_->SetDestroyInPlace(); + if (extSchedulingPoster_ != nullptr) { + extSchedulingPoster_->SetDestroyInPlace(); + extSchedulingPoster_.reset(nullptr); } } @@ -97,22 +98,13 @@ public: void SetCallbackPoster(PandaUniquePtr poster) { - ASSERT(!callbackPoster_); - callbackPoster_ = std::move(poster); + ASSERT(!extSchedulingPoster_); + extSchedulingPoster_ = std::move(poster); } bool IsExternalSchedulingEnabled() const { - return callbackPoster_ != nullptr; - } - - template - void PostExternalCallback(PosterCallback cb) - { - os::memory::LockHolder l(posterLock_); - if (callbackPoster_ != nullptr) { - callbackPoster_->Post(std::move(cb)); - } + return extSchedulingPoster_ != nullptr; } void OnCoroBecameActive(Coroutine *co); @@ -128,7 +120,7 @@ private: LocalStorage localStorage_; // event loop poster os::memory::Mutex posterLock_; - PandaUniquePtr callbackPoster_; + PandaUniquePtr extSchedulingPoster_; }; } // namespace ark diff --git a/static_core/runtime/include/external_callback_poster.h b/static_core/runtime/include/external_callback_poster.h index 24004b5178..b0fa7731a0 100644 --- a/static_core/runtime/include/external_callback_poster.h +++ b/static_core/runtime/include/external_callback_poster.h @@ -13,8 +13,8 @@ * limitations under the License. */ -#ifndef PANDA_PLUGINS_ETS_RUNTIME_ETS_EXTERNAL_CALLBACK_POSTER_H -#define PANDA_PLUGINS_ETS_RUNTIME_ETS_EXTERNAL_CALLBACK_POSTER_H +#ifndef PANDA_RUNTIME_EXTERNAL_CALLBACK_POSTER_H +#define PANDA_RUNTIME_EXTERNAL_CALLBACK_POSTER_H #include @@ -36,6 +36,13 @@ public: NO_COPY_SEMANTIC(CallbackPoster); NO_MOVE_SEMANTIC(CallbackPoster); + template + void Post(Args... args) + { + static_assert(sizeof...(args) == 0); + PostImpl(); + } + template void Post(Callback callback, Args... args) { @@ -61,6 +68,8 @@ protected: virtual void PostImpl(WrappedCallback &&callback) = 0; + virtual void PostImpl() {} + private: bool destroyInPlace_ = false; }; @@ -77,4 +86,4 @@ public: } // namespace ark -#endif // PANDA_PLUGINS_ETS_RUNTIME_ETS_EXTERNAL_CALLBACK_POSTER_H \ No newline at end of file +#endif // PANDA_RUNTIME_EXTERNAL_CALLBACK_POSTER_H -- Gitee