Skip to content

Commit 35cdabd

Browse files
committed
feat: implement message loop observer for V8's foreground task queue
1 parent 5555935 commit 35cdabd

2 files changed

Lines changed: 36 additions & 7 deletions

File tree

NativeScript/runtime/Runtime.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ class Runtime {
8282
std::unique_ptr<ModuleInternal> moduleInternal_;
8383
int workerId_;
8484
CFRunLoopRef runtimeLoop_;
85+
CFRunLoopObserverRef messageLoopObserver_ = nullptr;
8586
double startTime;
8687
double realtimeOrigin;
8788
// TODO: refactor this. This is only needed because, during program

NativeScript/runtime/Runtime.mm

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,15 @@
2222
#include "DisposerPHV.h"
2323
#include "IsolateWrapper.h"
2424

25+
#include <mutex>
2526
#include <unordered_map>
27+
#include "DevFlags.h"
28+
#include "HMRSupport.h"
2629
#include "ModuleBinding.hpp"
2730
#include "ModuleInternalCallbacks.h"
2831
#include "URLImpl.h"
2932
#include "URLPatternImpl.h"
3033
#include "URLSearchParamsImpl.h"
31-
#include <mutex>
32-
#include "HMRSupport.h"
33-
#include "DevFlags.h"
3434

3535
#define STRINGIZE(x) #x
3636
#define STRINGIZE_VALUE_OF(x) STRINGIZE(x)
@@ -128,7 +128,7 @@ static void InitializeImportMetaObject(Local<Context> context, Local<Module> mod
128128
std::atomic<int> Runtime::nextIsolateId{0};
129129
SimpleAllocator allocator_;
130130
NSDictionary* AppPackageJson = nil;
131-
static std::unordered_map<std::string, id> AppConfigCache; // generic cache for app config values
131+
static std::unordered_map<std::string, id> AppConfigCache; // generic cache for app config values
132132
static std::mutex AppConfigCacheMutex;
133133

134134
// Global flag to track when JavaScript errors occur during execution
@@ -185,6 +185,12 @@ void DisposeIsolateWhenPossible(Isolate* isolate) {
185185
}
186186

187187
Runtime::~Runtime() {
188+
if (messageLoopObserver_) {
189+
CFRunLoopObserverInvalidate(messageLoopObserver_);
190+
CFRelease(messageLoopObserver_);
191+
messageLoopObserver_ = nullptr;
192+
}
193+
188194
auto currentIsolate = this->isolate_;
189195
{
190196
// make sure we remove the isolate from the list of active isolates first
@@ -299,8 +305,8 @@ void DisposeIsolateWhenPossible(Isolate* isolate) {
299305
DefineDrainMicrotaskMethod(isolate, globalTemplate);
300306
// queueMicrotask(callback) per spec
301307
{
302-
Local<FunctionTemplate> qmtTemplate = FunctionTemplate::New(
303-
isolate, [](const FunctionCallbackInfo<Value>& info) {
308+
Local<FunctionTemplate> qmtTemplate =
309+
FunctionTemplate::New(isolate, [](const FunctionCallbackInfo<Value>& info) {
304310
auto* isolate = info.GetIsolate();
305311
if (info.Length() < 1 || !info[0]->IsFunction()) {
306312
isolate->ThrowException(Exception::TypeError(
@@ -425,6 +431,27 @@ void DisposeIsolateWhenPossible(Isolate* isolate) {
425431
cache->SetContext(context);
426432

427433
this->isolate_ = isolate;
434+
435+
// Pump V8's foreground task queue on each CFRunLoop iteration.
436+
// FinalizationRegistry cleanup callbacks are posted as foreground tasks by V8
437+
// during GC — without this, they never execute.
438+
CFRunLoopObserverContext obsCtx = {0, this, nullptr, nullptr, nullptr};
439+
messageLoopObserver_ = CFRunLoopObserverCreate(
440+
kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0,
441+
[](CFRunLoopObserverRef observer, CFRunLoopActivity activity, void* info) {
442+
auto* runtime = static_cast<Runtime*>(info);
443+
auto* isolate = runtime->GetIsolate();
444+
if (!IsAlive(isolate)) {
445+
return;
446+
}
447+
v8::Locker locker(isolate);
448+
while (v8::platform::PumpMessageLoop(platform_.get(), isolate,
449+
v8::platform::MessageLoopBehavior::kDoNotWait)) {
450+
continue;
451+
}
452+
},
453+
&obsCtx);
454+
CFRunLoopAddObserver(runtimeLoop_, messageLoopObserver_, kCFRunLoopCommonModes);
428455
}
429456

430457
void Runtime::RunMainScript() {
@@ -486,7 +513,8 @@ void DisposeIsolateWhenPossible(Isolate* isolate) {
486513
result = AppPackageJson[nsKey];
487514
}
488515

489-
// Store in cache (can cache nil as NSNull to differentiate presence if desired; for now, cache as-is)
516+
// Store in cache (can cache nil as NSNull to differentiate presence if desired; for now, cache
517+
// as-is)
490518
{
491519
std::lock_guard<std::mutex> lock(AppConfigCacheMutex);
492520
AppConfigCache[key] = result;

0 commit comments

Comments
 (0)