diff --git a/CHANGELOG.md b/CHANGELOG.md index acf5429..f57af6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,10 +20,12 @@ Released with 2meet-data-optimizer 1.2.0 (hardening 2.2). - With a persistent object cache (`wp_using_ext_object_cache()`), the transient filter keeps HivePress's per-post transient pairs in the `tmdo_hp_transient` cache group instead of `wp_options`, where they piled - up (1.15 M pairs on dev36). Reads fall back to `wp_options` for values - written before; deletes clear both. Without an object cache nothing changes. - After upgrading, remove the rows left behind with core's - `wp tmdo cleanup-hp-transients --options --confirm`. + up (1.15 M pairs on dev36). The cache is then the only store: rows written + before are neither read nor deleted (HivePress rebuilds those caches once), + because a `get_option()` miss adds the name to WordPress's `notoptions` for + good and a hit keeps the old row in the `options` cache group with no expiry. + Without an object cache nothing changes. After upgrading, remove the rows + left behind with core's `wp tmdo cleanup-hp-transients --options --confirm`. ### Fixed diff --git a/includes/class-tmdo-hivepress-transient-filter.php b/includes/class-tmdo-hivepress-transient-filter.php index 1deadff..8adb2d5 100644 --- a/includes/class-tmdo-hivepress-transient-filter.php +++ b/includes/class-tmdo-hivepress-transient-filter.php @@ -167,9 +167,14 @@ final class TMDO_Hivepress_Transient_Filter { // HivePress cache layer reads the timeout *first*, then decides whether // to read the value. We must preserve raw stored values until HivePress // itself orders deletion. - $value = wp_using_ext_object_cache() ? wp_cache_get( $option_name, self::CACHE_GROUP ) : false; - if ( false === $value ) { - // wp_options: where values went before an object cache was used. + if ( wp_using_ext_object_cache() ) { + // The object cache is the only store. Rows left in wp_options from + // before are not read (`wp tmdo cleanup-hp-transients --options` + // removes them): a get_option() miss would add the name to + // `notoptions` for good, as later writes never reach wp_options. + $value = wp_cache_get( $option_name, self::CACHE_GROUP ); + $value = false === $value ? null : $value; + } else { $value = get_option( $option_name, null ); } @@ -248,6 +253,7 @@ final class TMDO_Hivepress_Transient_Filter { if ( wp_using_ext_object_cache() ) { wp_cache_delete( $option_name, self::CACHE_GROUP ); + return true; } delete_option( $option_name ); return true; diff --git a/tests/integration/HivepressTransientFilterTest.php b/tests/integration/HivepressTransientFilterTest.php index 41e2f9a..3547f20 100644 --- a/tests/integration/HivepressTransientFilterTest.php +++ b/tests/integration/HivepressTransientFilterTest.php @@ -213,16 +213,18 @@ class HivepressTransientFilterTest extends TestCase { $this->assertSame( array( '99999999' ), WPDO_Hivepress_Transient_Filter::on_read( null, 100, '_transient_timeout_hp_models/cat/v1', true ) ); } - public function test_with_an_object_cache_a_value_left_in_wp_options_still_reads(): void { + public function test_with_an_object_cache_a_value_left_in_wp_options_is_not_read(): void { + // A miss must not fall back to get_option(): the option is absent after + // cleanup and WordPress would add its name to `notoptions` for good. $key = '_transient_hp_models/cat/v1'; $translated = WPDO_Hivepress_Transient_Filter::translate_key( 100, $key ); update_option( '_transient_' . $translated, 'from-before' ); $GLOBALS['_wp_using_ext_object_cache'] = true; - $this->assertSame( array( 'from-before' ), WPDO_Hivepress_Transient_Filter::on_read( null, 100, $key, true ) ); + $this->assertNull( WPDO_Hivepress_Transient_Filter::on_read( null, 100, $key, true ) ); } - public function test_with_an_object_cache_delete_clears_both_stores(): void { + public function test_with_an_object_cache_delete_leaves_wp_options_alone(): void { $key = '_transient_hp_models/cat/v1'; $translated = WPDO_Hivepress_Transient_Filter::translate_key( 100, $key ); update_option( '_transient_' . $translated, 'from-before' ); @@ -232,7 +234,7 @@ class HivepressTransientFilterTest extends TestCase { WPDO_Hivepress_Transient_Filter::on_delete( null, 100, $key, '', false ); $this->assertNull( WPDO_Hivepress_Transient_Filter::on_read( null, 100, $key, true ) ); - $this->assertFalse( get_option( '_transient_' . $translated ) ); + $this->assertSame( 'from-before', get_option( '_transient_' . $translated ), 'left for cleanup-hp-transients --options' ); } // ── count/purge legacy ──────────────────────────────────────────────────