88 "net"
99 "os"
1010 "path/filepath"
11+ "strconv"
1112 "sync"
1213 "time"
1314
@@ -20,6 +21,7 @@ import (
2021const (
2122 embeddedStartupLockTimeout = 5 * time .Minute
2223 embeddedStartupRetryDelay = 100 * time .Millisecond
24+ embeddedProbeTimeout = 200 * time .Millisecond
2325)
2426
2527var embeddedStartupMu sync.Mutex
@@ -123,9 +125,14 @@ func (r *embeddedRuntime) withStartupLock(
123125
124126 dsn , stop , startErr := fn ()
125127 unlockErr := lock .Unlock ()
126- if startErr != nil || unlockErr != nil {
128+ if startErr != nil {
127129 return "" , nil , errors .Join (startErr , unlockErr )
128130 }
131+ if unlockErr != nil {
132+ // The server did start, so its stop closer must not be dropped on the
133+ // floor: shut it back down rather than leaking an unmanaged cluster.
134+ return "" , nil , errors .Join (unlockErr , stop ())
135+ }
129136 return dsn , stop , nil
130137}
131138
@@ -178,7 +185,9 @@ func (r *embeddedRuntime) resolveLockedConfig() error {
178185}
179186
180187func (r * embeddedRuntime ) reuseRunningServer (dsn string ) (bool , error ) {
181- if ! portIsListening (r .port ) {
188+ ctx , cancel := context .WithTimeout (context .Background (), embeddedProbeTimeout )
189+ defer cancel ()
190+ if ! portIsListening (ctx , r .port ) {
182191 return false , nil
183192 }
184193 if err := r .validateServer (dsn ); err != nil {
@@ -245,7 +254,6 @@ func validateEmbeddedDataDirectory(ctx context.Context, conn *pgx.Conn, expected
245254
246255func validateFastTestingServer (ctx context.Context , conn * pgx.Conn ) error {
247256 var fsync , synchronousCommit , fullPageWrites string
248- var maxConnections int
249257 if err := conn .QueryRow (ctx , "SHOW fsync" ).Scan (& fsync ); err != nil {
250258 return fmt .Errorf ("read fsync: %w" , err )
251259 }
@@ -255,12 +263,29 @@ func validateFastTestingServer(ctx context.Context, conn *pgx.Conn) error {
255263 if err := conn .QueryRow (ctx , "SHOW full_page_writes" ).Scan (& fullPageWrites ); err != nil {
256264 return fmt .Errorf ("read full_page_writes: %w" , err )
257265 }
258- if err := conn .QueryRow (ctx , "SHOW max_connections" ).Scan (& maxConnections ); err != nil {
259- return fmt .Errorf ("read max_connections: %w" , err )
266+ maxConnections , err := showInt (ctx , conn , "max_connections" )
267+ if err != nil {
268+ return err
260269 }
261270 return validateFastTestingSettings (fsync , synchronousCommit , fullPageWrites , maxConnections )
262271}
263272
273+ // showInt reads a numeric server setting. SHOW always reports text (OID 25)
274+ // regardless of the underlying GUC type, so the value has to be parsed rather
275+ // than scanned straight into an int. setting is always a package-level literal;
276+ // SHOW takes no bind parameters.
277+ func showInt (ctx context.Context , conn * pgx.Conn , setting string ) (int , error ) {
278+ var raw string
279+ if err := conn .QueryRow (ctx , "SHOW " + setting ).Scan (& raw ); err != nil {
280+ return 0 , fmt .Errorf ("read %s: %w" , setting , err )
281+ }
282+ value , err := strconv .Atoi (raw )
283+ if err != nil {
284+ return 0 , fmt .Errorf ("parse %s %q: %w" , setting , raw , err )
285+ }
286+ return value , nil
287+ }
288+
264289func (r * embeddedRuntime ) serverConfig () embeddedpostgres.Config {
265290 config := embeddedpostgres .DefaultConfig ().
266291 Port (r .port ).
@@ -299,8 +324,9 @@ func (r *embeddedRuntime) dsn() string {
299324 )
300325}
301326
302- func portIsListening (port uint32 ) bool {
303- conn , err := net .DialTimeout ("tcp" , fmt .Sprintf ("localhost:%d" , port ), 200 * time .Millisecond )
327+ func portIsListening (ctx context.Context , port uint32 ) bool {
328+ var dialer net.Dialer
329+ conn , err := dialer .DialContext (ctx , "tcp" , fmt .Sprintf ("localhost:%d" , port ))
304330 if err != nil {
305331 return false
306332 }
0 commit comments