Make WordPress Core

Opened 9 years ago

Last modified 7 years ago

#34322 new defect (bug)

set_transient and get_transient don't seem to be working for some users since WP 4.3

Reported by: smashballoon's profile smashballoon Owned by:
Milestone: Awaiting Review Priority: normal
Severity: normal Version: 4.3
Component: Options, Meta APIs Keywords:
Focuses: Cc:

Description

I'm the developer of a social media plugin which relies on the WordPress set_transient and get_transient functions to temporarily cache data in the user's database. Since the WordPress 4.3 update, we've had reports of the cache not clearing automatically as it should, meaning that the transients aren't expiring correctly in the database. This seems to only be happening on some servers as I haven't been able to replicate the issue on my own test site, but have confirmed it on user sites, and looks like it could be related to this thread. The same problem seems to be happening to other developers of similar plugins, and I've had some users report that other unrelated WordPress plugins they're using are also not automatically updating either any more, which I'm assuming is caused by the same issue. I've been able to confirm the problem on user sites by using a page template containing the following basic code. I've commented it to explain what it does and can provide a Facebook Access Token privately if needed, although I included a link on how to obtain your own. The transient set using the script below should expire every 30 minutes and then the script should check the URL again for new data, but it doesn't unless I delete the transient manually.

<?php
/* Template Name: Transient test */

//Facebook URL to get data from. You can get an Access Token to use in the URL by following the directions here: https://smashballoon.com/custom-facebook-feed/access-token/
$url = 'https://graph.facebook.com/cnn/posts?access_token=ACCESS_TOKEN';

//Check the database for the transient containing the data
$transient = get_transient( 'test_transient_expiration' );

if( ! empty( $transient ) ) {
  //The transient was found in the database
	echo 'Got the existing transient from the database <br />';
  //Show the date the data was last updated from Facebook
  echo 'Last updated: ' . json_decode( json_encode($transient) )->headers->date;
} else {
  //No transient in the database - get the data from Facebook
  $facebook_data = wp_remote_get( $url );
  //Cache the data in the database
  set_transient( 'test_transient_expiration', $facebook_data, 1800 );
	echo 'Got the data from the URL <br />';
  //Show the date the data was last updated from Facebook
  echo 'Last updated: ' . json_decode( json_encode($facebook_data) )->headers->date;
}

?>

I was in two minds whether to report this as a bug as I've never reported one before, however it definitely seems like this could be a bug as the above code should work, but doesn't on some user's sites. I use code very similar to the above in my plugin and nothing has been changed in that code, but users started reporting an issue shortly after the WordPress 4.3 update was released. Through reading the change logs for the 4.3 update I know some changes were made to transients and so I'm wondering if that's what caused this problem.

Sorry for the long post!

John

Change History (9)

#1 @SergeyBiryukov
9 years ago

  • Component changed from General to Options, Meta APIs

#3 @smashballoon
9 years ago

Thanks, I've read through that thread a few times before and although it seems related it only references get_transient not deleting broken transients any more, which I don't think should be causing this issue. As far as I can tell nothing has been changed which should prevent the following script from working:

<?php
$transient = get_transient( 'test_transient_expiration' );

if( ! empty( $transient ) ) {
  echo 'From Database. Last updated: ' . json_decode( json_encode($transient) )->headers->date;
} else {
  $facebook_data = wp_remote_get( 'https://graph.facebook.com/cnn/posts?access_token=ACCESS_TOKEN' );
  set_transient( 'test_transient_expiration', $facebook_data, 1800 );
  echo 'From URL. Last updated: ' . json_decode( json_encode($facebook_data) )->headers->date;
}
?>

We use the same code in our plugin, and we've had lots of different users report an issue with it not working on their sites anymore. I've even logged into a few sites and set this exact script up on a blank test page and the transient will continually return as true and never expires unless manually deleted using delete_transient.

I know this probably isn't a particularly helpful bug report but I wanted to report the issue in case someone was able to replicate it or experienced the same problem. For now I'm going to try and come up with an alternative solution for caching the data instead of relying on set_transient and get_transient as I have been up until now.

John

#4 follow-up: @Nick_theGeek
9 years ago

I think I've found the issue. It is an old problem but the change in #30380 made it apparent.

Basically the DB limits the option names to 64 characters but by the time the transient expiration is built for the transients we are using, it ends up making the string too long. So it searches for something like _transient_timeout_{long transient name} but the actual option name is _transient_timeout_{abbreviated transient name}.

Since this doesn't match it returns a "false" value, which is treated as if the transient will never expire based on the new code.

The solution I came up with was:

$transient_timeout = '_transient_timeout_' . $transient;
$transient_timeout = strlen( $transient_timeout ) > 64 ? substr( $transient_timeout, 0, 64 ) : $transient_timeout;

Well, for my usage, I just shortened the transient name and that fixed it without editing WP, but while I was testing to find the issue I edited the option.php file to echo various values and hunt down what was happening, then applied that to the get_transient() function to verify it works. Then I reverted all that and changed the length of the transient key.

#5 @swissspidy
9 years ago

See #15058 for the transient key length issue.

#6 in reply to: ↑ 4 @smashballoon
9 years ago

Replying to Nick_theGeek:

I think I've found the issue. It is an old problem but the change in #30380 made it apparent.

Basically the DB limits the option names to 64 characters but by the time the transient expiration is built for the transients we are using, it ends up making the string too long. So it searches for something like _transient_timeout_{long transient name} but the actual option name is _transient_timeout_{abbreviated transient name}.

Since this doesn't match it returns a "false" value, which is treated as if the transient will never expire based on the new code.

The solution I came up with was:

$transient_timeout = '_transient_timeout_' . $transient;
$transient_timeout = strlen( $transient_timeout ) > 64 ? substr( $transient_timeout, 0, 64 ) : $transient_timeout;

Well, for my usage, I just shortened the transient name and that fixed it without editing WP, but while I was testing to find the issue I edited the option.php file to echo various values and hunt down what was happening, then applied that to the get_transient() function to verify it works. Then I reverted all that and changed the length of the transient key.

I was already limiting the transient name to 45 chars to account for this, as recommended in the [Codex](https://codex.wordpress.org/Function_Reference/set_transient). I ended up giving up trying to figure out why set/get_transient wasn't working reliably on some servers and just implemented a cron job in the next plugin update to delete the transients every hour.

#7 follow-up: @ReneHermi
7 years ago

I experience the same issue in WP 4.7.3 set_transient() does not seem to be working reliable in any case. I was not able to track that down, yet but there are confirmed users where set_transient() is not adding the expiration date. The result is a transient which never expires. E.g. set_transient('mashsb_limit_req', '1', 25);

@smashballoon Have you ever figured out the reason for this?

#8 in reply to: ↑ 7 @smashballoon
7 years ago

Replying to ReneHermi:

I experience the same issue in WP 4.7.3 set_transient() does not seem to be working reliable in any case. I was not able to track that down, yet but there are confirmed users where set_transient() is not adding the expiration date. The result is a transient which never expires. E.g. set_transient('mashsb_limit_req', '1', 25);

@smashballoon Have you ever figured out the reason for this?

Unfortunately not, we're still experiencing the same issue with some users. To get around it we had to use the wp_cron function to delete the transients at specific intervals. It's not ideal, but it works well enough.

#9 @ReneHermi
7 years ago

I noticed that all customers who are faced with that issue are using (uncommon) caching plugins. It's possible that one of these caching plugins is removing expiration time from the transient by a rare condition. Maybe this some sort of op caching method which is making trouble. On my own dev systems this never happens so i assume it's caused by a third party plugin.

Thanks for the tip. Think i need to go the same route like you.

Note: See TracTickets for help on using tickets.