Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 53 additions & 2 deletions tools/local-env/scripts/install.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ const local_env_utils = require( './utils' );

dotenvExpand.expand( dotenv.config() );

// Create wp-config.php.
wp_cli( `config create --dbname=wordpress_develop --dbuser=root --dbpass=password --dbhost=mysql --force --config-file="wp-config.php"` );
// Create wp-config.php. Use retry logic to handle database startup race conditions.
wp_cli_with_retry( `config create --dbname=wordpress_develop --dbuser=root --dbpass=password --dbhost=mysql --force --config-file="wp-config.php"` );

// Add the debug settings to wp-config.php.
// Windows requires this to be done as an additional step, rather than using the --extra-php option in the previous step.
Expand Down Expand Up @@ -61,3 +61,54 @@ wait_on( {
function wp_cli( cmd ) {
execSync( `npm --silent run env:cli -- ${cmd} --path=/var/www/${process.env.LOCAL_DIR}`, { stdio: 'inherit' } );
}

/**
* Synchronously pauses execution for the given number of milliseconds.
*
* Uses `Atomics.wait` so the pause works on every platform. Shelling out to a
* `sleep` command would fail on Windows, where `sleep` is not a `cmd.exe` builtin.
*
* @param {number} ms Milliseconds to wait.
*/
function sleep_sync( ms ) {
Atomics.wait( new Int32Array( new SharedArrayBuffer( 4 ) ), 0, 0, ms );
}

/**
* Runs WP-CLI commands with retry-on-failure to ride out database startup races.
*
* The Docker `mysqladmin ping` healthcheck can report MariaDB ready before the
* server accepts authenticated connections, causing `wp config create` to fail
* intermittently with "Database connection error (2002) Connection refused".
*
* Because `wp_cli` invokes `execSync` with `stdio: 'inherit'`, the caught error
* does not carry the underlying stderr that would let us classify the failure.
* Every failure is therefore treated as potentially transient and retried; the
* underlying wp-cli error is still streamed live to the user on each attempt,
* and a persistent failure surfaces the same error after the retry envelope
* is exhausted.
*
* Backoff schedule with the defaults: 1s, 2s, 4s, 8s, 16s (~31s total) before
* the final attempt.
*
* @param {string} cmd The WP-CLI command to run.
* @param {number} maxRetries Maximum number of attempts. Default 6.
* @param {number} delay Initial backoff delay in milliseconds. Default 1000.
*/
function wp_cli_with_retry( cmd, maxRetries = 6, delay = 1000 ) {
for ( let i = 0; i < maxRetries; i++ ) {
try {
wp_cli( cmd );
return;
} catch ( err ) {
if ( i === maxRetries - 1 ) {
const total_wait_seconds = ( delay * ( Math.pow( 2, maxRetries - 1 ) - 1 ) ) / 1000;
console.error( `Command failed after ${ maxRetries } attempts (~${ total_wait_seconds }s of retries). Likely a persistent database or configuration issue.` );
throw err;
}
const waitTime = delay * Math.pow( 2, i );
console.log( `Command failed (likely transient database startup race). Retrying in ${ waitTime }ms... (attempt ${ i + 1 }/${ maxRetries })` );
sleep_sync( waitTime );
}
}
}
Loading