Repository navigation
Conversation
When `set_transient()` or `set_site_transient()` creates a transient with an expiration and the database rejects the write of its timeout row, the value was still stored. Without a timeout row, `get_transient()` treats it as a transient that never expires. The value is now only stored when the timeout row was written or already exists. `add_option()` also returns false when the row is already there, for example when a concurrent request wrote the same timeout first, so a missing write is only treated as a failure when the database reports an error. Updating an existing timeout row is unchanged. Props mennoll, peterwilsoncc, sukhendu2002. Fixes #52345. See #44977.
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the Core Committers: Use this line as a base for the props when committing in SVN: To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
Test using WordPress PlaygroundThe changes in this pull request can previewed and tested using a WordPress Playground instance. WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser. Some things to be aware of
For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation. |
Trac ticket: https://core.trac.wordpress.org/ticket/52345
Refreshes the approach from #910 by @menno-ll (now conflicting with trunk), following @peterwilsoncc's suggestion in comment:5 on the ticket.
Summary
When
set_transient()creates a transient with an expiration, it writes two rows:_transient_timeout_{name}and_transient_{name}. On the "add" paths the result of writing the timeout row was ignored, so if the database rejected that INSERT the value was still stored. With no timeout row,get_transient()returns the value forever.set_site_transient()has the same pattern. sukhendu2002 confirmed the behaviour on trunk in #44977 (comment:5).Fix
On the add paths, the value is now only stored when the timeout row was written or already exists:
add_option()returningfalseis not enough on its own to mean "failed":INSERT … ON DUPLICATE KEY UPDATE. If another request wrote the same timeout in the same second, MySQL reports 0 changed rows andadd_option()returnsfalse. The cache primed at the start ofset_transient()still says the row is missing, so only the database error tells a real failure apart.wpdb::query()resetslast_errorbefore every query.add_option()returnsfalsewithout querying, and these keep today's behaviour through theget_option()check.The same rule applies to the branch that adds an expiration to an existing transient without a timeout, and to
set_site_transient().The update paths, where a timeout row already exists, are unchanged. A failed update there still leaves an expiry in place. It also avoids a trap in #910:
update_option()returnsfalsewhen the value is unchanged, so treating that as a failure drops a secondset_transient()with the same expiry in the same second.Testing
New tests (
@ticket 52345) inTests_Option_TransientandTests_Option_SiteTransient. The database failure is simulated by pointing only the timeout INSERT at a missing table, sowpdbreports a real error, and a concurrent write by replaying the same INSERT from thequeryfilter.false, nothing storedfalseThe 3 failure cases fail on trunk; all 9 pass with this change, on single site and multisite:
The one warning is an existing PHPUnit 10 deprecation notice in
Tests_Option_wpUserSettings.Use of AI Tools
AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 5.5
Used for: Tracing the transient write paths, writing the fix and tests, and reviewing it for regressions (two review passes found the concurrent-request case, which is now covered by tests). All changes were reviewed by me.
This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.