Skip to content

Start again on another port when a detected port is taken before the server binds it - #178

Open
zenios wants to merge 1 commit into
zonkyio:masterfrom
zenios:retry-detected-port-taken
Open

zenios wants to merge 1 commit into
zonkyio:masterfrom
zenios:retry-detected-port-taken

Conversation

@zenios

@zenios zenios commented Oct 31, 2026

Copy link
Copy Markdown

The problem

With no port set, Builder.start() picks a free port with new ServerSocket(0), closes it, runs initdb and only then starts the server. Anything can take the port in that window, which lasts as long as initdb. When tests run in parallel, another test's socket sometimes does. The server then logs:

LOG:  could not bind IPv4 address "127.0.0.1": Address already in use
FATAL:  could not create any TCP/IP sockets

and start() fails with Gave up waiting for server to start after ..., once the whole startup wait has passed. Nothing is wrong with the test, and it passes when run again.

The change

  • Builder.start() tries up to 3 times, with a new port and a new temporary directory each time, if the failed port really is taken.
  • It does not retry when the caller set the port or the data directory, or when the port is still free, as that failure has another cause and would only wait again.
  • A failed start now closes the instance: it releases the lock and removes the data directory. Without this, each failed attempt also left its lock open and its directory behind, and a shutdown hook that tries to stop a server that never started.

Tests

  • testDetectedPortTakenBeforeTheServerBindsItStartsOnAnother holds the first detected port and expects a working server on another. A package-private nextPort() makes the clash deterministic.
  • testAPortAskedForAndTakenFailsWithoutAnotherAttempt keeps today's behaviour when the caller chose the port.

Known limits

  • A failed attempt still waits out pgStartupWait before retrying, as today. The server has already exited by then, so failing fast could be a separate change.
  • Closing a failed instance logs Could not stop postmaster at ERROR, as pg_ctl stop finds nothing running.

…server binds it

A detected port is free only until the server binds it, after initdb, and something else can take it in between. Builder.start() now tries up to 3 times, with a new port and temporary directory, when the failed port really is taken. A port or data directory set by the caller, or a failure with the port still free, fails as before. A failed start now closes the instance, releasing its lock and removing its data directory.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant