[wp-trac] [WordPress Trac] #63594: Bug/Broken State possible in Multisite Caching Code: update_network_option
WordPress Trac
noreply at wordpress.org
Wed Jun 18 15:04:40 UTC 2025
#63594: Bug/Broken State possible in Multisite Caching Code: update_network_option
--------------------------------+-----------------------------
Reporter: EliW | Owner: (none)
Type: defect (bug) | Status: new
Priority: normal | Milestone: Awaiting Review
Component: Options, Meta APIs | Version: 6.8
Severity: normal | Keywords:
Focuses: |
--------------------------------+-----------------------------
I ran into an issue at one point with the caching code and how the "when
to refresh from database" logic is handled.
Here is how to reproduce:
1. Have WordPress running with a Cache backend of your choice
2. Create/Update a network option via update_network_option() or
add_network_option()
3. Now, delete the network option directly from the database
It is now impossible to update the network option, via
update_network_option() ... until the cache is also cleared because of how
the login runs in the method. Basically the following happens:
First, it attempts to get the option:
{{{#!php
$old_value = get_network_option( $network_id, $option );
}}}
And it will only 'add' the option, if it didn't exist already:
{{{#!php
if ( $value === $old_value || maybe_serialize( $value ) ===
maybe_serialize( $old_value ) ) {
return false;
}
if ( false === $old_value ) {
return add_network_option( $network_id, $option, $value );
}
}}}
So far so good (in a way) ... but then the next section of the code is
where a problem happens ... here's that section as a whole:
{{{#!php
$notoptions_key = "$network_id:notoptions";
$notoptions = wp_cache_get( $notoptions_key, 'site-options' );
if ( is_array( $notoptions ) && isset( $notoptions[ $option ] ) )
{
unset( $notoptions[ $option ] );
wp_cache_set( $notoptions_key, $notoptions, 'site-options'
);
}
if ( ! is_multisite() ) {
$result = update_option( $option, $value, false );
} else {
$value = sanitize_option( $option, $value );
$serialized_value = maybe_serialize( $value );
$result = $wpdb->update(
$wpdb->sitemeta,
array( 'meta_value' => $serialized_value ),
array(
'site_id' => $network_id,
'meta_key' => $option,
)
);
if ( $result ) {
$cache_key = "$network_id:$option";
wp_cache_set( $cache_key, $value, 'site-options'
);
}
}
}}}
So the problem in above lies in this underlying condition ... following
the logic for at this point (for multisite specifically). If the cache
entry was found (which it was) ... it's going to immediately drop into
section that will be doing the {{{ $wpdb->update() }}} ... which is going
to fail because there is no option/row to update.
Since it failed, the rest of the function just ends up being skipped, and
it returns false.
So basically:
- update_network_option ... will in fact create the option if it didn't
exist already
- BUT: if an old/stale cached version of the data exists, it will still
try to 'update' the option instead ... which will fail since it doesn't
really exist in the database.
In my case this happened (and has happened) multiple times due to
restoring/automating some database updates, that didn't also flush the
cache.
There are a couple solutions to this. One would be to update this code to
use `wpdb->replace` instead of update ... but that's potentially a bigger
change.
A very simple solution to this would simply be to change this code:
{{{#!php
if ( $result ) {
$cache_key = "$network_id:$option";
wp_cache_set( $cache_key, $value, 'site-options'
);
}
}}}
To this:
{{{#!php
if ( $result ) {
$cache_key = "$network_id:$option";
wp_cache_set( $cache_key, $value, 'site-options'
);
} else {
return add_network_option( $network_id, $option,
$value );
}
}}}
Which would basically be one additional 'attempt'. That if the code gets
here, tries to 'update' the option and can't for some reason, it goes
"Huh, well, let's assume that failed because it didn't exist. And so lets
give a one-time chance at just adding this instead
--
Ticket URL: <https://core.trac.wordpress.org/ticket/63594>
WordPress Trac <https://core.trac.wordpress.org/>
WordPress publishing platform
More information about the wp-trac
mailing list