Repository: denoland/deno
Pull request: fix(ext/node): tls upgrades for used sockets

Fixes `npm:postgres` support

Fixes https://github.com/denoland/deno/issues/30417
Fixes https://github.com/denoland/deno/issues/28551
Fixes https://github.com/denoland/deno/issues/28526
Fixes https://github.com/denoland/deno/issues/28245

```
$ target/debug/deno -A repro.mjs
Result(1) [
  {
    current_database: "testdb",
    version: "PostgreSQL 14.17 (Homebrew) on aarch64-apple-darwin24.2.0, compiled by Apple clang version 16.0.0 (clang-1600.0.26.6), 64-bit"
  }
]
```

Changed files:
filename: ext/node/polyfills/internal_binding/stream_wrap.ts
patch: @@ -33,6 +33,7 @@ const {
   Array,
   MapPrototypeGet,
   ObjectPrototypeIsPrototypeOf,
+  PromiseResolve,
   PromisePrototypeThen,
   Symbol,
   TypedArrayPrototypeSlice,
@@ -345,6 +346,12 @@ export class LibuvStreamWrap extends HandleWrap {
 
   /** Internal method for reading from the attached stream. */
   async #read() {
+    // Queue the read operation and allow TLS upgrades to complete.
+    //
+    // This is done to ensure that the resource is not locked up by
+    // op_read.
+    await PromiseResolve();
+
     let buf = this.#buf;
 
     let nread: number | null;

filename: tests/node_compat/config.toml
patch: @@ -551,7 +551,6 @@
 "parallel/test-http-header-read.js" = {}
 "parallel/test-http-header-validators.js" = {}
 "parallel/test-http-hex-write.js" = {}
-"parallel/test-http-highwatermark.js" = {}
 "parallel/test-http-host-headers.js" = {}
 "parallel/test-http-incoming-message-destroy.js" = {}
 "parallel/test-http-invalid-path-chars.js" = {}

filename: tests/unit_node/net_test.ts
patch: @@ -89,7 +89,7 @@ Deno.test("[node/net] net.connect().unref() works", async () => {
             const socket = net.connect(${port}, "${hostname}", () => {
               console.log("connected");
               socket.unref();
-              socket.on("data", (data) => console.log(data.toString()));
+              socket.on("data", (data) => data.toString());
               socket.write("GET / HTTP/1.1\\n\\n");
             });
           `,

filename: tests/unit_node/tls_test.ts
patch: @@ -131,6 +131,51 @@ Deno.test("tls.connect mid-read tcp->tls upgrade", async () => {
   await promise;
 });
 
+Deno.test("tls.connect after-read tls upgrade", async () => {
+  const { promise, resolve } = Promise.withResolvers<void>();
+  const ctl = new AbortController();
+  const serve = Deno.serve({
+    port: 8444,
+    key,
+    cert,
+    signal: ctl.signal,
+  }, () => new Response("hello"));
+
+  await delay(200);
+
+  const socket = net.connect({
+    host: "localhost",
+    port: 8444,
+  });
+  socket.on("connect", () => {
+    socket.on("data", () => {});
+    socket.removeAllListeners("data");
+
+    const conn = tls.connect({
+      host: "localhost",
+      port: 8444,
+      socket,
+      secureContext: {
+        ca: rootCaCert,
+        // deno-lint-ignore no-explicit-any
+      } as any,
+    });
+
+    conn.setEncoding("utf8");
+    conn.write(`GET / HTTP/1.1\nHost: www.google.com\n\n`);
+
+    conn.on("data", (e) => {
+      assertStringIncludes(e, "hello");
+      conn.destroy();
+      ctl.abort();
+    });
+    conn.on("close", resolve);
+  });
+
+  await serve.finished;
+  await promise;
+});
+
 Deno.test("tls.createServer creates a TLS server", async () => {
   const deferred = Promise.withResolvers<void>();
   const server = tls.createServer(