Fix race condition in update_product_stock

Modify the increment and decrement SQL query in
WC_Product_Data_Store_CPT::update_product_stock to be atomic. This fixes
a race condition in concurrent order placement.
This commit is contained in:
Gennady Kovshenin 2020-03-31 13:09:07 +03:00
parent 23e555823b
commit ca996a8941
1 changed files with 18 additions and 11 deletions

View File

@ -1356,9 +1356,14 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
if ( 'set' === $operation ) { if ( 'set' === $operation ) {
$new_stock = wc_stock_amount( $stock_quantity ); $new_stock = wc_stock_amount( $stock_quantity );
// Generate SQL.
$sql = $wpdb->prepare(
"UPDATE {$wpdb->postmeta} SET meta_value = %f WHERE post_id = %d AND meta_key='_stock'",
$new_stock,
$product_id_with_stock
);
} else { } else {
// @todo: potential race condition.
// Read current stock level and lock the row. If the lock can't be acquired, don't wait.
$current_stock = wc_stock_amount( $current_stock = wc_stock_amount(
$wpdb->get_var( $wpdb->get_var(
$wpdb->prepare( $wpdb->prepare(
@ -1368,25 +1373,27 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
) )
); );
// Calculate new value. // Calculate new value for filter below. Set multiplier to subtract or add the meta_value.
switch ( $operation ) { switch ( $operation ) {
case 'increase': case 'increase':
$new_stock = $current_stock + wc_stock_amount( $stock_quantity ); $new_stock = $current_stock + wc_stock_amount( $stock_quantity );
$multiplier = 1;
break; break;
default: default:
$new_stock = $current_stock - wc_stock_amount( $stock_quantity ); $new_stock = $current_stock - wc_stock_amount( $stock_quantity );
$multiplier = -1;
break; break;
} }
// Generate SQL.
$sql = $wpdb->prepare(
"UPDATE {$wpdb->postmeta} SET meta_value = meta_value %+f WHERE post_id = %d AND meta_key='_stock'",
wc_stock_amount( $stock_quantity ) * $multiplier, // This will either subtract or add depending on operation.
$product_id_with_stock
);
} }
// Generate SQL. $sql = apply_filters( 'woocommerce_update_product_stock_query', $sql, $product_id_with_stock, $new_stock, $operation );
$sql = $wpdb->prepare(
"UPDATE {$wpdb->postmeta} SET meta_value = %f WHERE post_id = %d AND meta_key='_stock'",
$new_stock,
$product_id_with_stock
);
$sql = apply_filters( 'woocommerce_update_product_stock_query', $sql, $product_id_with_stock, $new_stock, 'set' );
$wpdb->query( $sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared