PluginProbe
OPcache Reset / trunk
OPcache Reset vtrunk
trunk 1.0.0 2.0.0 2.0.1 2.1.0 2.1.1 2.1.2 2.1.3 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.2.0 2.3.0 2.4.0 2.4.1 2.4.2 2.4.3
opcache-reset / opcache-reset.php

opcache-reset.php in OPcache Reset trunk, at opcache-reset.php

177 lines 6.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Plugin Name: OPcache Reset
4 * Plugin URI: http://wordpress.org/plugins/opcache-reset/
5 * Description: Automatic reset of OPcache
6 * Version: 2.4.3
7 * Requires at least: 5.0
8 * Requires PHP: 7.4
9 * Author: Danila Vershinin
10 * Author URI: https://www.getpagespeed.com/
11 * License: GPL2
12 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
13 */
14
15 // Handle direct FastCGI OPcache reset request.
16 // This param can ONLY be set via direct FastCGI connection to PHP-FPM.
17 // nginx/Apache never forward arbitrary custom CGI params - only HTTP_* headers.
18 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated -- Checking existence only.
19 if ( isset( $_SERVER['GPS_OPCACHE_RESET_INTERNAL'] ) && '1' === $_SERVER['GPS_OPCACHE_RESET_INTERNAL'] ) {
20 if ( function_exists( 'opcache_reset' ) ) {
21 $reset = opcache_reset();
22 if ( ! $reset && function_exists( 'opcache_get_status' ) ) {
23 $opcache_status = opcache_get_status( false );
24 $reset = is_array( $opcache_status ) && ( ! empty( $opcache_status['restart_pending'] ) || ! empty( $opcache_status['restart_in_progress'] ) );
25 }
26 echo $reset ? 'OK' : 'RESET_FAILED';
27 } else {
28 echo 'NO_OPCACHE';
29 }
30 exit;
31 }
32
33 // Prevent direct access.
34 //
35 // This MUST stay below the FastCGI handler above: that handler is deliberately
36 // reachable with WordPress not loaded, which is the entire point of the direct
37 // FastCGI reset path. Everything from here down requires a booted WordPress.
38 if ( ! defined( 'ABSPATH' ) ) {
39 exit;
40 }
41
42 if ( is_admin() ) {
43 if ( ! ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) {
44 // We are in admin mode: notices and such are handled here
45 require_once __DIR__ . '/opcache-admin.php';
46 }
47 }
48
49 // Load WP-CLI commands.
50 if ( defined( 'WP_CLI' ) && WP_CLI ) {
51 require_once __DIR__ . '/opcache-cli.php';
52 }
53
54 /**
55 * Recursively remove an OPcache file-cache directory.
56 *
57 * The cache lives outside the WordPress filesystem and must be managed without
58 * WP_Filesystem so resets also work when shell functions are disabled.
59 *
60 * @param string $directory Directory to remove.
61 * @return bool Whether the directory was removed or no longer exists.
62 */
63 function gps_opcache_remove_directory( $directory ) {
64 if ( ! is_dir( $directory ) ) {
65 return ! file_exists( $directory );
66 }
67
68 try {
69 $iterator = new RecursiveIteratorIterator(
70 new RecursiveDirectoryIterator( $directory, FilesystemIterator::SKIP_DOTS ),
71 RecursiveIteratorIterator::CHILD_FIRST
72 );
73 } catch ( UnexpectedValueException $exception ) {
74 return false;
75 }
76
77 foreach ( $iterator as $item ) {
78 $path = $item->getPathname();
79 if ( $item->isDir() && ! $item->isLink() ) {
80 // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Best-effort cleanup; the result is checked below.
81 if ( ! @rmdir( $path ) && is_dir( $path ) ) {
82 return false;
83 }
84 // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Best-effort cleanup; the result is checked below.
85 } elseif ( ! @unlink( $path ) && file_exists( $path ) ) {
86 return false;
87 }
88 }
89
90 // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Best-effort cleanup; the result is checked below.
91 return @rmdir( $directory ) || ! is_dir( $directory );
92 }
93
94 /**
95 * Reset OPCache using different approach depending on the caller context (e.g. cron vs. web)
96 */
97 function gps_opcache_reset() {
98
99 if ( ! function_exists( 'opcache_reset' ) ) {
100 // Bail if extension is not loaded
101 return;
102 }
103
104 if ( empty( ini_get( 'opcache.enable' ) ) ) {
105 // Do not try doing anything if OPcache is loaded but disabled
106 return;
107 }
108
109 if ( ! empty( ini_get( 'opcache.restrict_api' ) ) && strpos( __FILE__, ini_get( 'opcache.restrict_api' ) ) !== 0 ) {
110 return;
111 }
112
113 // https://www.getpagespeed.com/server-setup/php/zend-opcache
114 // Follow the principles of reliable file cache clearing from this article
115 $file_cache_dir = ini_get( 'opcache.file_cache' );
116 // Check if file cache is enabled and delete it if enabled
117 if ( $file_cache_dir && is_writable( $file_cache_dir ) ) {
118 // check if we can create subdirectory in the parent directory.
119 // Normally, it's ~/.cache so we can
120 $cache_dir = dirname( $file_cache_dir );
121 if ( ! is_writable( $cache_dir ) ) {
122 $file_cache_dir = null;
123 }
124 }
125
126 $retired_cache_dir = $file_cache_dir ? "{$file_cache_dir}.rm" : null;
127 if ( $retired_cache_dir && file_exists( $retired_cache_dir ) ) {
128 gps_opcache_remove_directory( $retired_cache_dir );
129 }
130
131 if ( $file_cache_dir && $retired_cache_dir && file_exists( $file_cache_dir ) ) {
132 // move it out of the way to avoid race conditions
133 // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Cross-device failure has an in-place fallback below.
134 $cache_rotated = @rename( $file_cache_dir, $retired_cache_dir );
135 if ( ! $cache_rotated ) {
136 // Overlay/container filesystems can reject directory renames with EXDEV.
137 gps_opcache_remove_directory( $file_cache_dir );
138 }
139 }
140
141 // We are in PHP-FPM context and not file cache only
142 if ( php_sapi_name() !== 'cli' && ! ini_get( 'opcache.file_cache_only' ) ) {
143 opcache_reset();
144 }
145
146 if ( $file_cache_dir ) {
147 if ( $retired_cache_dir && file_exists( $retired_cache_dir ) ) {
148 gps_opcache_remove_directory( $retired_cache_dir );
149 }
150 // make sure OPcache directory is re-created
151 if ( ! file_exists( $file_cache_dir ) ) {
152 // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Another worker may recreate it concurrently.
153 @mkdir( $file_cache_dir, 0777, true );
154 }
155 }
156
157 // A CLI process cannot reset PHP-FPM's shared-memory cache directly, so send
158 // the reset to FPM over FastCGI using the socket from .cachetool.yml.
159 require_once __DIR__ . '/opcache-fastcgi.php';
160 $config_path = gps_find_cachetool_config();
161 if ( $config_path ) {
162 $config = gps_parse_cachetool_yml( $config_path );
163 if ( $config ) {
164 gps_fastcgi_opcache_reset( $config['fastcgi'], $config['fastcgi_chroot'] );
165 }
166 }
167 }
168
169
170 // Reset OPcache after plugin/theme/core updates (files are modified on disk).
171 // Note: Activation/deactivation/theme-switch don't need cache clear since files aren't modified.
172 add_action( 'upgrader_process_complete', 'gps_opcache_reset', PHP_INT_MAX - 1, 2 );
173
174 // Reset OPcache after a plugin is deleted/uninstalled (files are removed from disk).
175 // Important when opcache.validate_timestamps=0 to prevent serving cached deleted files.
176 add_action( 'deleted_plugin', 'gps_opcache_reset', PHP_INT_MAX - 1, 2 );
177