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