From a911c9523b7f30cb7d381d18c1ac16cc08016cfe Mon Sep 17 00:00:00 2001 From: Khokan Sardar Date: Thu, 20 Aug 2026 07:56:59 +0530 Subject: [PATCH] Upgrade/Install: Load the `.maintenance` file non-fatally. `wp_is_maintenance_mode()` and `WP_Automatic_Updater::has_fatal_error()` both check for `.maintenance` with `file_exists()` and then load it with `require`. An update running in a concurrent request can delete the file between the two, and with `opcache.enable_file_override=1` the cached script entry keeps `file_exists()` returning true after the deletion, so `require` fails and terminates the request. Load the file with `include` and bail when it returns false, so a file that disappears or becomes unreadable between the check and the load leaves maintenance mode off instead of ending the request. Fixes #65911. --- .../includes/class-wp-automatic-updater.php | 7 ++-- src/wp-includes/load.php | 8 ++++- .../tests/load/wpIsMaintenanceMode.php | 34 +++++++++++++++++++ 3 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 tests/phpunit/tests/load/wpIsMaintenanceMode.php diff --git a/src/wp-admin/includes/class-wp-automatic-updater.php b/src/wp-admin/includes/class-wp-automatic-updater.php index cd9426c6ef88b..fd094f0341231 100644 --- a/src/wp-admin/includes/class-wp-automatic-updater.php +++ b/src/wp-admin/includes/class-wp-automatic-updater.php @@ -1766,8 +1766,11 @@ protected function has_fatal_error() { return false; } - require $maintenance_file; - if ( ! is_int( $upgrading ) ) { + // The file may be removed between the check above and the load below. + // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Warning emitted in expected failure case. + $maintenance_file_loaded = @include $maintenance_file; + + if ( false === $maintenance_file_loaded || ! is_int( $upgrading ) ) { return false; } diff --git a/src/wp-includes/load.php b/src/wp-includes/load.php index 27c58b57dd671..bcab88242f3a3 100644 --- a/src/wp-includes/load.php +++ b/src/wp-includes/load.php @@ -441,7 +441,13 @@ function wp_is_maintenance_mode() { return false; } - require ABSPATH . '.maintenance'; + // The file may be removed between the check above and the load below. + // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Warning emitted in expected failure case. + $maintenance_file_loaded = @include ABSPATH . '.maintenance'; + + if ( false === $maintenance_file_loaded ) { + return false; + } // If the $upgrading timestamp is older than 10 minutes, consider maintenance over. if ( ( time() - $upgrading ) >= 10 * MINUTE_IN_SECONDS ) { diff --git a/tests/phpunit/tests/load/wpIsMaintenanceMode.php b/tests/phpunit/tests/load/wpIsMaintenanceMode.php new file mode 100644 index 0000000000000..a8b66e5aa8e01 --- /dev/null +++ b/tests/phpunit/tests/load/wpIsMaintenanceMode.php @@ -0,0 +1,34 @@ +markTestSkipped( 'A .maintenance file already exists in ABSPATH.' ); + } + + mkdir( ABSPATH . '.maintenance' ); + + $this->assertFalse( wp_is_maintenance_mode() ); + } +}