Make WordPress Core

Opened 16 months ago

Last modified 8 months ago

#59246 new defect (bug)

update_option returns true, even when the value didn't change, potentially adding back old data

Reported by: malthert's profile malthert Owned by:
Milestone: Awaiting Review Priority: normal
Severity: normal Version:
Component: Options, Meta APIs Keywords: has-patch has-unit-tests
Focuses: Cc:

Description

update_option will inconsistently return true/false, depending on whether another update option call is made in the meantime.

While this isn't a problem per se, it creates inconsistent and unexpected behavior.

`
$a = get_option( 'a' );
$b = get_option( 'b' );

sleep( 5 ); placeholder for code that takes some time
$b = '2';
update_option( 'b', $b );
update_option( 'a', $a );
$a didn't change, so it shouldn't update but return false - but it will update and return true
`

Request 2:

`
$a = get_option( 'a' );
$b = get_option( 'b' );

$a = '7';
$b = '3';
update_option( 'b', $b );
update_option( 'a', $a );
`

Result:
Option 'a' will have a value of 1 instead of 7.

Why does this happen?
update_option uses get_option to get the $old_value - however the $old_value might in fact be a NEW value that was added in the meantime by another request.

This leads to unexpected behavior of update_option if called with unmodified data - since update_option sometimes behaves seemingly atomically, while in reality it doesn't - but most devs are not aware of this.

These are extremely hard to track down bugs for most developers.

Possible solution(s):

  • ignore this issue, and improve documentation to make it clear that update_option might update an option even if the value didn't change - if the option was modified in the meantime
  • add an additional parameter to update_option for $old_value to have people pass it in for comparison

Change History (4)

This ticket was mentioned in PR #6057 on WordPress/wordpress-develop by @kkmuffme.


10 months ago
#1

  • Keywords has-patch has-unit-tests added

#2 @kkmuffme
10 months ago

We encountered this issue also within the same request in cases where an apply filters/do action callback modified an option value, which it turned out is an extremely common issue and requires manual mitigation at the moment.

Simple reproduction case:

<?php

$foo = get_option( 'foo' );

do_action( 'hello' ); // any callback may update the option value of 'foo', but any changes would be overwritten by the call to update_option below

$foo = apply_filters( 'filter_foo', $foo ); // any callback may update the option value of 'foo', but any changes would be overwritten by the call to update_option below

update_option( 'foo', $foo );

I created a PR that adds a 4th optional argument as well as a specific function for update_option to require the old value for atomic updates.

This ticket was mentioned in Slack in #core by kkmuffme. View the logs.


10 months ago

This ticket was mentioned in Slack in #core by kkmuffme. View the logs.


8 months ago

Note: See TracTickets for help on using tickets.