Repository navigation
Restart with OPcache's file cache when WP_CLI_OPCACHE is set - #6439
swissspidy wants to merge 4 commits into
Conversation
OPcache is off for the CLI by default, so every run compiles WP-CLI, WordPress and all active plugins again. With WP_CLI_OPCACHE set, WP-CLI restarts PHP once with OPcache's file cache enabled, in a directory in WP-CLI's cache, so compiled code is reused between runs. The restart uses composer/xdebug-handler, which keeps the current PHP settings and passes STDIN, STDOUT and the exit code through. It is skipped when OPcache isn't available, its file cache is already used, or Xdebug is active. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D26yjkN2BiqCXT6p6o1WqS
|
No actionable comments were generated in the recent review.
|
| Layer / File(s) | Summary |
|---|---|
Configure the OPcache restart composer.json, php/WP_CLI/OpcacheRestarter.php |
Adds composer/xdebug-handler and an OpcacheRestarter that adds CLI OPcache settings before delegating the restart. It skips restarting when Xdebug is active or the CLI file cache is already active. |
Activate and test the restart php/WP_CLI/Bootstrap/RestartWithOpcache.php, php/bootstrap.php, features/bootstrap.feature |
Adds a bootstrap step that checks the feature flag, OPcache availability, and cache-directory access. Registers the step and adds scenarios for OPcache behavior and configuration. |
Priority:
Estimated code review effort: 3 (Moderate) | ~20 minutes
Change: Feature
Sequence Diagram(s)
sequenceDiagram
participant Bootstrap as WP-CLI bootstrap
participant Step as RestartWithOpcache
participant Restarter as OpcacheRestarter
participant Handler as XdebugHandler
Bootstrap->>Step: process(BootstrapState)
Step->>Step: Check flag, OPcache, and cache directory
Step->>Restarter: check(cache directory)
Restarter->>Handler: Restart with OPcache CLI settings
Suggested labels: scope:framework, scope:testing
Suggested reviewers: schlessera
Merge Risk: 
Minimal · up to a27d9
-
No actionable merge-blocking issue is established. The change is ready for normal checks, though the phar restart path has not been verified.
Pre-merge checks: 
5
-
Full details: Docstring Coverage
-
Explanation
Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 3 functions across 3 files. (2 skipped: 2 unsupported.)
-
Finishing Touches: 💡 1
-
🛠️ Fix failing CI checks: 💡
-
- Commit to this branch
- Create a new PR
📝 Generate docstrings
-
- Commit to this branch
- Create a new PR
🧪 Generate unit tests (beta)
-
- Commit to this branch
- Create a new PR
-
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
Comment @coderabbitai help to get the list of available commands.
The step runs after the config is loaded, so a project can turn on the OPcache restart in its wp-cli.yml. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D26yjkN2BiqCXT6p6o1WqS
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D26yjkN2BiqCXT6p6o1WqS
The restarted process keeps the settings from php.ini, including OPcache settings that had no effect while OPcache was off for the CLI. Always keep doc comments, which commands are defined by, and validate file timestamps. Don't restart when opcache.enable is turned off, as nothing would be cached. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D26yjkN2BiqCXT6p6o1WqS
Each
wprun compiles WP-CLI, WordPress and all active plugins again. PHP's own default foropcache.enable_cliis0, though distributions, Docker images and hosts may turn it on. But even with it on, OPcache keeps compiled code in shared memory, which only lasts for one process. Only the file cache (opcache.file_cache) keeps it across runs, and it isn't set by default.With
WP_CLI_OPCACHE=1, WP-CLI now restarts PHP once with the file cache enabled. Compiled code is then stored on disk and reused by the next run.This is the idea behind PHPStan always running with OPcache (PHPStan 2.3). It's opt-in for now, as a prototype.
How it works
Turning it on: set
WP_CLI_OPCACHEas an environment variable, or in theenv:section of a config file:New bootstrap step:
RestartWithOpcacheruns after the config is loaded and the logger and formatter are set up. That's before the shutdown handler, the root check,--requirefiles and packages, so those only run in the restarted process.composer/xdebug-handler does the restart (new dependency,
^3.0.3). The bundle already ships it viacomposer/composer. It:php.iniwith the current settings, including-doptions andWP_CLI_PHP_ARGS;OpcacheRestarterextends it:requiresRestart()decides when to restart, andrestart()adds the OPcache options.Only the classes the restart needs are loaded before restarting (
Composer\XdebugHandler,Composer\Pcre), through WP-CLI's own autoloader. Loading the Composer autoloader there would also include every bundled command's registration file in a process that's about to be replaced. With it, the parent took ~280 ms before restarting; now it's ~15 ms.When it restarts: whenever the file cache isn't in use yet. That includes PHP setups that already have
opcache.enable_cli=1without a file cache.Skipped when:
WP_CLI_OPCACHEisn't set, or OPcache isn't loaded;opcache.enable=0(it defaults to1, so this is a deliberate opt-out, and nothing would be cached);php.ini;proc_open()is disabled.WP-CLI then runs as before.
Subprocesses: processes WP-CLI launches itself (
launch_self(),runcommand()withlaunch) restart in turn. The restarted process clears xdebug-handler's restart marker, so they don't mistake themselves for a restarted process.OPcache settings
The restarted process keeps the user's other settings. That includes OPcache settings from
php.inithat had no effect while OPcache was off for the CLI, for example ones tuned for PHP-FPM. So these are always set:opcache.enable_cli=1opcache.file_cache=<cache dir>/opcacheWP_CLI_CACHE_DIR(environment or config) or~/.wp-cli/cache.opcache/is created with0700: a cache directory other users can write to would let them inject compiled code.opcache.file_cache_only=1opcache.preload) isn't run in the CLI. JIT needs shared memory, so it's off; it made no difference for WP-CLI here.opcache.save_comments=10,CommandFactoryfalls back to re-reading each command's source file, and plugins that read doc comments through reflection would break.opcache.validate_timestamps=1wp plugin update. With0, edited files were still picked up in my tests, so this is defensive.opcache.memory_consumption,opcache.max_accelerated_filesand other shared-memory settings don't apply withfile_cache_only.opcache.blacklist_filenameis kept fromphp.ini.Results
hyperfine, 20 runs after 3 warmups, PHP 8.3, nightly bundle run from source, on a site with WooCommerce, Yoast, Elementor, ACF and CF7. OPcache is loaded with PHP's defaults, so
opcache.enable_cli=0:WP_CLI_OPCACHE=1option get siteurloption get siteurl --skip-plugins --skip-themescli versioncli versionorhelp, are therefore slower with it. That's one reason to keep it opt-in, or to only restart for commands that load WordPress.option get siteurlwent from 1.19 s to 0.57 s. I haven't run this PR's restart from a phar build.OPcache modes for
option get siteurl --skip-plugins --skip-themes, with the options passed directly (no restart), 15 runs:opcache.enable_cli=1only (shared memory)file_cache_only=1(what the restart uses)So setups that already have
opcache.enable_cli=1without a file cache gain the most: every run pays for OPcache's optimizer and keeps nothing, which is twice as slow as having it off.Open questions
php/wp-cli.php, would save another ~10 ms of the parent's work (compilingutils.phpandclass-wp-cli.php). But config files wouldn't be read yet, and it would need its own handling of the cache directory and autoloading.WP_CLI_EARLY_REQUIREfiles run before the bootstrap, so they run in both processes.wp cli infocould report whether the file cache is in use.wp cli cache clearwould delete them along with the rest of the cache.Tests
features/bootstrap.feature:WP_CLI_OPCACHE=1, it checks that the restarted process uses the file cache in<cache dir>/opcache, that STDIN and amemory_limitfromWP_CLI_PHP_ARGSreach it, and that its exit code (3) comes back.opcache.save_comments=0andopcache.validate_timestamps=0in the PHP settings, the restarted process still has both on.opcache.enable=0, there's no restart.wp-cli.yml.@require-extension-*can't be used). It fails without the new step, and thesave_commentscheck fails without the forced settings.runcommand()subprocess withlaunchrestarts too;-d opcache.enable_cli=1already set, it still restarts and ends up with the file cache;0700;file_cache_only, a configuredopcache.preloadscript doesn't run, while it does with shared memory.bootstrap,runner,cli-info,cliandhelppass, apart from 3 scenarios that also fail onmainin this environment (Composer installs blocked by the network, and early root detection while running as root).FileCacheTestfailures that also happen onmainwhen run as root. PHPCS is clean, and PHPStan reports no new errors.🤖 Generated with Claude Code
https://claude.ai/code/session_01D26yjkN2BiqCXT6p6o1WqS
Summary by CodeRabbit
WP_CLI_OPCACHE, including when the setting is provided throughwp-cli.yml.