Skip to content

Enforce transient deletion and creation in options table when using external object cache.#1653

Merged
Chouby merged 25 commits intomasterfrom
enforce-transient-deletion
Apr 22, 2025
Merged

Enforce transient deletion and creation in options table when using external object cache.#1653
Chouby merged 25 commits intomasterfrom
enforce-transient-deletion

Conversation

@Hug0-Drelon
Copy link
Member

@Hug0-Drelon Hug0-Drelon commented Apr 8, 2025

What?

Partially fixes #2125.

How?

  • Ensure pll_languages_list transient is deleted from options table even if external object cache is used.
    • In usual process thanks to delete_transient_pll_languages_list hook with new method in Languages model.
    • In upgrade or uninstallation by calling related core functions.
  • Add a PHPUnit test to cover the issue, introducing wpsyntex/object-cache-annihilator as dev dependency.

Note

Similar work must be done in Polylang Pro for pll_translated_slugs transient.

@Hug0-Drelon Hug0-Drelon self-assigned this Apr 8, 2025
@Hug0-Drelon Hug0-Drelon added the bug label Apr 8, 2025
@Hug0-Drelon Hug0-Drelon added this to the 3.8 milestone Apr 8, 2025
@Screenfeed Screenfeed self-requested a review April 11, 2025 09:05
Hug0-Drelon and others added 4 commits April 12, 2025 00:20
… the same instance of PLL_Model to avoid breaking global scope for other tests, do not clean cache to hard between actions, and call languages->set_ready() to allow Polylang to cache languages.
@Hug0-Drelon Hug0-Drelon requested a review from Screenfeed April 14, 2025 07:47
Co-authored-by: Grégory Viguier <[email protected]>
Comment on lines 758 to 762
public function set_transient_to_options_table( $value ): void {
if ( wp_using_ext_object_cache() || wp_installing() ) {
add_option( '_transient_' . self::TRANSIENT_NAME, $value, '', true );
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it useful? I we previously deleted the transient and at some point the external is not used, the option will be automatically re-created. Am I wrong?

Copy link
Member Author

@Hug0-Drelon Hug0-Drelon Apr 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed during our meeting, this is safer to avoid race conditions between cache and DB.

Copy link
Member Author

@Hug0-Drelon Hug0-Drelon Apr 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We eventually chose to relay only on transient deletion to keep the DB up to date.

So I removed set_transient_to_options_table() in 9d28161.

I also ensure the test fails correctly with this change (if the fix is removed), see ad16085.

Then I took the opportunity to create a dedicated testcase in 9e46188, indeed I'm currently working on the same issue in Polylang Pro and I could use a testcase for it.

Comment on lines 125 to 126
add_action( 'delete_transient_pll_languages_list', array( $this->languages, 'delete_transient_from_options_table' ) );
add_action( 'set_transient_pll_languages_list', array( $this->languages, 'set_transient_to_options_table' ) );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you are using the constant in Languages, it would be better to use it here the action name too.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in a0e5313.

}

delete_transient( 'pll_languages_list' );
delete_option( '_transient_pll_languages_list' ); // Force deletion of the transient from the options table in case external object cache is used.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it necessary? I suppose that the action added above will do its job. Am I wrong?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the added hooks to be effective, we must delay the deletion. Done in b01def5.

@@ -0,0 +1,523 @@
<?php
/**
* Object Cache Annihilator.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the source of this file is coming from https://github.com/polylang/object-cache-annihilator.
I suggest to maintain only the plugin and "require-dev" it with composer to re-use it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in e789585.

uninstall.php Outdated
Comment on lines 188 to 189
delete_transient( 'pll_languages_list' );
delete_option( '_transient_pll_languages_list' ); // Force deletion of the transient from the options table in case external object cache is used.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
delete_transient( 'pll_languages_list' );
delete_option( '_transient_pll_languages_list' ); // Force deletion of the transient from the options table in case external object cache is used.
delete_option( '_transient_pll_languages_list' ); // Force deletion of the transient from the options table in case external object cache is used.

We don't need to delete the external cache in case of clean uninstall. Do we?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 3e66676.

@Hug0-Drelon Hug0-Drelon requested a review from Chouby April 17, 2025 11:15
composer.json Outdated
Comment on lines 22 to 28
"repositories": [
{
"name": "wpsyntex/object-cache-annihilator",
"type": "vcs",
"url": "[email protected]:polylang/object-cache-annihilator.git"
}
],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice 👍
Done in 1fd4beb.

Hug0-Drelon and others added 3 commits April 17, 2025 22:09
Co-authored-by: Chouby <[email protected]>
…ister_shutdown_function to remove Object Cache Annihilator properly in case of fatal error.
…o activate the cache, - ensure database contains the transient - assert the transient from database does not exist in the end.
@Hug0-Drelon Hug0-Drelon requested a review from Chouby April 18, 2025 09:45
@Chouby Chouby merged commit 01572b1 into master Apr 22, 2025
8 checks passed
@Chouby Chouby deleted the enforce-transient-deletion branch April 22, 2025 08:05
@Chrystll
Copy link

Chrystll commented Feb 4, 2026

I have successfully tested the fix in an environment using Redis (via the Redis Object Cache plugin) to ensure that transients are correctly purged from the external cache.

Test :

  1. Preparation (SQL): With Redis disabled, I identified the ghost entries _transient_pll_languages_list and _transient_pll_translated_slugs in the wp_options table. I manually deleted them to ensure no stale data remained in SQL.

  2. Activation: Enabled Redis Object Cache. I verified that the status was "Enabled" and that WordPress was no longer writing transients to the SQL database (confirmed by the absence of new entries in wp_options).

  3. Execution:

  • Modified the language order in Languages > Languages.
  • Added a new language.
  • (Pro version) Translated some slugs to trigger _transient_pll_translated_slugs.
  1. Observation:
  • Front-end: All changes were reflected instantly on the live site.
  • Persistence: No manual "Flush Cache" was required. This confirms that delete_transient() successfully sent the purge command to Redis instead of trying to delete a row in the (now unused) SQL table.
  • SQL Check: Confirmed that the wp_options table remained empty for these keys, proving that the fix correctly targets the active cache layer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants