Repository: denoland/deno
Pull request: fix(ext/node): node compatibility issue missing fd in createServer callback socket object

This PR will address the node compatibility issue where, the callback socket object in net.createServer is missing a fd in socket._handle

Modified this PR to use internal symbol to expose fd instead of exposing it via lib.deno.d.ts
https://github.com/denoland/deno_core/pull/1061

fixes: https://github.com/denoland/deno/issues/27788

<!--
Before submitting a PR, please read https://docs.deno.com/runtime/manual/references/contributing

1. Give the PR a descriptive title.

  Examples of good title:
    - fix(std/http): Fix race condition in server
    - docs(console): Update docstrings
    - feat(doc): Handle nested reexports

  Examples of bad title:
    - fix #7123
    - update docs
    - fix bugs

2. Ensure there is a related issue and it is referenced in the PR text.
3. Ensure there are tests that cover the changes.
4. Ensure `cargo test` passes.
5. Ensure `./tools/format.js` passes without changing files.
6. Ensure `./tools/lint.js` passes.
7. Open as a draft PR if your work is still in progress. The CI won't run
   all steps, but you can add '[ci]' to a commit message to force it to.
8. If you would like to run the benchmarks on the CI, add the 'ci-bench' label.
-->


Changed files:
filename: ext/net/01_net.js
patch: @@ -5,6 +5,7 @@ const {
   BadResourcePrototype,
   InterruptedPrototype,
   internalRidSymbol,
+  internalFdSymbol,
   createCancelHandle,
 } = core;
 import {
@@ -99,13 +100,17 @@ class Conn {
 
   #readable;
   #writable;
-
-  constructor(rid, remoteAddr, localAddr) {
+  constructor(rid, remoteAddr, localAddr, fd) {
     ObjectDefineProperty(this, internalRidSymbol, {
       __proto__: null,
       enumerable: false,
       value: rid,
     });
+    ObjectDefineProperty(this, internalFdSymbol, {
+      __proto__: null,
+      enumerable: false,
+      value: fd,
+    });
     this.#rid = rid;
     this.#remoteAddr = remoteAddr;
     this.#localAddr = localAddr;
@@ -211,8 +216,8 @@ class UpgradedConn extends Conn {
 class TcpConn extends Conn {
   #rid = 0;
 
-  constructor(rid, remoteAddr, localAddr) {
-    super(rid, remoteAddr, localAddr);
+  constructor(rid, remoteAddr, localAddr, fd) {
+    super(rid, remoteAddr, localAddr, fd);
     ObjectDefineProperty(this, internalRidSymbol, {
       __proto__: null,
       enumerable: false,
@@ -278,12 +283,12 @@ class Listener {
     }
     this.#promise = promise;
     if (this.#unref) core.unrefOpPromise(promise);
-    const { 0: rid, 1: localAddr, 2: remoteAddr } = await promise;
+    const { 0: rid, 1: localAddr, 2: remoteAddr, 3: fd } = await promise;
     this.#promise = null;
     if (this.addr.transport == "tcp") {
       localAddr.transport = "tcp";
       remoteAddr.transport = "tcp";
-      return new TcpConn(rid, remoteAddr, localAddr);
+      return new TcpConn(rid, remoteAddr, localAddr, fd);
     } else if (this.addr.transport == "unix") {
       return new UnixConn(
         rid,

filename: ext/net/ops.rs
patch: @@ -46,6 +46,8 @@ use crate::resolve_addr::resolve_addr_sync;
 use crate::tcp::TcpListener;
 use crate::NetPermissions;
 
+pub type Fd = u32;
+
 #[derive(Serialize, Clone, Debug)]
 #[serde(rename_all = "camelCase")]
 pub struct TlsHandshakeInfo {
@@ -165,7 +167,7 @@ pub(crate) fn accept_err(e: std::io::Error) -> NetError {
 pub async fn op_net_accept_tcp(
   state: Rc<RefCell<OpState>>,
   #[smi] rid: ResourceId,
-) -> Result<(ResourceId, IpAddr, IpAddr), NetError> {
+) -> Result<(ResourceId, IpAddr, IpAddr, Option<Fd>), NetError> {
   let resource = state
     .borrow()
     .resource_table
@@ -180,14 +182,27 @@ pub async fn op_net_accept_tcp(
     .try_or_cancel(cancel)
     .await
     .map_err(accept_err)?;
+  let mut _fd_raw: Option<Fd> = None;
+  #[cfg(not(windows))]
+  {
+    use std::os::fd::AsFd;
+    use std::os::fd::AsRawFd;
+    let fd = tcp_stream.as_fd();
+    _fd_raw = Some(fd.as_raw_fd() as u32);
+  }
   let local_addr = tcp_stream.local_addr()?;
   let remote_addr = tcp_stream.peer_addr()?;
 
   let mut state = state.borrow_mut();
   let rid = state
     .resource_table
     .add(TcpStreamResource::new(tcp_stream.into_split()));
-  Ok((rid, IpAddr::from(local_addr), IpAddr::from(remote_addr)))
+  Ok((
+    rid,
+    IpAddr::from(local_addr),
+    IpAddr::from(remote_addr),
+    _fd_raw,
+  ))
 }
 
 #[op2(async)]

filename: ext/node/polyfills/internal_binding/tcp_wrap.ts
patch: @@ -27,6 +27,8 @@
 // TODO(petamoriken): enable prefer-primordials for node polyfills
 // deno-lint-ignore-file prefer-primordials
 
+import { core } from "ext:core/mod.js";
+const { internalFdSymbol } = core;
 import { notImplemented } from "ext:deno_node/_utils.ts";
 import { unreachable } from "ext:deno_node/_util/asserts.ts";
 import { ConnectionWrap } from "ext:deno_node/internal_binding/connection_wrap.ts";
@@ -141,6 +143,10 @@ export class TCP extends ConnectionWrap {
     }
   }
 
+  get fd() {
+    return this[kStreamBaseField]?.[internalFdSymbol];
+  }
+
   /**
    * Opens a file descriptor.
    * @param fd The file descriptor to open.
@@ -221,7 +227,6 @@ export class TCP extends ConnectionWrap {
     const address = listener.addr as Deno.NetAddr;
     this.#address = address.hostname;
     this.#port = address.port;
-
     this.#listener = listener;
 
     // TODO(kt3k): Delays the accept() call 2 ticks. Deno.Listener can't be closed
@@ -458,7 +463,6 @@ export class TCP extends ConnectionWrap {
 
     // Reset the backoff delay upon successful accept.
     this.#acceptBackoffDelay = undefined;
-
     const connectionHandle = new TCP(socketType.SOCKET, connection);
     this.#connections++;