|
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,8 @@ 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); |
| 85 | + SetProtoMethod(isolate, t, "unwatchPeerClose", UnwatchPeerClose); |
83 | 86 |
|
84 | 87 | #ifdef _WIN32 |
85 | 88 | SetProtoMethod(isolate, t, "setPendingInstances", SetPendingInstances); |
@@ -110,6 +113,8 @@ void PipeWrap::RegisterExternalReferences(ExternalReferenceRegistry* registry) { |
110 | 113 | registry->Register(Listen); |
111 | 114 | registry->Register(Connect); |
112 | 115 | registry->Register(Open); |
| 116 | + registry->Register(WatchPeerClose); |
| 117 | + registry->Register(UnwatchPeerClose); |
113 | 118 | #ifdef _WIN32 |
114 | 119 | registry->Register(SetPendingInstances); |
115 | 120 | #endif |
@@ -159,6 +164,11 @@ PipeWrap::PipeWrap(Environment* env, |
159 | 164 | // Suggestion: uv_pipe_init() returns void. |
160 | 165 | } |
161 | 166 |
|
| 167 | +PipeWrap::~PipeWrap() { |
| 168 | + peer_close_watching_ = false; |
| 169 | + peer_close_cb_.Reset(); |
| 170 | +} |
| 171 | + |
162 | 172 | void PipeWrap::Bind(const FunctionCallbackInfo<Value>& args) { |
163 | 173 | PipeWrap* wrap; |
164 | 174 | ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This()); |
@@ -213,6 +223,96 @@ void PipeWrap::Open(const FunctionCallbackInfo<Value>& args) { |
213 | 223 | args.GetReturnValue().Set(err); |
214 | 224 | } |
215 | 225 |
|
| 226 | +void PipeWrap::WatchPeerClose(const FunctionCallbackInfo<Value>& args) { |
| 227 | + PipeWrap* wrap; |
| 228 | + ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This()); |
| 229 | + |
| 230 | + if (!wrap->IsAlive()) { |
| 231 | + return args.GetReturnValue().Set(UV_EBADF); |
| 232 | + } |
| 233 | + |
| 234 | + if (wrap->peer_close_watching_) { |
| 235 | + return args.GetReturnValue().Set(0); |
| 236 | + } |
| 237 | + |
| 238 | + CHECK_GT(args.Length(), 0); |
| 239 | + CHECK(args[0]->IsFunction()); |
| 240 | + |
| 241 | + Environment* env = wrap->env(); |
| 242 | + Isolate* isolate = env->isolate(); |
| 243 | + |
| 244 | + // Store the JS callback securely so it isn't garbage collected. |
| 245 | + wrap->peer_close_cb_.Reset(isolate, args[0].As<Function>()); |
| 246 | + wrap->peer_close_watching_ = true; |
| 247 | + |
| 248 | + // Start reading to detect EOF/ECONNRESET from the peer. |
| 249 | + // We use our custom allocator and reader, ignoring actual data. |
| 250 | + int err = uv_read_start(wrap->stream(), PeerCloseAlloc, PeerCloseRead); |
| 251 | + if (err != 0) { |
| 252 | + wrap->peer_close_watching_ = false; |
| 253 | + wrap->peer_close_cb_.Reset(); |
| 254 | + } |
| 255 | + args.GetReturnValue().Set(err); |
| 256 | +} |
| 257 | + |
| 258 | +void PipeWrap::UnwatchPeerClose(const FunctionCallbackInfo<Value>& args) { |
| 259 | + PipeWrap* wrap; |
| 260 | + ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This()); |
| 261 | + |
| 262 | + if (!wrap->peer_close_watching_) { |
| 263 | + wrap->peer_close_cb_.Reset(); |
| 264 | + return args.GetReturnValue().Set(0); |
| 265 | + } |
| 266 | + |
| 267 | + // Stop listening and release the JS callback to prevent memory leaks. |
| 268 | + wrap->peer_close_watching_ = false; |
| 269 | + wrap->peer_close_cb_.Reset(); |
| 270 | + args.GetReturnValue().Set(uv_read_stop(wrap->stream())); |
| 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 || !wrap->peer_close_watching_) return; |
| 287 | + |
| 288 | + // Ignore actual data reads or EAGAIN (0). We only watch for disconnects. |
| 289 | + if (nread > 0 || 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 | + wrap->peer_close_watching_ = false; |
| 296 | + uv_read_stop(stream); |
| 297 | + |
| 298 | + if (wrap->peer_close_cb_.IsEmpty()) return; |
| 299 | + Environment* env = wrap->env(); |
| 300 | + Isolate* isolate = env->isolate(); |
| 301 | + |
| 302 | + // Set up V8 context and handles to safely execute the JS callback. |
| 303 | + v8::HandleScope handle_scope(isolate); |
| 304 | + v8::Context::Scope context_scope(env->context()); |
| 305 | + Local<Function> cb = wrap->peer_close_cb_.Get(isolate); |
| 306 | + // Reset before calling to prevent re-entrancy issues |
| 307 | + wrap->peer_close_cb_.Reset(); |
| 308 | + |
| 309 | + errors::TryCatchScope try_catch(env); |
| 310 | + try_catch.SetVerbose(true); |
| 311 | + |
| 312 | + // MakeCallback properly tracks AsyncHooks context and flushes microtasks. |
| 313 | + wrap->MakeCallback(cb, 0, nullptr); |
| 314 | +} |
| 315 | + |
216 | 316 | void PipeWrap::Connect(const FunctionCallbackInfo<Value>& args) { |
217 | 317 | Environment* env = Environment::GetCurrent(args); |
218 | 318 |
|
@@ -252,7 +352,6 @@ void PipeWrap::Connect(const FunctionCallbackInfo<Value>& args) { |
252 | 352 |
|
253 | 353 | args.GetReturnValue().Set(err); |
254 | 354 | } |
255 | | - |
256 | 355 | } // namespace node |
257 | 356 |
|
258 | 357 | NODE_BINDING_CONTEXT_AWARE_INTERNAL(pipe_wrap, node::PipeWrap::Initialize) |
|
0 commit comments