Skip to content

Boost: fully remove boost-cache directory on uninstall and make cleanup resilient on very large caches#49546

Draft
kraftbj wants to merge 4 commits into
trunkfrom
claude/boost-audit-prioritize-09rf6a-page-cache-cleanup
Draft

Boost: fully remove boost-cache directory on uninstall and make cleanup resilient on very large caches#49546
kraftbj wants to merge 4 commits into
trunkfrom
claude/boost-audit-prioritize-09rf6a-page-cache-cleanup

Conversation

@kraftbj

@kraftbj kraftbj commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Fixes #41982
Fixes #41983

Proposed changes

  • Fix Fix cache garbage collection #41982: uninstalling Boost now removes wp-content/boost-cache/ completely — all cache files, index.html placeholder files, the cache/, logs/ and static/ subdirectories, and the boost-cache/ directory itself. Root cause: Page_Cache_Setup::uninstall() reused the garbage-collection-oriented Simple_Delete path action, which deliberately skips index.html placeholders and calls Logger::debug() per entry — when debug logging is enabled those writes recreate boost-cache/logs/log-<date>.log.php while cleanup is running, so logs/ (and therefore boost-cache/) are never empty and never removed.
  • Fix Boost: if there are too many files in the boost-cache directory, uninstall "fails" #41983: cleanup no longer hangs/fails on very large caches (reported at ~400MB). The new Filesystem_Utils::delete_directory() streams the tree with RecursiveDirectoryIterator + CHILD_FIRST, deleting entries as it goes (constant memory, no in-memory file lists), lifts the PHP execution time limit where permitted, skips per-file logging, and suppresses per-entry errors so a single locked file doesn't abort the whole cleanup.
  • Safety preserved: the helper validates via realpath + is_boost_cache_directory() and refuses to delete anything outside wp-content/boost-cache; an already-missing directory is treated as success. The helper lives in the pre-WordPress layer and uses SPL only (no WP APIs, no new files to require_once).
  • Adds unit tests for full-tree removal (including placeholders, logs, nested dirs), deep (30-level) and many-file (20×50) trees, outside-path refusal, and missing-directory handling.

Related product discussion/links

Does this pull request change what data or activity we track or use?

No.

Testing instructions

  1. On a test site, install and activate Jetpack Boost, enable Page Cache (permalinks enabled), and browse several pages logged-out to populate wp-content/boost-cache/cache/. Optionally enable cache debug logging so boost-cache/logs/ contains log files.
  2. Confirm wp-content/boost-cache/ contains cache/, logs/, index.html placeholders in each directory, and cached .html files.
  3. For the large-cache case, generate many cache files, e.g.:
    mkdir -p wp-content/boost-cache/cache/stress && for i in $(seq 1 200); do mkdir wp-content/boost-cache/cache/stress/$i; for j in $(seq 1 200); do echo x > wp-content/boost-cache/cache/stress/$i/$j.html; done; done
    (~40k files; scale to taste.)
  4. Deactivate Jetpack Boost from Plugins, then click Delete (uninstall).
  5. Verify deletion completes without hanging or an error in plugins.php, and that wp-content/boost-cache/ no longer exists at all (no leftover cache/, logs/, static/, or index.html).
  6. Verify wp-content/advanced-cache.php is removed and Boost's WP_CACHE define is gone from wp-config.php.
  7. Unit tests: composer install --working-dir=projects/plugins/boost && cd projects/plugins/boost && vendor/bin/phpunit-select-config phpunit.#.xml.dist --bootstrap tests/bootstrap.php --testsuite unit --filter Filesystem_Utils_Test (full Boost unit suite passing: 118 tests, 337 assertions).

https://claude.ai/code/session_01PgpTrtTCH4hpz6Krh3ssho


Generated by Claude Code

Fixes two Page Cache uninstall issues (#41982, #41983):

- The uninstall cleanup used Filesystem_Utils::iterate_directory() with a
  Simple_Delete action, which deliberately preserves index.html placeholder
  files and only removes directories it considers empty, and logs every
  deletion via Logger::debug(). The logging could recreate the log file in
  boost-cache/logs/ while the cleanup was still running, leaving the logs/
  and cache/ directories, index.html placeholders, and the boost-cache/
  directory itself behind after uninstall.

- Add a dedicated Filesystem_Utils::delete_directory() (pre-WordPress safe)
  which streams over the tree with RecursiveDirectoryIterator/CHILD_FIRST,
  deletes every file (including index.html) and directory as it is visited
  without building in-memory file lists, lifts the PHP time limit when
  allowed, and suppresses per-entry errors so a single failure does not
  abort the cleanup. This keeps uninstall from hanging or timing out on
  very large caches (e.g. hundreds of MB of cache files).

- Page_Cache_Setup::uninstall() now uses delete_directory(), and still
  refuses to touch anything outside wp-content/boost-cache.

- Add unit tests covering full tree removal (cache/, logs/, static/,
  nested dirs, index.html placeholders), a deep/many-file tree, path
  validation, and an already-missing directory. Also define a fallback
  WP_CONTENT_DIR in the test so it can run standalone with --filter.

https://claude.ai/code/session_01PgpTrtTCH4hpz6Krh3ssho
@kraftbj kraftbj added [Status] In Progress [Plugin] Boost A feature to speed up the site and improve performance. [Tests] Includes Tests [Boost Feature] Page Cache labels Jun 11, 2026 — with Claude
@github-actions

Copy link
Copy Markdown
Contributor

Thank you for your PR!

When contributing to Jetpack, we have a few suggestions that can help us test and review your patch:

  • ✅ Include a description of your PR changes.
  • ✅ Add a "[Status]" label (In Progress, Needs Review, ...).
  • ✅ Add testing instructions.
  • ✅ Specify whether this PR includes any changes to data or privacy.
  • ✅ Add changelog entries to affected projects

This comment will be updated as you work on your PR and make changes. If you think that some of those checks are not needed for your PR, please explain why you think so. Thanks for cooperation 🤖


Follow this PR Review Process:

  1. Ensure all required checks appearing at the bottom of this PR are passing.
  2. Make sure to test your changes on all platforms that it applies to. You're responsible for the quality of the code you ship.
  3. You can use GitHub's Reviewers functionality to request a review.
  4. When it's reviewed and merged, you will be pinged in Slack to deploy the changes to WordPress.com simple once the build is done.

If you have questions about anything, reach out in #jetpack-developers for guidance!


Boost plugin:

No scheduled milestone found for this plugin.

If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack.

@jp-launch-control

jp-launch-control Bot commented Jun 11, 2026

Copy link
Copy Markdown

Code Coverage Summary

Coverage changed in 1 file.

File Coverage Δ% Δ Uncovered
projects/plugins/boost/app/modules/optimizations/page-cache/pre-wordpress/class-filesystem-utils.php 88/106 (83.02%) 5.80% 0 💚

Full summary · PHP report · JS report

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves Jetpack Boost Page Cache uninstall cleanup by replacing the previous GC-oriented deletion path with a purpose-built directory remover that fully deletes wp-content/boost-cache/ (including index.html placeholders and subdirectories) and is designed to remain performant on very large caches. It also adds PHPUnit coverage and a changelog entry.

Changes:

  • Add Filesystem_Utils::delete_directory() to delete the entire cache tree efficiently during uninstall.
  • Update Page_Cache_Setup::uninstall() to call the new deletion helper.
  • Add unit tests covering full-tree removal, deep/many-file trees, outside-path refusal, and missing-directory handling.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
projects/plugins/boost/app/modules/optimizations/page-cache/pre-wordpress/class-filesystem-utils.php Adds a new deletion helper intended for full-tree uninstall cleanup.
projects/plugins/boost/app/modules/optimizations/page-cache/class-page-cache-setup.php Switches uninstall from iterate_directory + Simple_Delete to delete_directory.
projects/plugins/boost/tests/php/modules/optimizations/page-cache/Filesystem_Utils_Test.php Adds unit tests for the new delete behavior and edge cases.
projects/plugins/boost/changelog/fix-page-cache-uninstall-cleanup Adds a patch changelog entry describing the uninstall cleanup fix.

Comment thread projects/plugins/boost/changelog/fix-page-cache-uninstall-cleanup Outdated
claude added 2 commits June 11, 2026 11:59
- delete_directory() now validates the resolved path against the real
  boost-cache root itself instead of relying on validate_path(), whose
  is_boost_cache_directory() substring match would also accept sibling
  paths like boost-cache-old. Only the cache root or paths inside it are
  accepted, compared on realpath()ed values.
- Wrap the recursive iteration in try/catch so an unreadable subdirectory
  surfaces as a Boost_Cache_Error instead of an uncaught exception during
  uninstall.
- Add a regression test covering the boost-cache-old sibling case, and
  reword the changelog entry in imperative mood.

https://claude.ai/code/session_01PgpTrtTCH4hpz6Krh3ssho
…ubdirectory

The unreadable-subdirectory test exercises the Throwable catch added in
review; it skips under root, where permission bits are not enforced.

https://claude.ai/code/session_01PgpTrtTCH4hpz6Krh3ssho
Code review (ce-code-review + two Codex adversarial passes) found a
symlink-escape: if WP_CONTENT_DIR/boost-cache were a symlink, realpath()
resolved both the input path and the cache-root guard to the same external
target, so the containment check passed and uninstall would recursively
delete files OUTSIDE the cache tree.

Reject a symlinked cache root up front with is_link() on the literal path,
before realpath() follows it. Symlinks encountered inside the tree are
already handled correctly (unlinked, never followed) by the deletion loop.

Add tests:
- a symlink inside the tree is unlinked without deleting its target
- a symlinked cache root is refused with invalid-directory
- the post-iteration rmdir() failure path returns could-not-delete-directory
  distinctly from the iterator-throw path (read-only parent)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Boost: if there are too many files in the boost-cache directory, uninstall "fails" Fix cache garbage collection

3 participants