Repository navigation
createDevServer starts on a port that another server holds on 127.0.0.1 #460
Description
Summary
If another server listens on 127.0.0.1:<port>, createDevServer still starts on the same port and reports no error. With the default host localhost, devframe binds only [::1]:<port>. Requests to 127.0.0.1:<port> then go to the other server.
The cause is in get-port-please, which devframe bundles. getPort({ port, host }) tries to bind only the given host. 127.0.0.1 and ::1 can each hold the same port, so the port looks free. This is unjs/get-port-please#121, which is still open.
packages/devframe/src/node/instance-shell.ts already notes that getPort can "disagree across the v4/v6 duals of localhost". The retry there runs only when the bind fails with EADDRINUSE. In this case the bind succeeds, so the retry does not run.
Versions
devframe1.2.6 (mainat0e4a49c7),get-port-please3.2.0 (the latest release)- Linux: Node 22.23 in Docker
node:22-bookworm, with IPv6 on the loopback interface - macOS 15: Node 22.22
Reproduction
import { createServer } from 'node:net'
import { defineDevframe } from 'devframe'
import { createDevServer } from 'devframe/adapters/dev'
const port = 19876
// Another server holds the port on 127.0.0.1 only.
const other = createServer()
await new Promise(resolve => other.listen(port, '127.0.0.1', resolve))
const devframe = defineDevframe({ id: 'repro', name: 'Repro', cli: { port }, setup() {} })
const server = await createDevServer(devframe, { openBrowser: false })
console.log(server.port) // Expected: a port other than 19876
await server.close()
other.close()The script prints 19876 on Linux and on macOS.
Results with another server on 127.0.0.1:<port>:
| Platform | host |
Conflict found | devframe binds |
|---|---|---|---|
| Linux, IPv6 on | localhost or not set |
No | [::1]:<port> |
| Linux, IPv6 on | 0.0.0.0 |
Yes | another port |
| Linux, IPv6 off | any | Yes | another port |
| macOS | localhost or not set |
No | [::1]:<port> |
| macOS | 0.0.0.0 |
No | *:<port> |
When the conflict is not found, http://127.0.0.1:<port> reaches the other server. On Linux, Node fetch('http://localhost:<port>') also reaches the other server. With host: '0.0.0.0' on macOS, localhost and 127.0.0.1 both reach the other server.
Proposed fix
I have a fix with tests, and I can open a PR:
- In
get-port-please,getPortalso tries127.0.0.1,::1,0.0.0.0and::when the host islocalhost, a loopback address, or a wildcard address. The port is in use if one of these binds fails withEADDRINUSEorEACCES. Other errors, such asEADDRNOTAVAILwhen IPv6 is off, mean that the address does not exist. They do not count as "in use". - devframe bundles
get-port-please, so it carries the same change as a pnpm patch (patches/get-port-please@3.2.0.patch). The fix then reaches every caller ofdevframe/utils/get-port, for example Vite DevTools. You can remove the patch when upstream releases a fix. - A regression test in
packages/devframe/src/adapters/__tests__/dev.test.ts. It fails onmainand passes with the patch, on Linux (IPv6 on) and on macOS.
I will also send the same change upstream for unjs/get-port-please#121. If you want a different shape, tell me and I will change the PR. One option is a small wrapper in devframe/utils/get-port instead of a patch.
Activity