◐ Shell
clean mode source ↗

fix(timers): order timers with the Java MessageQueue instead of ALooper fds by edusperoni · Pull Request #1967 · NativeScript/android

added 3 commits

June 11, 2026 10:55
…kResource

Chrome DevTools no longer fetches external source maps itself when
debugging remote targets: it issues Network.loadNetworkResource to the
target and reads the result back through IO.read/IO.close. None of these
embedder-side CDP domains are implemented by V8's inspector, so external
source maps failed and apps had to fall back to bloated
inline-source-map builds.

- Handle Network.loadNetworkResource natively: resolve the URL back to a
  file on disk and reply with a stream handle (success:false +
  net::ERR_FILE_NOT_FOUND when missing).
- Implement IO.read (1MB base64 chunks; eof only on a final empty read,
  since the frontend discards data accompanying eof) and IO.close.
- Reply with a JSON-RPC error for unsupported schemes (e.g. https) so
  DevTools keeps its existing fallback of fetching from the host.
- Rewrite sourceMapURL in outgoing Debugger.scriptParsed /
  Debugger.scriptFailedToParse events from relative/file:// URLs to a
  custom nsruntime:// scheme. DevTools hard-excludes file:, data: and
  devtools: URLs from loading through the target, so without the rewrite
  it would never send Network.loadNetworkResource and instead try (and
  fail) to read device files from the host machine. data: and http(s)
  URLs are left untouched, keeping inline source maps working.
- Allow opting out via nativescript.config.ts:
  android.disableSourceMapURLRewrite (or the same key at the top level).
- Serve these messages on the websocket read thread (new native
  handleMessageOnSocketThread), since the main-thread queue is
  unavailable exactly when DevTools needs source maps: the pause loop
  bypasses dispatchMessage and a busy isolate never drains the queue.
  The handler is V8-free and returns the response for Java to send on
  the receiving socket.
- Make Debugger.pause interrupt busy JS via Isolate::RequestInterrupt,
  skipped while already paused in the nested loop to avoid a spurious
  re-pause after resume.
- Vendor nlohmann/json v3.12.0 (third_party/json.hpp, header-only) for
  CDP message handling outside V8.

Ports NativeScript/ios#385 and NativeScript/ios#378 to Android.

Refs: nodejs/node#58077
…er fds

Timers were delivered through a pipe fd registered with ALooper_addFd.
Android services fd callbacks inside MessageQueue.nativePollOnce at every
message boundary, so timer callbacks and Handler messages lived in two
queues with no mutual ordering: a setTimeout(0) could fire before or
interleave around an already-queued Handler.postDelayed(0) runnable.

Timers now ride the Java MessageQueue itself via a dedicated per-runtime
TimerHandler bound to the isolate's Looper:

- Each scheduled timer enqueues one message with sendMessageAtTime, so
  timers share a single queue with Handler.post/postDelayed and fire in
  exact MessageQueue order.
- Messages are anonymous "due tokens": a native list sorted by exact
  (sub-millisecond) due time picks the earliest due timer per token,
  preserving the previous relative ordering of JS timers despite the
  millisecond-quantized Java queue.
- Due-now timers post at (long)now so they tie (FIFO) with a
  postDelayed(0) made in the same millisecond; future timers post at
  ceil(dueTime) so they never fire early.
- The background watcher thread, pipe, mutex and condition variables are
  removed; the MessageQueue does all delayed scheduling and everything
  runs on the isolate's thread with no locking. Worker isolates get the
  same behavior on their own loopers.

Also adds ordering regression tests (timers vs Handler posts), scheduled
from a java-posted runnable to avoid the >=5-level nesting clamp that
jasmine's spec chaining otherwise triggers.

Fixes timer/Handler ordering so setTimeout(0) reliably yields behind
already-queued main-thread work.

coderabbitai[bot]

This was referenced

Jun 11, 2026