GeezerVison is showing github.com — refactor(runtime): a Runtime owns one isolate and publishes it from Init by edusperoni · Pull Request #513 · NativeScript/ios
Conversation
edusperoni
commented
Oct 13, 2026
•
edited by coderabbitai
Bot
Loading
Uh oh!
There was an error while loading. Please reload this page.
Supersedes #512, which found the race this fixes. Thanks @adrian-niculescu.
Problem
Setting up a runtime was assembled by hand at each call site:
- Main (
NativeScript.mm): new Runtime(), CreateIsolate(), Locker, Init(isolate).
- Worker (
Worker.mm): the same, plus WatchHeapLimit before Init(isolate, true) and SetWorkerId after it.
That split caused several problems:
- Startup race.
CreateIsolate added the isolate to the live registry (isolates_) before Init wrote its Caches slot. Runtime::NotifyMemoryPressure (feat(runtime): report native memory held by wrappers to V8, forward memory pressure #507) runs on a dispatch queue and reads that slot for every registered isolate, so a memory-pressure event during startup could read it while it was being written. On a miss it also built a stand-in Caches off-thread. fix(runtime): publish an isolate's Caches under the registry lock #512 fixed this by writing the slot under the registry lock; this PR removes the window instead.
- Wrong worker flag.
SetWorkerId ran after Init, so Runtime::IsWorker() returned false during a worker's Init. That includes worker-scoped class naming in ClassBuilder and the main-isolate-only checks in ns:runtime.
- Leaked isolates.
CreateIsolate could be called more than once on the same Runtime. And a Runtime deleted before Init left its isolate registered and never disposed, because isolate_ was only set at the end of Init.
Change
Runtime(const RuntimeOptions&) creates the isolate on the calling thread, which becomes its home thread. The isolate limits and the worker id are passed in up front, so IsWorker() is correct from the start.
- The caller still takes the Locker and can work with the isolate before it's ready; the worker arms
WatchHeapLimit there. It then calls Init().
Init() adds the isolate to the registry only after writing its Caches slot. Teardown already removes it from the registry before clearing the slots. So any thread that finds an isolate in the registry may read its slots while holding the registry lock. This rule is documented on isolates_, and the memory-pressure handler relies on it unchanged.
- A
Runtime deleted before Init disposes its isolate, which no other thread has seen yet.
CreateIsolate and SetWorkerId are removed. Runtime.h is a project header, not part of the public framework headers.
Not covered
A reader that finds the isolate in the registry, releases the lock, and only then reads the slot can still race teardown's Caches::Remove. The pre-Locker IsolateWrapper::IsValid() in ArgConverter::MethodCallback does this. That's a separate fix: read the slot only under the isolate's Locker.
Tests
No new test: the startup overlap depends on timing (see #512 for a sleep-based repro). The full suite under ASan on a dedicated simulator gives 1835 specs, 0 failures, and no sanitizer reports.
Summary by CodeRabbit
- Refactor
- Runtime setup now applies resource limits and worker identification from the outset. Initialization and cleanup follow a consistent lifecycle for both standard and worker runtimes.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
adrian-niculescu reacted with thumbs up emoji
coderabbitai
Bot
commented
Oct 13, 2026
•
edited
Loading
Uh oh!
There was an error while loading. Please reload this page.
|

No actionable comments were generated in the recent review. 🎉
ℹ️ Recent review info
-
⚙️ Run configuration
-
- Configuration used: Repository UI
- Review profile: CHILL
- Plan: Advanced
- Run ID:
62eb19cd-6ee9-40c3-b0d7-d687688c2f83
📥 Commits
-
Reviewing files that changed from the base of the PR and between 66ac3ce and ca7dda6.
📒 Files selected for processing: 4
-
NativeScript/NativeScript.mm
NativeScript/runtime/Runtime.h
NativeScript/runtime/Runtime.mm
NativeScript/runtime/Worker.mm
Included review availability: This review used your included allowance. Your plan provides up to 2 included reviews per hour; 0 remain after this review.
📝 Walkthrough
-
Runtime now creates and configures isolates in the constructor using RuntimeOptions. Init() initializes caches and registers the isolate. Application and worker setup retrieve the isolate from Runtime and call Init() without arguments.
Changes: Runtime Isolate Lifecycle
| Layer / File(s) |
Summary |
Configure and create the isolate NativeScript/runtime/Runtime.h, NativeScript/runtime/Runtime.mm |
RuntimeOptions carries isolate limits and a worker ID. The constructor creates and configures the isolate. The destructor directly disposes of the isolate if initialization did not complete. |
Initialize and publish the isolate NativeScript/runtime/Runtime.h, NativeScript/runtime/Runtime.mm, NativeScript/NativeScript.mm, NativeScript/runtime/Worker.mm |
Init() initializes caches, registers the isolate, and marks the runtime initialized. Application and worker setup retrieve the isolate from Runtime and call Init() without arguments. Worker setup passes its resource limits and ID to the constructor. |
Priority: ➖ Normal
Estimated code review effort: 3 (Moderate) | ~20 minutes
Change: Bug fix
Suggested reviewers: nathanwalker
Merge Risk: ⚪ Minimal · up to ca7dd
-
The inspected lifecycle ordering addresses the memory-pressure startup race. No issue remains that should block merging after normal checks.
Pre-merge checks: 4 · 1
-
Failed checks: 1 warning
| Check name |
Status |
Explanation |
Resolution |
| Docstring Coverage |
 |
Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 3 functions across 1 files. (3 skipped: 3 … |
Write docstrings for the functions missing them to satisfy the coverage threshold. |
Passed checks: 4 passed
-
| Check name |
Status |
Explanation |
| Linked Issues check |
 |
Check skipped because no linked issues were found for this pull request. |
| Out of Scope Changes check |
 |
Check skipped because no linked issues were found for this pull request. |
| Description Check |
 |
Check skipped - CodeRabbit’s high-level summary is enabled. |
| Title check |
 |
The title clearly describes the main runtime change: Runtime creates and owns one isolate, then publishes it during Init. |
Full details: Docstring Coverage
-
Explanation
Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 3 functions across 1 files. (3 skipped: 3 unsupported.)
- Fix all pre-merge checks with AI
✨ Finishing Touches: 💡 1
-
📝 Generate docstrings: 💡
-
- Commit to this branch
- Create a new PR
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
❤️ Share
-
A rabbit checks the runtime’s start, With limits tucked beside its heart. The isolate wakes, then joins the line,
Init() sets the caches in time. Worker IDs arrive in tow, And off the little runtimes go.
Comment @coderabbitai help to get the list of available commands.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Setting up a runtime took CreateIsolate, Init and, for workers, SetWorkerId,
assembled by hand at each call site. The isolate joined the live registry in
CreateIsolate, before Init wrote its Caches slot, so a thread that found it
there (the memory-pressure handler, #512) could read that slot while it was
being written. CreateIsolate could also be called twice on one Runtime, and a
Runtime deleted before Init left its isolate registered and never disposed.
- Runtime(const RuntimeOptions&) creates the isolate, homed on the calling
thread, and takes the limits and worker id up front, so IsWorker() is
correct during a worker's Init. The caller still locks the isolate and can
set it up (WatchHeapLimit) before calling Init().
- Init() publishes the isolate to the registry only after writing its Caches
slot. With the teardown already unregistering before clearing it, a thread
that finds an isolate in the registry may read its slots under the lock.
- A Runtime deleted before Init disposes its unpublished isolate.
- CreateIsolate and SetWorkerId are gone.
Reported in #512.
edusperoni
changed the title
fix(runtime): keep isolate ids in the runtime registry
refactor(runtime): a Runtime owns one isolate and publishes it from Init
Oct 13, 2026
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Assignees
No one assigned
Milestone
No milestone
Development
Successfully merging this pull request may close these issues.
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.
|