Repository navigation
Cache API: Do not cache a miss while cache addition is suspended. - #13340
jigneshbhavani wants to merge 1 commit into
Conversation
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the Core Committers: Use this line as a base for the props when committing in SVN: To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
Test using WordPress PlaygroundThe changes in this pull request can previewed and tested using a WordPress Playground instance. WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser. Some things to be aware of
For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation. |
|
cc @josephscott |
Are there drop-ins that only declare 3 params? The |
| && | ||
| ! is_numeric( $_network ) | ||
| ) { | ||
| $unusable_value_cached = ( false !== $_network ); |
There was a problem hiding this comment.
The naming of the $unusable_value_cached variable is misleading. Given that we're already in this branch, we already know it is not an unusable cached value since it checked the types of $_network already.
What this seems to be getting at is to check whether there was a cache hit or not. In that case, passing a variable by reference to wp_cache_hit() seems better, per above.
There was a problem hiding this comment.
@westonruter Fair. $found would also be the more accurate source than the name suggests: a persistent cache holding a literal false is a hit that false !== $_network reads as a miss.
Both are moot if the guard below loses its first condition, which I now think it should. Answered there.
| * Not wp_cache_add(), since an unusable cached value must be replaced. Replacing an | ||
| * existing entry is not an addition, so only a genuine miss defers to the flag. | ||
| */ | ||
| if ( $unusable_value_cached || ! wp_suspend_cache_addition() ) { |
There was a problem hiding this comment.
Why not this?
| if ( $unusable_value_cached || ! wp_suspend_cache_addition() ) { | |
| if ( ! wp_suspend_cache_addition() ) { |
If cache addition is suspended, why would this not always apply to setting as well?
There was a problem hiding this comment.
@westonruter You are right, and there is a caller that settles it. Health Check & Troubleshooting calls wp_suspend_cache_addition( true ) for the whole request while a troubleshooting session is active, commented "Attempt to avoid cache entries from a troubleshooting session". Replacing an entry during that session still writes to the shared cache, which is the thing that plugin is trying to avoid. Same code in WordPress/site-health-troubleshooting.
What the first condition was protecting is r63354's own stated reason for set(), that "the unusable value survived and every later lookup for that object queried the database again". Dropping it reinstates that inside suspended requests only, so on wordcamp.org's wp_suspend_cache_addition( true ) / switch_to_blog() import loop a poisoned entry would be re-queried per iteration. That does not outweigh honouring the flag.
Taking your version. It removes $unusable_value_cached from all five classes, and test_get_instance_replaces_a_poisoned_cache_value_while_cache_addition_is_suspended in wpPost.php goes with it. The five ..._does_not_cache_a_miss_while_cache_addition_is_suspended tests stay.
There was a problem hiding this comment.
(Please disclose every comment that is generated with AI.)
There was a problem hiding this comment.
@westonruter Understood, will do. Those four were drafted with Claude Code and reviewed by me before posting.
|
@westonruter Only one that I can find, and it is not a live case: Simple Cache's memcache drop-in, 1,000 installs and last updated in 2021. Everything current declares the fourth parameter, including the memcached drop-in bundled in The reason was weak anyway. Initialising |
[63354] replaced wp_cache_add() with wp_cache_set() in get_instance() on WP_Post, WP_Term, WP_Comment, WP_Site and WP_Network. Only add() checks wp_suspend_cache_addition(), so all five began caching a miss while additions are suspended. The check moves to the call site. See #66006.
03c8718 to
53b8ba8
Compare
| * Not wp_cache_add(), since an unusable cached value may still be present and must be | ||
| * replaced. add() checks wp_suspend_cache_addition() and set() does not, so the check | ||
| * moves to the call site. |
There was a problem hiding this comment.
| * Not wp_cache_add(), since an unusable cached value may still be present and must be | |
| * replaced. add() checks wp_suspend_cache_addition() and set() does not, so the check | |
| * moves to the call site. | |
| * Not wp_cache_add(), since an unusable cached value may still be present and must be replaced. | |
| * Note that wp_cache_add() checks wp_suspend_cache_addition() and wp_cache_set() does not. |
Could be applied to the other instances below as well.
|
Thank you for the ping. I've been thinking through what this could look like and ultimately given the past decisions and where WordPress core is right now I'm leaning towards utilizing Using Post as an example, it would be: $found = false;
$_post = wp_cache_get( $post_id, 'posts', false, $found );
// A cached value that is not usable as a post is treated as a cache miss.
if ( ! ( $_post instanceof stdClass || $_post instanceof WP_Post ) || ! isset( $_post->ID ) ) {
$_post = sanitize_post( $_post, 'raw' );
/*
* A cache result was found, but it was invalid. Remove it so that the correct value
* can be added. This pattern is used instead of a direct wp_cache_set() because
* wp_cache_add() will respect wp_suspend_cache_addition().
*/
if ( $found ) {
wp_cache_delete( $post_id, 'posts' );
}
wp_cache_add( (int) $_post->ID, $_post, 'posts' );
} elseif ( empty( $_post->filter ) || 'raw' !== $_post->filter ) {
$_post = sanitize_post( $_post, 'raw' );
}If we had something like |
|
@josephscott Perhaps but don't you think incorporating a |
|
It is an approach that would work. When I looked at the usage of Then I searched for I still think having a |
|
We should look into the history of |
That was the first thing I had wondered about to - and even considered updating Looking at |
wp_cache_add()returns early whenwp_suspend_cache_addition()is on andwp_cache_set()does not, so the swap in r63354 leftWP_Post,WP_Term,WP_Comment,WP_SiteandWP_Network::get_instance()writing to the object cache while additions are suspended.r63354 reached for
set()because an unusable cached value has to be replaced andadd()will not overwrite. That still works: only the suspension check moves to the call site, so a poisoned entry is replaced as before on a normal request, and nothing is written while the flag is on. Simplified per review; the first version also replaced while suspended.Measured on a running site with the cache cleared and the flag on: post, term and comment are PRESENT on trunk and MISS on this branch, with a plain
wp_cache_add()control MISS throughout. Each of the five guards is mutation checked, reverting one file fails exactly one test. Groups post, comment, taxonomy, cache, ms-site and ms-network are green, and the single taxonomy warning is present on clean trunk too. PHPCS and PHPStan clean.Trac ticket: https://core.trac.wordpress.org/ticket/66006
Use of AI Tools
AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 5
Used for: Drafting the fix and the regression tests, and running the reproduction, mutation and lint checks. Reviewed and verified by me before opening.