@@ -2134,50 +2134,83 @@ func startBuiltinPostgres(ctx context.Context, cfg config.Root, logger slog.Logg
21342134return "" ,nil ,xerrors .New ("The built-in PostgreSQL cannot run as the root user. Create a non-root user and run again!" )
21352135}
21362136
2137- // Ensure a password and port have been generated!
2138- connectionURL ,err := embeddedPostgresURL (cfg )
2139- if err != nil {
2140- return "" ,nil ,err
2141- }
2142- pgPassword ,err := cfg .PostgresPassword ().Read ()
2143- if err != nil {
2144- return "" ,nil ,xerrors .Errorf ("read postgres password: %w" ,err )
2145- }
2146- pgPortRaw ,err := cfg .PostgresPort ().Read ()
2147- if err != nil {
2148- return "" ,nil ,xerrors .Errorf ("read postgres port: %w" ,err )
2149- }
2150- pgPort ,err := strconv .ParseUint (pgPortRaw ,10 ,16 )
2151- if err != nil {
2152- return "" ,nil ,xerrors .Errorf ("parse postgres port: %w" ,err )
2153- }
2154-
21552137cachePath := filepath .Join (cfg .PostgresPath (),"cache" )
21562138if customCacheDir != "" {
21572139cachePath = filepath .Join (customCacheDir ,"postgres" )
21582140}
21592141stdlibLogger := slog .Stdlib (ctx ,logger .Named ("postgres" ),slog .LevelDebug )
2160- ep := embeddedpostgres .NewDatabase (
2161- embeddedpostgres .DefaultConfig ().
2162- Version (embeddedpostgres .V13 ).
2163- BinariesPath (filepath .Join (cfg .PostgresPath (),"bin" )).
2164- // Default BinaryRepositoryURL repo1.maven.org is flaky.
2165- BinaryRepositoryURL ("https://repo.maven.apache.org/maven2" ).
2166- DataPath (filepath .Join (cfg .PostgresPath (),"data" )).
2167- RuntimePath (filepath .Join (cfg .PostgresPath (),"runtime" )).
2168- CachePath (cachePath ).
2169- Username ("coder" ).
2170- Password (pgPassword ).
2171- Database ("coder" ).
2172- Encoding ("UTF8" ).
2173- Port (uint32 (pgPort )).
2174- Logger (stdlibLogger .Writer ()),
2175- )
2176- err = ep .Start ()
2177- if err != nil {
2178- return "" ,nil ,xerrors .Errorf ("Failed to start built-in PostgreSQL. Optionally, specify an external deployment with `--postgres-url`: %w" ,err )
2142+
2143+ // If the port is not defined, an available port will be found dynamically.
2144+ maxAttempts := 1
2145+ _ ,err = cfg .PostgresPort ().Read ()
2146+ retryPortDiscovery := errors .Is (err ,os .ErrNotExist )&& os .Getenv ("CI" )== "true"
2147+ if retryPortDiscovery {
2148+ // There is no way to tell Postgres to use an ephemeral port, so in order to avoid
2149+ // flaky tests in CI we need to retry EmbeddedPostgres.Start in case of a race
2150+ // condition where the port we quickly listen on and close in embeddedPostgresURL()
2151+ // is not free by the time the embedded postgres starts up. This maximum_should
2152+ // cover most cases where port conflicts occur in CI and cause flaky tests.
2153+ maxAttempts = 3
2154+ }
2155+
2156+ var startErr error
2157+ for attempt := 0 ;attempt < maxAttempts ;attempt ++ {
2158+ // Ensure a password and port have been generated.
2159+ connectionURL ,err := embeddedPostgresURL (cfg )
2160+ if err != nil {
2161+ return "" ,nil ,err
2162+ }
2163+ pgPassword ,err := cfg .PostgresPassword ().Read ()
2164+ if err != nil {
2165+ return "" ,nil ,xerrors .Errorf ("read postgres password: %w" ,err )
2166+ }
2167+ pgPortRaw ,err := cfg .PostgresPort ().Read ()
2168+ if err != nil {
2169+ return "" ,nil ,xerrors .Errorf ("read postgres port: %w" ,err )
2170+ }
2171+ pgPort ,err := strconv .ParseUint (pgPortRaw ,10 ,16 )
2172+ if err != nil {
2173+ return "" ,nil ,xerrors .Errorf ("parse postgres port: %w" ,err )
2174+ }
2175+
2176+ ep := embeddedpostgres .NewDatabase (
2177+ embeddedpostgres .DefaultConfig ().
2178+ Version (embeddedpostgres .V13 ).
2179+ BinariesPath (filepath .Join (cfg .PostgresPath (),"bin" )).
2180+ // Default BinaryRepositoryURL repo1.maven.org is flaky.
2181+ BinaryRepositoryURL ("https://repo.maven.apache.org/maven2" ).
2182+ DataPath (filepath .Join (cfg .PostgresPath (),"data" )).
2183+ RuntimePath (filepath .Join (cfg .PostgresPath (),"runtime" )).
2184+ CachePath (cachePath ).
2185+ Username ("coder" ).
2186+ Password (pgPassword ).
2187+ Database ("coder" ).
2188+ Encoding ("UTF8" ).
2189+ Port (uint32 (pgPort )).
2190+ Logger (stdlibLogger .Writer ()),
2191+ )
2192+
2193+ startErr = ep .Start ()
2194+ if startErr == nil {
2195+ return connectionURL ,ep .Stop ,nil
2196+ }
2197+
2198+ logger .Warn (ctx ,"failed to start embedded postgres" ,
2199+ slog .F ("attempt" ,attempt + 1 ),
2200+ slog .F ("max_attempts" ,maxAttempts ),
2201+ slog .F ("port" ,pgPort ),
2202+ slog .Error (startErr ),
2203+ )
2204+
2205+ if retryPortDiscovery {
2206+ // Since a retry is needed, we wipe the port stored here at the beginning of the loop.
2207+ _ = cfg .PostgresPort ().Delete ()
2208+ }
21792209}
2180- return connectionURL ,ep .Stop ,nil
2210+
2211+ return "" ,nil ,xerrors .Errorf ("failed to start built-in PostgreSQL after %d attempts. " +
2212+ "Optionally, specify an external deployment. See https://coder.com/docs/tutorials/external-database " +
2213+ "for more details: %w" ,maxAttempts ,startErr )
21812214}
21822215
21832216func ConfigureHTTPClient (ctx context.Context ,clientCertFile ,clientKeyFile string ,tlsClientCAFile string ) (context.Context ,* http.Client ,error ) {