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