Package or repository
@antelopejs/api 1.3.0 (dist/port-reservation.js). The same pattern exists in @antelopejs/dms-frontend (src/ports.ts, reserveFreePort).
Problem
Port reservation can stall startup when something connects to the port while it is held.
holdPort reserves the port with net.createServer() and no connection handler, then listen()s. releaseReservedPorts / closeHolder release it with holder.close(cb). net.Server#close stops accepting new connections, but its callback only fires once every open connection has ended.
While the port is held, the kernel still accepts incoming connections into the holder. Nobody reads or destroys those sockets, so the client stays connected. If a client connects in that window, closeHolder never resolves, and the real server never binds. Such a client can be a health-check loop without a timeout, like the until curl -fsS http://127.0.0.1:5010/... probes in .amp/services.yaml, or anything polling the port.
This was seen while running DMS module playgrounds with startup probes. It is pre-existing and independent of any DMS change. Our scripts now wait for .antelope/dev.json and the "Server ready" line before probing, which only avoids the window.
Proposed solution
Make the holder never keep a connection:
net.createServer((socket) => socket.destroy()), so connections made during the reservation are dropped immediately;
- and/or
holder.closeAllConnections?.() (Node ≥ 18.2 on http.Server; for a net.Server, track sockets and destroy them) before close().
Apply the same to dms-frontend's reserveFreePort, which uses the same hold-and-close pattern. Add a test: connect a socket to a held port, then release it; the release must resolve promptly and the port must be bindable.
Alternatives
- Timeouts on every probe: they work around it per caller and leave the stall in the library.
- No reservation at all (bind directly): that loses the "reserve all ports first, then start" guarantee the module relies on.
Additional context
Package or repository
@antelopejs/api1.3.0 (dist/port-reservation.js). The same pattern exists in@antelopejs/dms-frontend(src/ports.ts,reserveFreePort).Problem
Port reservation can stall startup when something connects to the port while it is held.
holdPortreserves the port withnet.createServer()and no connection handler, thenlisten()s.releaseReservedPorts/closeHolderrelease it withholder.close(cb).net.Server#closestops accepting new connections, but its callback only fires once every open connection has ended.While the port is held, the kernel still accepts incoming connections into the holder. Nobody reads or destroys those sockets, so the client stays connected. If a client connects in that window,
closeHoldernever resolves, and the real server never binds. Such a client can be a health-check loop without a timeout, like theuntil curl -fsS http://127.0.0.1:5010/...probes in.amp/services.yaml, or anything polling the port.This was seen while running DMS module playgrounds with startup probes. It is pre-existing and independent of any DMS change. Our scripts now wait for
.antelope/dev.jsonand the "Server ready" line before probing, which only avoids the window.Proposed solution
Make the holder never keep a connection:
net.createServer((socket) => socket.destroy()), so connections made during the reservation are dropped immediately;holder.closeAllConnections?.()(Node ≥ 18.2 onhttp.Server; for anet.Server, track sockets and destroy them) beforeclose().Apply the same to dms-frontend's
reserveFreePort, which uses the same hold-and-close pattern. Add a test: connect a socket to a held port, then release it; the release must resolve promptly and the port must be bindable.Alternatives
Additional context
net.Server#close: "Stops the server from accepting new connections and keeps existing connections… The server is finally closed when all connections are ended."