Skip to content

net.Socket: connect and error events never fire; net.connect() factory returns undefined #422

@proggeramlug

Description

@proggeramlug

Summary

Perry's net stdlib module accepts socket creation and .connect(...) calls without throwing, but no 'connect' or 'error' event ever fires on the resulting socket. The TCP connection is never visibly opened (no entry in lsof, server logs see no incoming connection). Additionally, the net.connect() factory variant returns undefined.

This blocks any pure-TS client over net.Socket. Surfaced while smoke-testing @perryts/postgres end-to-end against a real Postgres — the driver's startup handshake never begins because the underlying socket never reports connect.

Tested with locally-built perry 0.5.493.

Reproducers (each ~10 lines, deterministic)

A real listener at 127.0.0.1:5432 is needed for one of the variants — any TCP server works (nc -l 5432, a local Postgres, etc.). The first reproducer below is sufficient on its own.

Reproducer 1 — new net.Socket().connect(port, host)

import * as net from "net"

console.log("[a] before connect")
const sock = new net.Socket()
sock.on("connect", () => console.log("[b] connected"))
sock.on("error", (e: Error) => console.log("[c] error:", e.message))
sock.connect(5432, "127.0.0.1")
console.log("[d] connect call returned")

setTimeout(() => {
  console.log("[e] 2s timer fired")
  sock.destroy()
}, 2000)

Expected (matches Node):

[a] before connect
[d] connect call returned
[b] connected         ← OR [c] error: ECONNREFUSED if no listener
[e] 2s timer fired

Actual on Perry (with a real Postgres listening on 5432):

[a] before connect
[d] connect call returned
[e] 2s timer fired

[b] and [c] never print. lsof -p <pid> during the 2-second window shows no TCP socket opened by the process. The server's connection log shows no incoming connection.

Reproducer 2 — new net.Socket().connect({port, host}) (object form)

Same outcome as reproducer 1: 'connect' and 'error' never fire.

Reproducer 3 — net.connect(port, host) factory

import * as net from "net"
const sock = net.connect(5432, "127.0.0.1")
console.log("type =", typeof sock)  // prints: type = undefined

net.connect() returns undefined rather than a Socket. Any subsequent property access blows up.

Why this matters

net.Socket is the foundation for every pure-TS networking client (Postgres, MySQL, Redis variants not using ioredis, custom binary protocols, MQTT, etc.). The Perry-stdlib already shims fastify and ioredis at higher layers, but pure-TS drivers like @perryts/postgres build directly on net.Socket (and tls.connect), so this gap blocks any of them from working end-to-end.

The @perryts/postgres repo's own examples/cold-start-minimal.ts reproduces the same hang against a real Postgres on Perry 0.5.493 — driver code unmodified, only PGHOST=127.0.0.1 in the environment. The driver's connect timeout (10s) does not appear to fire either, suggesting the issue extends to interaction between the timer callback and the never-emitting socket events, or the timer-vs-pending-socket interaction in Perry's event loop. (Standalone setTimeout works fine — [e] in the reproducer above prints on time.)

Workaround

None known. perry-stdlib's higher-level shims (fastify, ioredis) do work for the cases they cover, but anything that expects to drive a raw socket is blocked.

Surfaced via

Phase 1 of an exploratory port of MedusaJS to Perry (Medusa-API-compatible native binary). The data-layer prototype compiles to a 5.3 MB binary that boots cleanly, sets up all module-level state, and is correctly wired up to @perryts/postgres — but blocks on this socket bug at the first DB connection.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions