Skip to content

Commit

Permalink
✨ feat: Enhance V8 await mode
Browse files Browse the repository at this point in the history
* Add `V8AwaitMode.RunNoWait`
* Fix unexpected behavior of `V8AwaitMode.RunOnce`
  • Loading branch information
caoccao committed Apr 28, 2024
1 parent b10c185 commit 9c9e021
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 9 deletions.
5 changes: 3 additions & 2 deletions cpp/jni/javet_enums.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ namespace Javet {

namespace V8AwaitMode {
enum V8AwaitMode {
RunOnce = 0,
RunTillNoMoreTasks = 1,
RunNoWait = 2,
RunOnce = 1,
RunTillNoMoreTasks = 0,
};
};

Expand Down
19 changes: 16 additions & 3 deletions cpp/jni/javet_v8_runtime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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);
}
Expand All @@ -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);
Expand Down
6 changes: 6 additions & 0 deletions docs/release_notes/release_notes_3_1.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
--------------

Expand Down
16 changes: 12 additions & 4 deletions src/main/java/com/caoccao/javet/enums/V8AwaitMode.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
78 changes: 78 additions & 0 deletions src/test/java/com/caoccao/javet/interop/TestNodeRuntime.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.*;

Expand Down Expand Up @@ -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();
Expand Down

0 comments on commit 9c9e021

Please sign in to comment.