From ca996a8941b5732aafe69b631c55342ec6eb5ef4 Mon Sep 17 00:00:00 2001 From: Gennady Kovshenin Date: Tue, 31 Mar 2020 13:09:07 +0300 Subject: [PATCH] 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. --- .../class-wc-product-data-store-cpt.php | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/includes/data-stores/class-wc-product-data-store-cpt.php b/includes/data-stores/class-wc-product-data-store-cpt.php index 28eb05aae9d..6c2baf5afff 100644 --- a/includes/data-stores/class-wc-product-data-store-cpt.php +++ b/includes/data-stores/class-wc-product-data-store-cpt.php @@ -1356,9 +1356,14 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da if ( 'set' === $operation ) { $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 { - // @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( $wpdb->get_var( $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 ) { case 'increase': $new_stock = $current_stock + wc_stock_amount( $stock_quantity ); + $multiplier = 1; break; default: $new_stock = $current_stock - wc_stock_amount( $stock_quantity ); + $multiplier = -1; 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 = $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' ); + $sql = apply_filters( 'woocommerce_update_product_stock_query', $sql, $product_id_with_stock, $new_stock, $operation ); $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared