Skip to content

Commit

Permalink
Merge pull request #1325 from Automattic/improve/post-processor-respo…
Browse files Browse the repository at this point in the history
…nse-cache

Improve post processor response cache
  • Loading branch information
westonruter committed Aug 23, 2018
2 parents ba5c3a3 + 65ad9fa commit 234b722
Show file tree
Hide file tree
Showing 5 changed files with 456 additions and 97 deletions.
106 changes: 105 additions & 1 deletion includes/class-amp-theme-support.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,34 @@ class AMP_Theme_Support {
*/
const RESPONSE_CACHE_GROUP = 'amp-response';

/**
* Post-processor cache effectiveness group name.
*
* @var string
*/
const POST_PROCESSOR_CACHE_EFFECTIVENESS_GROUP = 'post_processor_cache_effectiveness_group';

/**
* Post-processor cache effectiveness key name.
*
* @var string
*/
const POST_PROCESSOR_CACHE_EFFECTIVENESS_KEY = 'post_processor_cache_effectiveness';

/**
* Cache miss threshold for determining when to disable post-processor cache.
*
* @var int
*/
const CACHE_MISS_THRESHOLD = 20;

/**
* Cache miss URL option name.
*
* @var string
*/
const CACHE_MISS_URL_OPTION = 'amp_cache_miss_url';

/**
* Sanitizer classes.
*
Expand Down Expand Up @@ -1760,6 +1788,13 @@ public static function prepare_response( $response, $args = array() ) {
$current_url = amp_get_current_url();
$ampless_url = amp_remove_endpoint( $current_url );

// When response caching is enabled, determine if it should be turned off for cache misses.
$caches_for_url = null;
if ( true === $args['enable_response_caching'] ) {
list( $disable_response_caching, $caches_for_url ) = self::check_for_cache_misses();
$args['enable_response_caching'] = ! $disable_response_caching;
}

// Return cache if enabled and found.
$cache_response = null;
if ( true === $args['enable_response_caching'] ) {
Expand Down Expand Up @@ -1802,7 +1837,15 @@ public static function prepare_response( $response, $args = array() ) {
return $response_cache['body'];
}

$cache_response = function( $body, $validation_results ) use ( $response_cache_key ) {
$cache_response = function( $body, $validation_results ) use ( $response_cache_key, $caches_for_url ) {
$caches_for_url[] = $response_cache_key;
wp_cache_set(
AMP_Theme_Support::POST_PROCESSOR_CACHE_EFFECTIVENESS_KEY,
$caches_for_url,
AMP_Theme_Support::POST_PROCESSOR_CACHE_EFFECTIVENESS_GROUP,
600 // 10 minute cache.
);

return wp_cache_set(
$response_cache_key,
compact( 'body', 'validation_results' ),
Expand Down Expand Up @@ -1956,6 +1999,67 @@ public static function prepare_response( $response, $args = array() ) {
return $response;
}

/**
* Check for cache misses. When found, store in an option to retain the URL.
*
* @since 1.0
*
* @return array {
* State.
*
* @type bool Flag indicating if the threshold has been exceeded.
* @type string[] Collection of URLs.
* }
*/
private static function check_for_cache_misses() {
// If the cache miss threshold is exceeded, return true.
if ( self::exceeded_cache_miss_threshold() ) {
return array( true, null );
}

// Get the cache miss URLs.
$cache_miss_urls = wp_cache_get( self::POST_PROCESSOR_CACHE_EFFECTIVENESS_KEY, self::POST_PROCESSOR_CACHE_EFFECTIVENESS_GROUP );
$cache_miss_urls = is_array( $cache_miss_urls ) ? $cache_miss_urls : array();

$exceeded_threshold = (
! empty( $cache_miss_urls )
&&
count( $cache_miss_urls ) >= self::CACHE_MISS_THRESHOLD
);

if ( ! $exceeded_threshold ) {
return array( $exceeded_threshold, $cache_miss_urls );
}

// When the threshold is exceeded, store the URL for cache miss and turn off response caching.
update_option( self::CACHE_MISS_URL_OPTION, amp_get_current_url() );
AMP_Options_Manager::update_option( 'enable_response_caching', false );
return array( true, null );
}

/**
* Reset the cache miss URL option.
*
* @since 1.0
*/
public static function reset_cache_miss_url_option() {
if ( get_option( self::CACHE_MISS_URL_OPTION ) ) {
delete_option( self::CACHE_MISS_URL_OPTION );
}
}

/**
* Checks if cache miss threshold has been exceeded.
*
* @since 1.0
*
* @return bool
*/
public static function exceeded_cache_miss_threshold() {
$url = get_option( self::CACHE_MISS_URL_OPTION, false );
return ! empty( $url );
}

/**
* Adds 'data-amp-layout' to the allowed <img> attributes for wp_kses().
*
Expand Down
52 changes: 52 additions & 0 deletions includes/options/class-amp-options-manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class AMP_Options_Manager {
'disable_admin_bar' => false,
'all_templates_supported' => true,
'supported_templates' => array( 'is_singular' ),
'enable_response_caching' => true,
);

/**
Expand All @@ -48,6 +49,7 @@ public static function register_settings() {

add_action( 'update_option_' . self::OPTION_NAME, array( __CLASS__, 'maybe_flush_rewrite_rules' ), 10, 2 );
add_action( 'admin_notices', array( __CLASS__, 'persistent_object_caching_notice' ) );
add_action( 'admin_notices', array( __CLASS__, 'render_cache_miss_notice' ) );
}

/**
Expand Down Expand Up @@ -78,6 +80,7 @@ public static function get_options() {
if ( empty( $options ) ) {
$options = array();
}
self::$defaults['enable_response_caching'] = wp_using_ext_object_cache();
return array_merge( self::$defaults, $options );
}

Expand Down Expand Up @@ -198,6 +201,16 @@ public static function validate_options( $new_options ) {
// Store the current version with the options so we know the format.
$options['version'] = AMP__VERSION;

// Handle the caching option.
$options['enable_response_caching'] = (
wp_using_ext_object_cache()
&&
! empty( $new_options['enable_response_caching'] )
);
if ( $options['enable_response_caching'] ) {
AMP_Theme_Support::reset_cache_miss_url_option();
}

return $options;
}

Expand Down Expand Up @@ -325,4 +338,43 @@ public static function persistent_object_caching_notice() {
);
}
}

/**
* Render the cache miss admin notice.
*
* @return void
*/
public static function render_cache_miss_notice() {
if ( 'toplevel_page_' . self::OPTION_NAME !== get_current_screen()->id ) {
return;
}

if ( ! self::show_response_cache_disabled_notice() ) {
return;
}

printf(
'<div class="notice notice-warning is-dismissible"><p>%s <a href="%s">%s</a></p></div>',
esc_html__( "The AMP plugin's post-processor cache disabled due to the detection of highly-variable content.", 'amp' ),
esc_url( 'https://github.com/Automattic/amp-wp/wiki/Post-Processor-Cache' ),
esc_html__( 'More details', 'amp' )
);
}

/**
* Show the response cache disabled notice.
*
* @since 1.0
*
* @return bool
*/
public static function show_response_cache_disabled_notice() {
return (
wp_using_ext_object_cache()
&&
! self::get_option( 'enable_response_caching' )
&&
AMP_Theme_Support::exceeded_cache_miss_threshold()
);
}
}
41 changes: 41 additions & 0 deletions includes/options/class-amp-options-menu.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,19 @@ public function add_menu_items() {
)
);

if ( wp_using_ext_object_cache() ) {
add_settings_field(
'caching',
__( 'Caching', 'amp' ),
array( $this, 'render_caching' ),
AMP_Options_Manager::OPTION_NAME,
'general',
array(
'class' => 'amp-caching-field',
)
);
}

$submenus = array(
new AMP_Analytics_Options_Submenu( AMP_Options_Manager::OPTION_NAME ),
);
Expand Down Expand Up @@ -390,6 +403,34 @@ function updateFieldsetVisibility() {
<?php
}

/**
* Render the caching settings section.
*
* @since 1.0
*
* @todo Change the messaging and description to be user-friendly and helpful.
*/
public function render_caching() {
?>
<fieldset>
<?php if ( AMP_Options_Manager::show_response_cache_disabled_notice() ) : ?>
<div class="notice notice-info notice-alt inline">
<p><?php esc_html_e( 'The post-processor cache was disabled due to detecting randomly generated content found on', 'amp' ); ?> <a href="<?php echo esc_url( get_option( AMP_Theme_Support::CACHE_MISS_URL_OPTION, '' ) ); ?>"><?php esc_html_e( 'on this web page.', 'amp' ); ?></a></p>
<p><?php esc_html_e( 'Randomly generated content was detected on this web page. To avoid filling up the cache with unusable content, the AMP plugin\'s post-processor cache was automatically disabled.', 'amp' ); ?>
<a href="<?php echo esc_url( 'https://github.com/Automattic/amp-wp/wiki/Post-Processor-Cache' ); ?>"><?php esc_html_e( 'Read more', 'amp' ); ?></a>.</p>
</div>
<?php endif; ?>
<p>
<label for="enable_response_caching">
<input id="enable_response_caching" type="checkbox" name="<?php echo esc_attr( AMP_Options_Manager::OPTION_NAME . '[enable_response_caching]' ); ?>" <?php checked( AMP_Options_Manager::get_option( 'enable_response_caching' ) ); ?>>
<?php esc_html_e( 'Enable post-processor caching.', 'amp' ); ?>
</label>
</p>
<p class="description"><?php esc_html_e( 'This will enable post-processor caching to speed up processing an AMP response after WordPress renders a template.', 'amp' ); ?></p>
</fieldset>
<?php
}

/**
* List template conditional options.
*
Expand Down
Loading

0 comments on commit 234b722

Please sign in to comment.