GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — feat: per-runtime EventLoop - v8 platform tasks + two-lane scheduler (Java MessageQueue / ALooper fd) by edusperoni · Pull Request #2003 · NativeScript/android

feat: per-runtime EventLoop - v8 platform tasks + two-lane scheduler (Java MessageQueue / ALooper fd) #2003

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

Sign up for GitHub

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
edusperoni merged 6 commits into main from feat/v8-platform-event-loop
Aug 13, 2026

Uh oh!

There was an error while loading. Please reload this page.

Merged

feat: per-runtime EventLoop - v8 platform tasks + two-lane scheduler (Java MessageQueue / ALooper fd) #2003

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
6 commits
Select commit Hold shift + click to select a range
0e3de2a
feat: run v8 platform foreground tasks on the runtime looper
edusperoni Aug 11, 2026
79aa489
refactor: two-lane EventLoop scheduler (ordered Java lane, internal f…
edusperoni Aug 11, 2026
8da0d88
fix(event-loop): unit accounting and isolate-reuse hardening from des…
edusperoni Aug 11, 2026
5ca0268
feat(event-loop): merge timers into the ordered lane; route __runOnMa…
edusperoni Aug 11, 2026
c2be640
perf(event-loop): cancellable timer tokens (claim cells + @CriticalNa…
edusperoni Aug 12, 2026
d89d08e
fix(event-loop): claim-gate ABI below API 26 and failure-path rollbac…
edusperoni Aug 13, 2026
File filter
Filter by extension

Clear filters
Conversations
Failed to load comments.
Loading

Uh oh!

There was an error while loading. Please reload this page.

Jump to
Jump to file
Failed to load files.
Loading

Uh oh!

There was an error while loading. Please reload this page.

Diff view
Diff view

There are no files selected for viewing

1 change: 1 addition & 0 deletions 1 test-app/app/src/main/assets/app/mainpage.js
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ shared.runWorkerTests();
shared.runPerformanceTests();
shared.runStructuredCloneTests();
require("./tests/testWebAssembly");
require("./tests/testEventLoop");
require("./tests/testMultithreadedJavascript");
require("./tests/testWorkerTerminateDuringLoad");
require("./tests/testInterfaceDefaultMethods");
Expand Down
3 changes: 3 additions & 0 deletions 3 test-app/app/src/main/assets/app/tests/eventLoopEchoWorker.js
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
onmessage = function (msg) {
postMessage(msg.data);
};
273 changes: 273 additions & 0 deletions 273 test-app/app/src/main/assets/app/tests/testEventLoop.js
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
// V8 delivers these resolutions as platform foreground tasks, so they only
// settle if the runtime pumps its foreground task runner (EventLoopHandler).
describe("event loop foreground tasks", function () {
it("resolves Atomics.waitAsync when notified on the same thread", function (done) {
const sab = new SharedArrayBuffer(4);
const i32 = new Int32Array(sab);

const result = Atomics.waitAsync(i32, 0, 0);
expect(result.async).toBe(true);

result.value.then(value => {
expect(value).toBe("ok");
done();
}).catch(e => {
// jasmine 2.0.1: done has no .fail - record the failure, then complete
expect("resolved").toBe("rejected: " + e);
done();
});

const woken = Atomics.notify(i32, 0);
expect(woken).toBe(1);
});

it("resolves Atomics.waitAsync with 'timed-out' after the timeout", function (done) {
const sab = new SharedArrayBuffer(4);
const i32 = new Int32Array(sab);

const result = Atomics.waitAsync(i32, 0, 0, 50);
expect(result.async).toBe(true);

result.value.then(value => {
expect(value).toBe("timed-out");
done();
}).catch(e => {
// jasmine 2.0.1: done has no .fail - record the failure, then complete
expect("resolved").toBe("rejected: " + e);
done();
});
});

it("resolves Atomics.waitAsync synchronously on value mismatch", function () {
const sab = new SharedArrayBuffer(4);
const i32 = new Int32Array(sab);
i32[0] = 42;

const result = Atomics.waitAsync(i32, 0, 0);
expect(result.async).toBe(false);
expect(result.value).toBe("not-equal");
});

it("keeps ordinary promise chains working alongside foreground tasks", function (done) {
const sab = new SharedArrayBuffer(4);
const i32 = new Int32Array(sab);
const order = [];

Atomics.waitAsync(i32, 0, 0).value.then(() => {
order.push("waitAsync");
return Promise.resolve();
}).then(() => {
order.push("chained");
expect(order).toEqual(["waitAsync", "chained"]);
done();
}).catch(e => {
expect("resolved").toBe("rejected: " + e);
done();
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

Uh oh!

There was an error while loading. Please reload this page.


Atomics.notify(i32, 0);
});
});

// The ordered lane rides the Java MessageQueue, so these callbacks must be
// strict macrotasks: after the current turn's microtasks, FIFO with timers.
describe("event loop ordered macrotasks", function () {
it("__ns__queueMacrotask runs the callback asynchronously", function (done) {
let ran = false;
__ns__queueMacrotask(() => {
ran = true;
done();
});
expect(ran).toBe(false);
});

it("runs after the current turn's microtasks", function (done) {
const order = [];
__ns__queueMacrotask(() => {
order.push("macrotask");
expect(order).toEqual(["microtask", "macrotask"]);
done();
});
Promise.resolve().then(() => order.push("microtask"));
});

// native timers (__ns__*): the app-level `setTimeout` global in this test
// app is an old Handler-based polyfill, not the runtime timers
it("stays FIFO-ordered with native setTimeout(0)", function (done) {
const order = [];
__ns__queueMacrotask(() => order.push("macro1"));
__ns__setTimeout(() => order.push("timeout"), 0);
__ns__queueMacrotask(() => {
order.push("macro2");
expect(order).toEqual(["macro1", "timeout", "macro2"]);
done();
});
});

it("rejects non-function arguments", function () {
expect(() => __ns__queueMacrotask("nope")).toThrowError(TypeError);
expect(() => __ns__queueMacrotask()).toThrowError(TypeError);
});

it("runs on the main thread when posted from a background JS thread", function (done) {
const mainThreadId = java.lang.Thread.currentThread().getId();
new java.lang.Thread(new java.lang.Runnable({
run() {
expect(java.lang.Thread.currentThread().getId()).not.toEqual(mainThreadId);
__ns__queueMacrotask(() => {
expect(java.lang.Thread.currentThread().getId()).toEqual(mainThreadId);
done();
});
}
})).start();
});
});

// clearTimeout leaves a tombstone in the merged ordered domain, so the
// cleared timer's already-queued token consumes its own slot as a no-op
// instead of running a later-scheduled item ahead of Java messages queued
// between the two tokens' positions.
describe("event loop ordered tombstones", function () {
it("cleared timeout's token does not run a later timer ahead of java posts", function (done) {
const order = [];
const handler = new android.os.Handler(android.os.Looper.myLooper());
const t1 = __ns__setTimeout(() => order.push("cleared"), 0);
__ns__clearTimeout(t1);
handler.post(new java.lang.Runnable({
run: () => order.push("java")
}));
__ns__setTimeout(() => {
order.push("t2");
expect(order).toEqual(["java", "t2"]);
done();
}, 0);
});

it("cleared timeout's token does not run a queued macrotask ahead of java posts", function (done) {
const order = [];
const handler = new android.os.Handler(android.os.Looper.myLooper());
const t1 = __ns__setTimeout(() => order.push("cleared"), 0);
__ns__clearTimeout(t1);
handler.post(new java.lang.Runnable({
run: () => order.push("java")
}));
__ns__queueMacrotask(() => {
order.push("macro");
expect(order).toEqual(["java", "macro"]);
done();
});
});
});

// Long (>=32ms) timers carry an identified token whose clear removes the
// queued wakeup; short timers carry a native claim cell whose clear is a
// single CAS. Both must keep exact clear semantics under any thread.
describe("event loop token cancellation", function () {
it("cleared identified (long) timeout never fires and later timers are unaffected", function (done) {
let fired = false;
const t = __ns__setTimeout(() => { fired = true; }, 100);
__ns__clearTimeout(t);
__ns__setTimeout(() => {
expect(fired).toBe(false);
done();
}, 150);
});

it("background-thread clear racing dispatch neither jumps java posts nor ghost-fires", function (done) {
// only iterations whose clear provably ran count toward the quota, so
// the spec can't pass on 30 runs where the thread never raced at all
let remaining = 30;
let attempts = 0;
(function iter() {
if (++attempts > 300) {
expect("background clears raced " + (30 - remaining) + "/30 times")
.toBe("background clears raced 30/30 times");
done();
return;
}
const order = [];
const cleared = new java.util.concurrent.atomic.AtomicBoolean(false);
const handler = new android.os.Handler(android.os.Looper.myLooper());
const t1 = __ns__setTimeout(() => order.push("t1"), 0);
new java.lang.Thread(new java.lang.Runnable({
run() {
__ns__clearTimeout(t1);
cleared.set(true);
}
})).start();
handler.post(new java.lang.Runnable({
run: () => order.push("java")
}));
__ns__setTimeout(() => {
order.push("t2");
const observed = order.join(">");
// t1 either fired before the clear landed (at its own legal
// slot, ahead of "java") or never; t2 must never jump "java"
expect(observed === "java>t2" || observed === "t1>java>t2").toBe(true);
if (cleared.get() && --remaining === 0) {
done();
} else {
iter();
}
}, 5);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

Uh oh!

There was an error while loading. Please reload this page.

})();
});

it("clearing an identified interval stops it", function (done) {
let ticks = 0;
const iv = __ns__setInterval(() => {
ticks++;
if (ticks === 2) {
__ns__clearInterval(iv);
__ns__setTimeout(() => {
expect(ticks).toBe(2);
done();
}, 120);
}
}, 40);
});
});

describe("event loop internal lane", function () {
// Regression for the eventfd unit-accounting bug: a worker reply's wakeup
// arriving while an overdue waitAsync timeout is still unsignaled must not
// be spent on the timeout entry, or the reply starves.
it("delivers worker messages whose wakeup raced an overdue waitAsync timeout", function (done) {
const worker = new Worker("./eventLoopEchoWorker.js");
let warm = false;
worker.onmessage = function (msg) {
if (msg.data === "warmup") {
warm = true;
const i32 = new Int32Array(new SharedArrayBuffer(4));
Atomics.waitAsync(i32, 0, 0, 50);
worker.postMessage("ping");
// block the looper until both the timeout and the reply are
// pending, so their wakeups are serviced from the same poll
const start = Date.now();
while (Date.now() - start < 150) { }
} else {
expect(warm).toBe(true);
expect(msg.data).toBe("ping");
worker.terminate();
done();
}
};
worker.postMessage("warmup");
});

it("keeps event loops healthy across worker churn", function (done) {
let remaining = 8;
(function cycle() {
const worker = new Worker("./eventLoopEchoWorker.js");
worker.onmessage = function () {
worker.terminate();
if (--remaining === 0) {
__ns__queueMacrotask(done);
} else {
cycle();
}
};
worker.postMessage("alive");
})();
});
});
7 changes: 3 additions & 4 deletions 7 test-app/runtime/CMakeLists.txt
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ set(RUNTIME_BUILTIN_JS
${RUNTIME_BUILTIN_JS_DIR}/events.js
${RUNTIME_BUILTIN_JS_DIR}/inspect.js
${RUNTIME_BUILTIN_JS_DIR}/json-helper.js
${RUNTIME_BUILTIN_JS_DIR}/message-loop-timer.js
${RUNTIME_BUILTIN_JS_DIR}/node-util.js
${RUNTIME_BUILTIN_JS_DIR}/ns-util.js
${RUNTIME_BUILTIN_JS_DIR}/performance.js
Expand Down Expand Up @@ -152,8 +151,9 @@ add_library(
src/main/cpp/Constants.cpp
src/main/cpp/DirectBuffer.cpp
src/main/cpp/ErrorEvents.cpp
src/main/cpp/FrameCallbacks.cpp
src/main/cpp/EventLoop.cpp
src/main/cpp/Events.cpp
src/main/cpp/FrameCallbacks.cpp
src/main/cpp/FieldAccessor.cpp
src/main/cpp/File.cpp
src/main/cpp/Interop.cpp
Expand All @@ -166,9 +166,7 @@ add_library(
src/main/cpp/JsArgToArrayConverter.cpp
src/main/cpp/JSONObjectHelper.cpp
src/main/cpp/Logger.cpp
src/main/cpp/LooperTasks.cpp
src/main/cpp/ManualInstrumentation.cpp
src/main/cpp/MessageLoopTimer.cpp
src/main/cpp/MetadataMethodInfo.cpp
src/main/cpp/MetadataNode.cpp
src/main/cpp/MetadataReader.cpp
Expand All @@ -179,6 +177,7 @@ add_library(
src/main/cpp/ModuleInternal.cpp
src/main/cpp/ModuleInternalCallbacks.cpp
src/main/cpp/NativeScriptException.cpp
src/main/cpp/NativeScriptPlatform.cpp
src/main/cpp/NsBuiltinModules.cpp
src/main/cpp/NumericCasts.cpp
src/main/cpp/ObjectManager.cpp
Expand Down
Loading
Oops, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

Add this suggestion to a batch that can be applied as a single commit. This suggestion is invalid because no changes were made to the code. Suggestions cannot be applied while the pull request is closed. Suggestions cannot be applied while viewing a subset of changes. Only one suggestion per line can be applied in a batch. Add this suggestion to a batch that can be applied as a single commit. Applying suggestions on deleted lines is not supported. You must change the existing code in this line in order to create a valid suggestion. Outdated suggestions cannot be applied. This suggestion has been applied or marked resolved. Suggestions cannot be applied from pending reviews. Suggestions cannot be applied on multi-line comments. Suggestions cannot be applied while the pull request is queued to merge. Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.