Skip to content

Add the MAx Cache extension - #1409

Open
tkalimullin-cloudlinux wants to merge 1 commit into
BoldGrid:masterfrom
tkalimullin-cloudlinux:maxcache-extension-upstream
Open

tkalimullin-cloudlinux wants to merge 1 commit into
BoldGrid:masterfrom
tkalimullin-cloudlinux:maxcache-extension-upstream

Conversation

@tkalimullin-cloudlinux

@tkalimullin-cloudlinux tkalimullin-cloudlinux commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Description

Adds an extension that serves the Disk: Enhanced page cache through the CloudLinux MAx Cache module (mod_maxcache on Apache, ngx_http_maxcache_module on NGINX) where it is installed, answering a cached request before PHP starts. How the cache is built does not change - the same files, in the same place, with the same names; only the last step, handing a cached file to the visitor, moves to a server module written for that one job.

Where the module is present and the configuration is one it can reproduce, it appears on the Extensions page and, once turned on, a <IfModule maxcache_module> block is added to the file W3TC already writes, describing the cache path, exclusions and encoding suffixes, and the corresponding RewriteRule/RewriteCond lines are withheld through the existing w3tc_pgcache_rules_required filter: exactly one component ever answers a request. Turning it off, removing the module from the host, or changing the configuration into one it can no longer reproduce reverses that on its own, without further action.

A configuration it cannot reproduce exactly is refused rather than approximated, with the reason shown on the Extensions page - among them: Page Cache not set to Disk: Enhanced, multisite, an older module version than the integration needs, a Browser Cache setting the module cannot carry
on what it serves (an expiry header, a dropped ETag, Cache-Control), a cache directory outside what the web server can address, and a page-cache layout (query strings, dynamic cookies, file name) the directive block cannot express as written.

Two helpers already used internally by W3TC's own rule generation (PgCache_Environment::reject_cookies(), reject_user_agents()) are made public so the extension names the same visitors W3TC's own rules would, rather than keeping a second copy that could drift. On NGINX, where the module reads its configuration from shared memory rather than from .htaccess, a short-lived local socket message tells the configuration daemon the file changed; a daemon that is not there is simply an unsuccessful notification.

How to test

The extension looks for two things: a file saying the module is installed, and a second one saying it is new enough to expand W3TC's cache-file tokens - support for that landed in 1.2.6, which is also the version currently shipping. On Apache those are .version/.version-1.2.6; on NGINX, .nginx-version/.nginx-version-1.2.6; both under /opt/cloudlinux/maxcache/. Installing the module on CloudLinux OS (yum install ea-apache24-mod_maxcache / ea-nginx-maxcache) writes both. Anywhere else, faking them works the same way:
mkdir -p /opt/cloudlinux/maxcache touch /opt/cloudlinux/maxcache/.version /opt/cloudlinux/maxcache/.version-1.2.6
the extension offers itself identically either way; only the final delivery differs.

  1. With Page Cache on Disk: Enhanced, reload the W3TC Extensions page - MAx Cache is listed and enabled.
  2. Turn it on. .htaccess gains a block naming the cache path and exclusions; W3TC's own RewriteRule lines for page cache are withheld.
  3. Remove the version file (or uninstall the module) and load any admin page - the block is removed and W3TC's own rules return, with no setting touched.
  4. Set Page Cache to a mode other than Disk: Enhanced - the extension reports why it is unavailable instead of writing a block it cannot honor.

New dependencies

None - no new Composer package, no HTTP request, no cron job.


Note

High Risk
Changes who serves page cache and mutates .htaccess/rule ownership; mis-handoff or a bad directive could break caching or the whole site on Apache line-limit errors, though the gate refuses unsafe configs.

Overview
Adds a MAx Cache extension that, on supported CloudLinux hosts, writes a marked <IfModule maxcache_module> block into the same rules file W3TC already manages and withholds W3TC’s Disk: Enhanced rewrite rules via w3tc_pgcache_rules_required when delivery is confirmed—so only one layer serves cached HTML.

The integration includes eligibility gating (host/module version, multisite, Browser Cache quirks, directive size/pattern safety, foreign blocks in .htaccess), NGINX daemon reload over a unix socket when rules change, admin Extensions listing and dismissible notes, and refactors PgCache_Environment::reject_cookies() / reject_user_agents() (plus public apache_cache_uri_path()) so module directives match W3TC’s own exclusions.

Also fixes Util_WpFile::write_to_file() treating an empty successful write as failure, and adds standalone tests for block lifecycle, preview/saved config, and safety invariants.

Reviewed by Cursor Bugbot for commit ffc490b. Bugbot is set up for automated code reviews on this repo. Configure here.

@tkalimullin-cloudlinux
tkalimullin-cloudlinux force-pushed the maxcache-extension-upstream branch 2 times, most recently from 3c8b41b to 211ccfa Compare October 2, 2026 14:30
@tkalimullin-cloudlinux
tkalimullin-cloudlinux marked this pull request as ready for review October 5, 2026 13:56
@tkalimullin-cloudlinux
tkalimullin-cloudlinux requested a review from a team October 5, 2026 13:56

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread Extension_MaxCache_Plugin.php
@tkalimullin-cloudlinux
tkalimullin-cloudlinux force-pushed the maxcache-extension-upstream branch from 211ccfa to 919241d Compare October 5, 2026 18:00

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

}

return '';
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Both builds refuse nginx requests

Medium Severity

When both MAx Cache builds are installed, get_mode() locks to apache, so is_nginx() is false. A request whose SERVER_SOFTWARE is nginx then fails get_web_server_unsupported_reason() even though nginx_module_installed() is true. The extension reports it cannot serve and never falls back to the nginx build, which is the cPanel/CloudLinux case the notify path already special-cases.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 919241d. Configure here.

Comment thread Extension_MaxCache_Core.php
@tkalimullin-cloudlinux
tkalimullin-cloudlinux force-pushed the maxcache-extension-upstream branch from 919241d to d075e55 Compare October 5, 2026 18:28

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread Extension_MaxCache_Plugin.php
Comment thread Extension_MaxCache_Environment.php
@tkalimullin-cloudlinux
tkalimullin-cloudlinux force-pushed the maxcache-extension-upstream branch from d075e55 to 739f97b Compare October 5, 2026 18:38

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread Extension_MaxCache_Plugin.php Outdated
@tkalimullin-cloudlinux
tkalimullin-cloudlinux force-pushed the maxcache-extension-upstream branch from 739f97b to afd0c6c Compare October 5, 2026 18:53

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

There are 3 total unresolved issues (including 1 from previous review).

Fix All in Cursor

Bugbot Autofix is ON, but it could not run because the branch was deleted or merged before autofix could start.

Reviewed by Cursor Bugbot for commit afd0c6c. Configure here.

Comment thread Extension_MaxCache_Core.php
if ( \is_admin() || \wp_doing_cron() || ( \defined( 'WP_CLI' ) && WP_CLI ) ) {
// config_save() drives both passes with a Config instance no earlier refresh has seen; re-entrant by design.
Extension_MaxCache_Core::refresh( $w3tc_config );
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cron refresh stores torn-file verdict

Low Severity

The rules filter calls refresh() on cron and WP-CLI, and refresh() writes whatever deep_unsupported_reason() sees into the hour-long transient. A concurrent .htaccess write can look like an unfinished MAx Cache block, so that torn read is stored as blocked. Front-end requests then treat the site as unsupported for up to an hour.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit afd0c6c. Configure here.

…he module

Gate + rules writer + admin notices + NGINX daemon notify (Extension_MaxCache_*).
PgCache_Environment's cookie/UA/cache-path helpers made public for reuse.
Util_WpFile: file_put_contents() returning 0 read as failure - fixed.

@cssjoe cssjoe left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the extension. Not ready to merge on ffc490b: three findings, highest first.

I ran the standalone suite on that commit (php tests/test-maxcache-core.php, 32 passed, and php tests/test-pgcache-rules-required-filter.php, 10 passed). Travis, phpcs, semgrep, and conftest are green.

1. High — Disk: Enhanced rules stay after the module block is written

w3tc_pgcache_rules_required in Extension_MaxCache_Plugin.php (around line 56) withholds W3TC rewrite rules only when is_enabled() and is_delivering() are both true. Root_Environment runs PgCache_Environment before the MAx Cache listener (priority 100). On the pass that first writes the block, delivery is still false, so the Disk: Enhanced block is written, and then note_delivering(true) in Extension_MaxCache_Environment.php (around line 211) records that the module is serving. Nothing in that request removes the Disk: Enhanced block.

The next removal is a later wp-admin rules pass, and only when "Verify rewrite rules" is on and the request is a W3TC admin screen or plugins.php. With that checkbox off, both blocks stay until the next settings save.

Activation is the sharp case. Extensions_Util::activate_extension() calls Config::save(), which does not run the environment fix, and the handler redirects before admin_notices. The extensions page that follows is the request that writes both blocks.

A local check seeded the W3TC page-cache core markers, called fix_on_event('config_change'), and found both the core markers and the MAx Cache markers still in the file while the filter already returned false. Test [19] checks that filter return value and does not check that the earlier block was removed.

Fix: after note_delivering(true), remove the page-cache rewrite rules in that same request.

Test: seed the core markers, run handlers in Root_Environment order, and assert that only the MAx Cache block remains.

2. Medium — nginx request refused when both builds are installed

Extension_MaxCache_Core::get_mode() (around line 149) locks to apache when both builds are installed, so is_nginx() is false. get_web_server_unsupported_reason() (around line 243) then refuses a request whose SERVER_SOFTWARE is nginx, even though the nginx build is installed. Test [3] pins the Apache-only build.

Fix: when the nginx build is installed and the live server header is nginx, treat that request as nginx.

Test: set both install flags and SERVER_SOFTWARE to nginx, and expect a supported verdict.

3. Low — cron refresh can store an unfinished-file refusal for an hour

On cron and WP-CLI, w3tc_pgcache_rules_required calls refresh() (Extension_MaxCache_Plugin.php around line 51). refresh() stores whatever deep_unsupported_reason() returns (Extension_MaxCache_Core.php around line 667), including a rules file that has the begin marker and no end marker (foreign_block_reason() around line 437). That blocked row is kept for an hour. Test [15] reads get_unsupported_reason() against an already-good transient and does not call refresh() after the partial write.

Fix: do not replace a clear stored verdict with an unfinished-block read.

Test: store a clear verdict, write a begin-without-end file, call refresh() the way the cron filter does, and assert the stored row stays unblocked.

Happy to re-review once these are updated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants