Make WordPress Core

Opened 10 years ago

Closed 10 years ago

#25883 closed defect (bug) (fixed)

get_site_option cache logic for notoptions is incorrect

Reported by: broveloper's profile broveloper Owned by: wonderboymusic's profile wonderboymusic
Milestone: 3.7.2 Priority: normal
Severity: major Version: 3.7
Component: Options, Meta APIs Keywords: has-patch commit fixed-major
Focuses: multisite, performance Cc:

Description

The core function get_site_option has incorrect logic for wp_cache_get(set) for "notoptions".

Note: This is a multisite bug
Example is if you have 2 networks (A,B) and network(A) has a sitemeta record "testmetakey" and network(B) does not. If network(B) should happen to be requested first, then it save the "testmetakey" into the "notoptions" array cache. Then when network(A) makes a request for "testmetakey", it will find that "testmetakey" is in the "notoptions" cached array, and thus return nothing even though it does have a meta value for "testmetakey". Case and point is "active_sitewide_plugins"

function get_site_option( $option, $default = false, $use_cache = true ) {
        global $wpdb;

        // Allow plugins to short-circuit site options.
        $pre = apply_filters( 'pre_site_option_' . $option, false );
        if ( false !== $pre )
                return $pre;

        // prevent non-existent options from triggering multiple queries
        $notoptions = wp_cache_get( 'notoptions', 'site-options' );
        if ( isset( $notoptions[$option] ) )
                return apply_filters( 'default_site_option_' . $option, $default );

        if ( ! is_multisite() ) {
                $default = apply_filters( 'default_site_option_' . $option, $default );
                $value = get_option($option, $default);
        } else {
                $cache_key = "{$wpdb->siteid}:$option";
                if ( $use_cache )
                        $value = wp_cache_get($cache_key, 'site-options');

                if ( !isset($value) || (false === $value) ) {
                        $row = $wpdb->get_row( $wpdb->prepare("SELECT meta_value FROM $wpdb->sitemeta WHERE meta_key = %s AND site_id = %d", $option, $wpdb->siteid ) );

                        // Has to be get_row instead of get_var because of funkiness with 0, false, null values
                        if ( is_object( $row ) ) {
                                $value = $row->meta_value;
                                $value = maybe_unserialize( $value );
                                wp_cache_set( $cache_key, $value, 'site-options' );
                        } else {
                                $notoptions[$option] = true;
                                wp_cache_set( 'notoptions', $notoptions, 'site-options' );
                                $value = apply_filters( 'default_site_option_' . $option, $default );
                        }
                }
        }

        return apply_filters( 'site_option_' . $option, $value );
}

The cache_key should be using the same logic for options that are not in "notoptions":

$cache_key = "{$wpdb->siteid}:notoptions";

Patch would be:
line 760:

$notoptions = wp_cache_get( "{$wpdb->siteid}:notoptions", 'site-options' );

line 782:

wp_cache_set( "{$wpdb->siteid}:notoptions", $notoptions, 'site-options' );

Or set the notoption's cache key in some variable.

Attachments (1)

25883.diff (2.7 KB) - added by wonderboymusic 10 years ago.

Download all attachments as: .zip

Change History (9)

#1 @nacin
10 years ago

  • Milestone changed from Awaiting Review to 3.7.2
  • Version changed from 3.7.1 to 3.7

Yeah, that cache key needs to be keyed by $wpdb->siteid.

#2 @wonderboymusic
10 years ago

  • Keywords has-patch commit added; needs-patch removed

25883.diff does this

#3 @wonderboymusic
10 years ago

  • Owner set to wonderboymusic
  • Resolution set to fixed
  • Status changed from new to closed

In 26304:

Prefix the notoptions cache key in the multisite site-options group with $wpdb->siteid to avoid collisions and race conditions when using a fancy multi-network setup. Adds unit test.

Fixes #25883.

#4 @wonderboymusic
10 years ago

In 26305:

Mark test skipped when not in multisite. See #25883.

#5 @nacin
10 years ago

  • Keywords fixed-major added
  • Resolution fixed deleted
  • Status changed from closed to reopened

Reopening for 3.7.2.

#6 @jeremyfelt
10 years ago

  • Component changed from Multisite to Options and Meta
  • Focuses multisite performance added

#7 @GaryJ
10 years ago

Is milestone 3.7.2 still valid?

#8 @nacin
10 years ago

  • Resolution set to fixed
  • Status changed from reopened to closed

In 27888:

Prefix the notoptions cache key in the multisite site-options group with $wpdb->siteid to avoid collisions.

Merges [26304] (and [26305]) from 3.8 to the 3.7 branch.

props wonderboymusic.
fixes #25883.

Note: See TracTickets for help on using tickets.