diff --git a/cpp/jni/javet_enums.h b/cpp/jni/javet_enums.h index bf5e797f6..c371a48c8 100644 --- a/cpp/jni/javet_enums.h +++ b/cpp/jni/javet_enums.h @@ -28,8 +28,9 @@ namespace Javet { namespace V8AwaitMode { enum V8AwaitMode { - RunOnce = 0, - RunTillNoMoreTasks = 1, + RunNoWait = 2, + RunOnce = 1, + RunTillNoMoreTasks = 0, }; }; diff --git a/cpp/jni/javet_v8_runtime.cpp b/cpp/jni/javet_v8_runtime.cpp index cb7c86cac..4f5287c42 100644 --- a/cpp/jni/javet_v8_runtime.cpp +++ b/cpp/jni/javet_v8_runtime.cpp @@ -97,6 +97,19 @@ namespace Javet { bool V8Runtime::Await(const Javet::Enums::V8AwaitMode::V8AwaitMode awaitMode) noexcept { bool hasMoreTasks = false; #ifdef ENABLE_NODE + uv_run_mode uvRunMode; + switch (awaitMode) + { + case Javet::Enums::V8AwaitMode::V8AwaitMode::RunOnce: + uvRunMode = UV_RUN_ONCE; + break; + case Javet::Enums::V8AwaitMode::V8AwaitMode::RunNoWait: + uvRunMode = UV_RUN_NOWAIT; + break; + default: + uvRunMode = UV_RUN_DEFAULT; + break; + } do { { // Reduce the locking granularity so that Node.js can respond to requests from other threads. @@ -105,12 +118,12 @@ namespace Javet { V8HandleScope v8HandleScope(v8Isolate); auto v8Context = GetV8LocalContext(); auto v8ContextScope = GetV8ContextScope(v8Context); - uv_run(&uvLoop, UV_RUN_NOWAIT); + uv_run(&uvLoop, uvRunMode); // DrainTasks is thread-safe. v8PlatformPointer->DrainTasks(v8Isolate); } hasMoreTasks = uv_loop_alive(&uvLoop); - if (hasMoreTasks) { + if (uvRunMode == UV_RUN_DEFAULT && hasMoreTasks) { // Sleep a while to give CPU cycles to other threads. std::this_thread::sleep_for(oneMillisecond); } @@ -124,7 +137,7 @@ namespace Javet { node::EmitProcessBeforeExit(nodeEnvironment.get()); hasMoreTasks = uv_loop_alive(&uvLoop); } - } while (awaitMode == Javet::Enums::V8AwaitMode::RunTillNoMoreTasks && hasMoreTasks); + } while (uvRunMode == UV_RUN_DEFAULT && hasMoreTasks); #else // It has to be v8::platform::MessageLoopBehavior::kDoNotWait, otherwise it blockes; v8::platform::PumpMessageLoop(v8PlatformPointer, v8Isolate); diff --git a/docs/release_notes/release_notes_3_1.rst b/docs/release_notes/release_notes_3_1.rst index 5477efec4..d4942c7fe 100644 --- a/docs/release_notes/release_notes_3_1.rst +++ b/docs/release_notes/release_notes_3_1.rst @@ -2,6 +2,12 @@ Release Notes 3.1.x =================== +3.1.2 V8 v12.5 +-------------- + +* Added ``V8AwaitMode.RunNoWait`` +* Fixed unexpected behavior of ``V8AwaitMode.RunOnce`` + 3.1.1 V8 v12.4 -------------- diff --git a/src/main/java/com/caoccao/javet/enums/V8AwaitMode.java b/src/main/java/com/caoccao/javet/enums/V8AwaitMode.java index faf5d2c24..207e79613 100644 --- a/src/main/java/com/caoccao/javet/enums/V8AwaitMode.java +++ b/src/main/java/com/caoccao/javet/enums/V8AwaitMode.java @@ -23,19 +23,27 @@ */ public enum V8AwaitMode { /** - * Run once tells Javet to drain the tasks once and return. + * RunNoWait tells Javet to trigger the task queue execution but do not wait. + * It only works in Node.js mode. + * + * @since 3.1.2 + */ + RunNoWait(2), + /** + * RunOnce tells Javet to drain the tasks once and return. * It only works in Node.js mode. * * @since 2.0.4 */ - RunOnce(0), + RunOnce(1), /** - * Run till no more tasks tells Javet to keep waiting till there are no more tasks. + * RunTillNoMoreTasks tells Javet to keep waiting till there are no more tasks. + * This is the default mode. * It only works in Node.js mode. * * @since 2.0.4 */ - RunTillNoMoreTasks(1); + RunTillNoMoreTasks(0); private final int id; diff --git a/src/test/java/com/caoccao/javet/interop/TestNodeRuntime.java b/src/test/java/com/caoccao/javet/interop/TestNodeRuntime.java index 9c80ebd30..fcd17ff0c 100644 --- a/src/test/java/com/caoccao/javet/interop/TestNodeRuntime.java +++ b/src/test/java/com/caoccao/javet/interop/TestNodeRuntime.java @@ -18,8 +18,10 @@ import com.caoccao.javet.BaseTestJavet; import com.caoccao.javet.enums.JSRuntimeType; +import com.caoccao.javet.enums.V8AwaitMode; import com.caoccao.javet.exceptions.JavetError; import com.caoccao.javet.exceptions.JavetException; +import com.caoccao.javet.interop.converters.JavetProxyConverter; import com.caoccao.javet.interop.options.NodeRuntimeOptions; import com.caoccao.javet.node.modules.NodeModuleAny; import com.caoccao.javet.node.modules.NodeModuleProcess; @@ -35,6 +37,7 @@ import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.text.MessageFormat; +import java.util.concurrent.atomic.AtomicInteger; import static org.junit.jupiter.api.Assertions.*; @@ -82,6 +85,81 @@ protected void internalTest(String fileName, String expectedJsonString) throws J } } + @Test + public void testAwaitRunNoWait() { + try { + nodeRuntime.setConverter(new JavetProxyConverter()); + AtomicInteger counter = new AtomicInteger(); + nodeRuntime.getGlobalObject().set("counter", counter); + nodeRuntime.getExecutor("let timeoutIDs = [];" + + "function a() {\n" + + " counter.incrementAndGet();\n" + + " if (counter.get() < 10) {\n" + + " timeoutIDs.push(setTimeout(a, 1000));\n" + + " }\n" + + "};\n" + + "a();").executeVoid(); + assertTrue(nodeRuntime.await(V8AwaitMode.RunNoWait)); + nodeRuntime.getExecutor("timeoutIDs.forEach(id => clearTimeout(id));").executeVoid(); + assertEquals(1, counter.get()); + nodeRuntime.getGlobalObject().delete("counter"); + } catch (Throwable t) { + fail(t); + } finally { + nodeRuntime.lowMemoryNotification(); + } + } + + @Test + public void testAwaitRunOnce() { + try { + nodeRuntime.setConverter(new JavetProxyConverter()); + AtomicInteger counter = new AtomicInteger(); + nodeRuntime.getGlobalObject().set("counter", counter); + nodeRuntime.getExecutor("let timeoutIDs = [];" + + "function a() {\n" + + " counter.incrementAndGet();\n" + + " if (counter.get() < 10) {\n" + + " timeoutIDs.push(setTimeout(a, 0));\n" + + " }\n" + + "};\n" + + "a();").executeVoid(); + while (counter.get() < 2) { + assertTrue(nodeRuntime.await(V8AwaitMode.RunOnce)); + } + nodeRuntime.getExecutor("timeoutIDs.forEach(id => clearTimeout(id));").executeVoid(); + assertEquals(2, counter.get()); + nodeRuntime.getGlobalObject().delete("counter"); + } catch (Throwable t) { + fail(t); + } finally { + nodeRuntime.lowMemoryNotification(); + } + } + + @Test + public void testAwaitRunTillNoMoreTasks() { + try { + nodeRuntime.setConverter(new JavetProxyConverter()); + AtomicInteger counter = new AtomicInteger(); + nodeRuntime.getGlobalObject().set("counter", counter); + nodeRuntime.getExecutor("function a() {\n" + + " counter.incrementAndGet();\n" + + " if (counter.get() < 10) {\n" + + " setTimeout(a, 0);\n" + + " }\n" + + "};\n" + + "a();").executeVoid(); + assertFalse(nodeRuntime.await(V8AwaitMode.RunTillNoMoreTasks)); + assertEquals(10, counter.get()); + nodeRuntime.getGlobalObject().delete("counter"); + } catch (Throwable t) { + fail(t); + } finally { + nodeRuntime.lowMemoryNotification(); + } + } + @Test public void testConsoleArguments() throws JavetException { NodeRuntimeOptions runtimeOptions = new NodeRuntimeOptions();