@@ -82,6 +82,7 @@ void PipeWrap::Initialize(Local<Object> target,
8282 SetProtoMethod (isolate, t, " listen" , Listen);
8383 SetProtoMethod (isolate, t, " connect" , Connect);
8484 SetProtoMethod (isolate, t, " open" , Open);
85+ SetProtoMethod (isolate, t, " watchPeerClose" , WatchPeerClose);
8586
8687#ifdef _WIN32
8788 SetProtoMethod (isolate, t, " setPendingInstances" , SetPendingInstances);
@@ -114,6 +115,7 @@ void PipeWrap::RegisterExternalReferences(ExternalReferenceRegistry* registry) {
114115 registry->Register (Listen);
115116 registry->Register (Connect);
116117 registry->Register (Open);
118+ registry->Register (WatchPeerClose);
117119#ifdef _WIN32
118120 registry->Register (SetPendingInstances);
119121#endif
@@ -231,6 +233,103 @@ void PipeWrap::Open(const FunctionCallbackInfo<Value>& args) {
231233 args.GetReturnValue ().Set (err);
232234}
233235
236+ void PipeWrap::WatchPeerClose (const FunctionCallbackInfo<Value>& args) {
237+ PipeWrap* wrap;
238+ ASSIGN_OR_RETURN_UNWRAP (&wrap, args.This ());
239+
240+ CHECK_GT (args.Length (), 0 );
241+ CHECK (args[0 ]->IsBoolean ());
242+ const bool enable = args[0 ].As <v8::Boolean>()->Value ();
243+ Environment* env = wrap->env ();
244+ Isolate* isolate = env->isolate ();
245+ v8::HandleScope handle_scope (isolate);
246+ v8::Context::Scope context_scope (env->context ());
247+ Local<Object> obj = wrap->object ();
248+
249+ // UnwatchPeerClose
250+ if (!enable) {
251+ if (obj->GetInternalField (kPeerCloseCallbackField )
252+ .As <Value>()
253+ ->IsUndefined ()) {
254+ return ;
255+ }
256+
257+ obj->SetInternalField (kPeerCloseCallbackField , v8::Undefined (isolate));
258+ uv_read_stop (wrap->stream ());
259+ return ;
260+ }
261+
262+ if (!wrap->IsAlive ()) {
263+ return ;
264+ }
265+ if (!obj->GetInternalField (kPeerCloseCallbackField )
266+ .As <Value>()
267+ ->IsUndefined ()) {
268+ return ;
269+ }
270+
271+ CHECK_GT (args.Length (), 1 );
272+ CHECK (args[1 ]->IsFunction ());
273+
274+ // Store the JS callback in an internal field.
275+ obj->SetInternalField (kPeerCloseCallbackField , args[1 ]);
276+
277+ // Start reading to detect EOF/ECONNRESET from the peer.
278+ // We use our custom allocator and reader, ignoring actual data.
279+ int err = uv_read_start (wrap->stream (), PeerCloseAlloc, PeerCloseRead);
280+ if (err != 0 ) {
281+ obj->SetInternalField (kPeerCloseCallbackField , v8::Undefined (isolate));
282+ }
283+ }
284+
285+ void PipeWrap::PeerCloseAlloc (uv_handle_t * handle,
286+ size_t suggested_size,
287+ uv_buf_t * buf) {
288+ // We only care about EOF, not the actual data.
289+ // Using a static 1-byte buffer avoids dynamic memory allocation overhead.
290+ static char scratch;
291+ *buf = uv_buf_init (&scratch, 1 );
292+ }
293+
294+ void PipeWrap::PeerCloseRead (uv_stream_t * stream,
295+ ssize_t nread,
296+ const uv_buf_t * buf) {
297+ PipeWrap* wrap = static_cast <PipeWrap*>(stream->data );
298+ if (wrap == nullptr ) return ;
299+
300+ // Ignore actual data reads or EAGAIN (0). We only watch for disconnects.
301+ if (nread >= 0 ) return ;
302+
303+ // Wait specifically for EOF or connection reset (peer closed).
304+ if (nread != UV_EOF && nread != UV_ECONNRESET ) return ;
305+
306+ // Peer has closed the connection. Stop reading immediately.
307+ uv_read_stop (stream);
308+
309+ Environment* env = wrap->env ();
310+ Isolate* isolate = env->isolate ();
311+
312+ // Set up V8 context and handles to safely execute the JS callback.
313+ v8::HandleScope handle_scope (isolate);
314+ v8::Context::Scope context_scope (env->context ());
315+ Local<Object> obj = wrap->object ();
316+
317+ // Check if callback is set
318+ if (obj->GetInternalField (kPeerCloseCallbackField )
319+ .As <Value>()
320+ ->IsUndefined ()) {
321+ return ;
322+ }
323+ Local<Value> cb_value =
324+ obj->GetInternalField (kPeerCloseCallbackField ).As <Value>();
325+ Local<Function> cb = cb_value.As <Function>();
326+ // Reset before calling to prevent re-entrancy issues
327+ obj->SetInternalField (kPeerCloseCallbackField , v8::Undefined (isolate));
328+
329+ // MakeCallback properly tracks AsyncHooks context and flushes microtasks.
330+ wrap->MakeCallback (cb, 0 , nullptr );
331+ }
332+
234333void PipeWrap::Connect (const FunctionCallbackInfo<Value>& args) {
235334 Environment* env = Environment::GetCurrent (args);
236335
0 commit comments