From 92230ebd594ddc0fe82821b7ad4867c96c0e3e53 Mon Sep 17 00:00:00 2001 From: Kirill Zhumarin Date: Tue, 30 Jun 2026 00:56:43 +0300 Subject: [PATCH 1/2] fix: deliver poller events with MakeCallback --- src/poller.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/poller.cpp b/src/poller.cpp index 0b046926..0e393472 100644 --- a/src/poller.cpp +++ b/src/poller.cpp @@ -81,13 +81,13 @@ void Poller::onData(uv_poll_t* handle, int status, int events) { if (0 != status) { // fprintf(stdout, "OnData Error status=%s events=%d\n", uv_strerror(status), events); obj->_stop(); // doesn't matter if this errors - obj->callback.Call({Napi::Error::New(env, uv_strerror(status)).Value(), env.Undefined()}); + obj->callback.MakeCallback(obj->Value(), {Napi::Error::New(env, uv_strerror(status)).Value(), env.Undefined()}); } else { // fprintf(stdout, "OnData status=%d events=%d subscribed=%d\n", status, events, obj->events); // remove triggered events from the poll int newEvents = obj->events & ~events; obj->poll(env, newEvents); - obj->callback.Call({env.Null(), Napi::Number::New(env, events)}); + obj->callback.MakeCallback(obj->Value(), {env.Null(), Napi::Number::New(env, events)}); } } From 3fbc715b19b564fa9ccbf2a3c0cbfa8fb77ffbc5 Mon Sep 17 00:00:00 2001 From: DragonWork <97512+DragonWork@users.noreply.github.com> Date: Thu, 10 Sep 2026 02:12:15 +0200 Subject: [PATCH 2/2] fix: preserve native async contexts and scope Windows callbacks --- src/poller.cpp | 10 +++++++--- src/poller.h | 1 + src/serialport_win.cpp | 14 ++++++++------ src/serialport_win.h | 6 ++++-- 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/poller.cpp b/src/poller.cpp index 0e393472..6a86af21 100644 --- a/src/poller.cpp +++ b/src/poller.cpp @@ -2,7 +2,8 @@ #include #include "./poller.h" -Poller::Poller (const Napi::CallbackInfo &info) : Napi::ObjectWrap(info) +Poller::Poller (const Napi::CallbackInfo &info) : Napi::ObjectWrap(info), + async_context(info.Env(), "serialport:Poller") { Napi::Env env = info.Env(); Napi::HandleScope scope(env); @@ -81,13 +82,16 @@ void Poller::onData(uv_poll_t* handle, int status, int events) { if (0 != status) { // fprintf(stdout, "OnData Error status=%s events=%d\n", uv_strerror(status), events); obj->_stop(); // doesn't matter if this errors - obj->callback.MakeCallback(obj->Value(), {Napi::Error::New(env, uv_strerror(status)).Value(), env.Undefined()}); + obj->callback.MakeCallback(env.Global(), + {Napi::Error::New(env, uv_strerror(status)).Value(), env.Undefined()}, obj->async_context); } else { // fprintf(stdout, "OnData status=%d events=%d subscribed=%d\n", status, events, obj->events); // remove triggered events from the poll int newEvents = obj->events & ~events; obj->poll(env, newEvents); - obj->callback.MakeCallback(obj->Value(), {env.Null(), Napi::Number::New(env, events)}); + // uv_poll invokes us outside Node's callback scopes. MakeCallback drains + // nextTick and Promise continuations before the event loop waits again. + obj->callback.MakeCallback(env.Global(), {env.Null(), Napi::Number::New(env, events)}, obj->async_context); } } diff --git a/src/poller.h b/src/poller.h index c15a2e9f..c9cfec22 100644 --- a/src/poller.h +++ b/src/poller.h @@ -17,6 +17,7 @@ class Poller : public Napi::ObjectWrap { int fd; uv_poll_t* poll_handle = nullptr; Napi::FunctionReference callback; + Napi::AsyncContext async_context; bool uv_poll_init_success = false; // can this be read off of poll_handle? diff --git a/src/serialport_win.cpp b/src/serialport_win.cpp index 7e79216a..7c2a177a 100644 --- a/src/serialport_win.cpp +++ b/src/serialport_win.cpp @@ -380,9 +380,9 @@ void EIO_AfterWrite(uv_async_t* req) { v8::Local argv[1]; if (baton->errorString[0]) { - baton->callback.Call({Napi::Error::New(env, baton->errorString).Value()}); + baton->callback.MakeCallback(env.Global(), {Napi::Error::New(env, baton->errorString).Value()}, baton->async_context); } else { - baton->callback.Call({env.Null()}); + baton->callback.MakeCallback(env.Global(), {env.Null()}, baton->async_context); } baton->buffer.Reset(); delete baton; @@ -414,7 +414,7 @@ Napi::Value Write(const Napi::CallbackInfo& info) { return env.Null(); } - WriteBaton* baton = new WriteBaton(); + WriteBaton* baton = new WriteBaton(env); baton->callback = Napi::Persistent(info[2].As()); baton->fd = fd; baton->buffer.Reset(buffer); @@ -552,9 +552,11 @@ void EIO_AfterRead(uv_async_t* req) { uv_close(reinterpret_cast(req), AsyncCloseCallback); if (baton->errorString[0]) { - baton->callback.Call({Napi::Error::New(env, baton->errorString).Value(), env.Undefined()}); + baton->callback.MakeCallback(env.Global(), + {Napi::Error::New(env, baton->errorString).Value(), env.Undefined()}, baton->async_context); } else { - baton->callback.Call({env.Null(), Napi::Number::New(env, static_cast(baton->bytesRead))}); + baton->callback.MakeCallback(env.Global(), + {env.Null(), Napi::Number::New(env, static_cast(baton->bytesRead))}, baton->async_context); } delete baton; } @@ -600,7 +602,7 @@ Napi::Value Read(const Napi::CallbackInfo& info) { Napi::TypeError::New(env, "Fifth argument must be a function").ThrowAsJavaScriptException(); return env.Null(); } - ReadBaton* baton = new ReadBaton(); + ReadBaton* baton = new ReadBaton(env); baton->callback = Napi::Persistent(info[4].As()); baton->fd = fd; baton->offset = offset; diff --git a/src/serialport_win.h b/src/serialport_win.h index b5357373..53dc9120 100644 --- a/src/serialport_win.h +++ b/src/serialport_win.h @@ -13,7 +13,7 @@ static inline HANDLE int2handle(int ptr) { } struct WriteBaton { - WriteBaton() : bufferData(), errorString() {} + explicit WriteBaton(Napi::Env env) : bufferData(), async_context(env, "serialport:Write"), errorString() {} int fd = 0; char* bufferData = nullptr; size_t bufferLength = 0; @@ -23,6 +23,7 @@ struct WriteBaton { bool complete = false; Napi::ObjectReference buffer; Napi::FunctionReference callback; + Napi::AsyncContext async_context; int result = 0; char errorString[ERROR_STRING_SIZE]; }; @@ -30,7 +31,7 @@ struct WriteBaton { Napi::Value Write(const Napi::CallbackInfo& info); struct ReadBaton { - ReadBaton() : errorString() {} + explicit ReadBaton(Napi::Env env) : async_context(env, "serialport:Read"), errorString() {} int fd = 0; char* bufferData = nullptr; size_t bufferLength = 0; @@ -39,6 +40,7 @@ struct ReadBaton { size_t offset = 0; void* hThread = nullptr; Napi::FunctionReference callback; + Napi::AsyncContext async_context; bool complete = false; char errorString[ERROR_STRING_SIZE]; };