[wp-trac] [WordPress Trac] #52798: delete_option() does not clear cache if option is missing in database.
WordPress Trac
noreply at wordpress.org
Fri Mar 12 19:31:51 UTC 2021
#52798: delete_option() does not clear cache if option is missing in database.
--------------------------------+-----------------------------
Reporter: emrikol | Owner: (none)
Type: defect (bug) | Status: new
Priority: normal | Milestone: Awaiting Review
Component: Options, Meta APIs | Version: trunk
Severity: minor | Keywords: 2nd-opinion
Focuses: |
--------------------------------+-----------------------------
It's an old one, but this is the mirror of #25015.
If, for some reason, an object does not exist in the database, but does
exist in persistent object cache, calling `delete_option()` will fail and
the cached option will continue to exist:
{{{
wp> get_option( 'test-option' );
=> bool(false)
wp> update_option( 'test-option', 'example', false );
=> bool(true)
wp> get_option( 'test-option' );
=> string(7) "example"
wp> global $wpdb;
wp> $wpdb->get_results( 'SELECT * FROM wp_options WHERE option_name="test-
option"' );
=> array(1) {
[0]=>
object(stdClass)#1977 (4) {
["option_id"]=>
string(3) "121"
["option_name"]=>
string(11) "test-option"
["option_value"]=>
string(7) "example"
["autoload"]=>
string(2) "no"
}
}
wp> $wpdb->delete( $wpdb->options, array( 'option_name' => 'test-option' )
);
=> int(1)
wp> $wpdb->get_results( 'SELECT * FROM wp_options WHERE option_name="test-
option"' );
=> array(0) {
}
wp> get_option( 'test-option' );
=> string(7) "example"
wp> delete_option( 'test-option' );
=> bool(false)
wp> get_option( 'test-option' );
=> string(7) "example"
}}}
I know what you're thinking, "Don't ever delete directly on the database."
But recently when I ran into this, manual deletion wasn't the cause. More
than likely it was some sort of race condition between distributed
database or cache servers, or other arcane edge cases. The end result
though was that the cache and database were out of sync and subsequent
automated `delete_option()` calls were silently failing to clear the
cache.
The easiest solution would be to move the cache deletion before the
database check:
{{{
diff --git a/src/wp-includes/option.php b/src/wp-includes/option.php
index 8692db7199..2f4a000ca1 100644
--- a/src/wp-includes/option.php
+++ b/src/wp-includes/option.php
@@ -648,6 +648,18 @@ function delete_option( $option ) {
wp_protect_special_option( $option );
+ if ( ! wp_installing() ) {
+ if ( 'yes' === $row->autoload ) {
+ $alloptions = wp_load_alloptions( true );
+ if ( is_array( $alloptions ) && isset(
$alloptions[ $option ] ) ) {
+ unset( $alloptions[ $option ] );
+ wp_cache_set( 'alloptions', $alloptions,
'options' );
+ }
+ } else {
+ wp_cache_delete( $option, 'options' );
+ }
+ }
+
// Get the ID, if no ID then return.
$row = $wpdb->get_row( $wpdb->prepare( "SELECT autoload FROM
$wpdb->options WHERE option_name = %s", $option ) );
if ( is_null( $row ) ) {
@@ -665,18 +677,6 @@ function delete_option( $option ) {
$result = $wpdb->delete( $wpdb->options, array( 'option_name' =>
$option ) );
- if ( ! wp_installing() ) {
- if ( 'yes' === $row->autoload ) {
- $alloptions = wp_load_alloptions( true );
- if ( is_array( $alloptions ) && isset(
$alloptions[ $option ] ) ) {
- unset( $alloptions[ $option ] );
- wp_cache_set( 'alloptions', $alloptions,
'options' );
- }
- } else {
- wp_cache_delete( $option, 'options' );
- }
- }
-
if ( $result ) {
/**
}}}
but that's also before the `delete_option` hook. Instead, the cache
purging logic block could be duplicated right before returning `false`:
{{{
diff --git a/src/wp-includes/option.php b/src/wp-includes/option.php
index 8692db7199..ab0a3f79f2 100644
--- a/src/wp-includes/option.php
+++ b/src/wp-includes/option.php
@@ -651,6 +651,19 @@ function delete_option( $option ) {
// Get the ID, if no ID then return.
$row = $wpdb->get_row( $wpdb->prepare( "SELECT autoload FROM
$wpdb->options WHERE option_name = %s", $option ) );
if ( is_null( $row ) ) {
+
+ if ( ! wp_installing() ) {
+ if ( 'yes' === $row->autoload ) {
+ $alloptions = wp_load_alloptions( true );
+ if ( is_array( $alloptions ) && isset(
$alloptions[ $option ] ) ) {
+ unset( $alloptions[ $option ] );
+ wp_cache_set( 'alloptions',
$alloptions, 'options' );
+ }
+ } else {
+ wp_cache_delete( $option, 'options' );
+ }
+ }
+
return false;
}
}}}
but that's not very clean, with that much duplicate code.
I'm also open to suggestions if this is even a "core" bug, or if it should
be the responsibility of the theme/plugin code to make sure the option
cache is properly cleared?
--
Ticket URL: <https://core.trac.wordpress.org/ticket/52798>
WordPress Trac <https://core.trac.wordpress.org/>
WordPress publishing platform
More information about the wp-trac
mailing list