commit cebc855a6982fac1161efcfb71e0565c001c56ab
Author: Damien Tournoud <damien@commerceguys.com>
Date:   Sat Jun 11 14:25:55 2011 -0400

    Implement pessimistic locking.

diff --git a/includes/commerce.controller.inc b/includes/commerce.controller.inc
index 886477c..d05ac63 100644
--- a/includes/commerce.controller.inc
+++ b/includes/commerce.controller.inc
@@ -10,6 +10,43 @@
 class DrupalCommerceEntityController extends DrupalDefaultEntityController implements EntityAPIControllerInterface {
 
   /**
+   * Stores our transaction object, necessary for pessimistic locking to work.
+   */
+  protected $controllerTransaction = NULL;
+
+  /**
+   * Override of DrupalDefaultEntityController::buildQuery().
+   *
+   * Handle pessimistic locking.
+   */
+  protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) {
+    $query = parent::buildQuery($ids, $conditions, $revision_id);
+    if (isset($this->entityInfo['locking mode']) && $this->entityInfo['locking mode'] == 'pessimistic') {
+      // In pessimistic locking mode, we issue the load query with a FOR UPDATE
+      // clause. This will block all other load queries to the loaded objects
+      // but requires us to start a transaction.
+      if (empty($this->controllerTransaction)) {
+        $this->controllerTransaction = db_transaction();
+      }
+
+      $query->forUpdate();
+    }
+
+    return $query;
+  }
+
+  public function resetCache(array $ids = NULL) {
+    parent::resetCache($ids);
+    if (empty($this->entityCache) && !empty($this->controllerTransaction)) {
+      // If we don't have any entity in our local cache anymore, we commit the
+      // transaction so as to remove the locks we acquired.
+      // This will not commit the translation directly. Drupal will commit
+      // it as soon as possible given the state of the transaction stack.
+      unset($this->controllerTransaction);
+    }
+  }
+
+  /**
    * (Internal use) Invokes a hook on behalf of the entity.
    *
    * For hooks that have a respective field API attacher like insert/update/..
diff --git a/modules/cart/tests/commerce_cart.test b/modules/cart/tests/commerce_cart.test
index ddc559c..723fa1f 100644
--- a/modules/cart/tests/commerce_cart.test
+++ b/modules/cart/tests/commerce_cart.test
@@ -510,6 +510,8 @@ class CommerceCartTestCaseAnonymousToAuthenticated extends CommerceCartTestCase
 
     // Get the order just created.
     $order_anonymous = reset(commerce_order_load_multiple(array(), array('uid' => $user->uid, 'status' => 'cart'), TRUE));
+    // Reset the cache as we don't want to keep the lock.
+    entity_get_controller('commerce_order')->resetCache();
 
     // Access to the cart and check if the product is in it.
     $this->drupalGet($this->getCommerceUrl('cart'));
@@ -528,6 +530,8 @@ class CommerceCartTestCaseAnonymousToAuthenticated extends CommerceCartTestCase
 
     // Get the order for user just logged in.
     $order_authenticated = reset(commerce_order_load_multiple(array(), array('uid' => $this->store_customer->uid, 'status' => 'cart'), TRUE));
+    // Reset the cache as we don't want to keep the lock.
+    entity_get_controller('commerce_order')->resetCache();
 
     // Access to the cart and check if the product is in it.
     $this->drupalGet($this->getCommerceUrl('cart'));
diff --git a/modules/checkout/tests/commerce_checkout.test b/modules/checkout/tests/commerce_checkout.test
index 2263739..38b9c27 100644
--- a/modules/checkout/tests/commerce_checkout.test
+++ b/modules/checkout/tests/commerce_checkout.test
@@ -77,6 +77,8 @@ class CommerceCheckoutTestProcess extends CommerceBaseTestCase {
 
     // Get the order for the anonymous user.
     $this->order = reset(commerce_order_load_multiple(array(), array('uid' => $user->uid, 'status' => 'cart'), TRUE));
+    // Reset the cache as we don't want to keep the lock.
+    entity_get_controller('commerce_order')->resetCache();
   }
 
   /**
@@ -160,6 +162,8 @@ class CommerceCheckoutTestProcess extends CommerceBaseTestCase {
 
     // Load the order to check the status.
     $order = commerce_order_load_multiple(array($this->order->order_id), array(), TRUE);
+    // Reset the cache as we don't want to keep the lock.
+    entity_get_controller('commerce_order')->resetCache();
 
     // At this point we should be in Checkout Review.
     $this->assertEqual(reset($order)->status, 'checkout_review', t('Order status is \'Checkout Review\' in the review phase.'));
@@ -320,6 +324,8 @@ class CommerceCheckoutTestProcess extends CommerceBaseTestCase {
 
     // Load the order to check the status.
     $order = commerce_order_load_multiple(array($this->order->order_id), array(), TRUE);
+    // Reset the cache as we don't want to keep the lock.
+    entity_get_controller('commerce_order')->resetCache();
 
     // At this point we should be in Checkout Review.
     $this->assertEqual(reset($order)->status, 'checkout_review', t('Order status is \'Checkout Review\' in the review phase.'));
diff --git a/modules/order/commerce_order.module b/modules/order/commerce_order.module
index df031db..094b01e 100644
--- a/modules/order/commerce_order.module
+++ b/modules/order/commerce_order.module
@@ -14,6 +14,7 @@ function commerce_order_entity_info() {
     'commerce_order' => array(
       'label' => t('Commerce Order', array(), array('context' => 'a drupal commerce order')),
       'controller class' => 'CommerceOrderEntityController',
+      'locking mode' => 'pessimistic',
       'base table' => 'commerce_order',
       'revision table' => 'commerce_order_revision',
       'load hook' => 'commerce_order_load',
diff --git a/modules/order/tests/commerce_order_ui.test b/modules/order/tests/commerce_order_ui.test
index 682a56b..3118e34 100644
--- a/modules/order/tests/commerce_order_ui.test
+++ b/modules/order/tests/commerce_order_ui.test
@@ -61,6 +61,8 @@ class CommerceOrderUIAdminTest extends CommerceBaseTestCase {
 
     // Load the order from database for later use.
     $this->order = reset(commerce_order_load_multiple(array(), array('uid' => $this->store_customer->uid)));
+    // Reset the cache as we don't want to keep the lock.
+    entity_get_controller('commerce_order')->resetCache();
 
     // Enable an additional currency.
     $this->enableCurrencies(array('EUR'));
@@ -146,6 +148,8 @@ class CommerceOrderUIAdminTest extends CommerceBaseTestCase {
 
     // Reload the order directly from db.
     $order = reset(commerce_order_load_multiple(array($this->order->order_id), array(), TRUE));
+    // Reset the cache as we don't want to keep the lock.
+    entity_get_controller('commerce_order')->resetCache();
 
     // Check if the product has been added to the order.
     foreach (entity_metadata_wrapper('commerce_order', $order)->commerce_line_items as $delta => $line_item_wrapper) {
@@ -180,6 +184,8 @@ class CommerceOrderUIAdminTest extends CommerceBaseTestCase {
     // Reload the order directly from db and wrap it to get the line item ids.
     $order = reset(commerce_order_load_multiple(array($this->order->order_id), array(), TRUE));
     $order_wrapper = entity_metadata_wrapper('commerce_order', $order);
+    // Reset the cache as we don't want to keep the lock.
+    entity_get_controller('commerce_order')->resetCache();
 
     // Also wrap the product to access easier to its price.
     $product_wrapper = entity_metadata_wrapper('commerce_product', $this->product);
@@ -252,6 +258,8 @@ class CommerceOrderUIAdminTest extends CommerceBaseTestCase {
 
     // Check if the links for editing the order are present.
     $links = menu_contextual_links('commerce-order', 'admin/commerce/orders', array($this->order->order_id));
+    // Reset the cache as we don't want to keep the lock.
+    entity_get_controller('commerce_order')->resetCache();
     $this->assertRaw((theme('links', array('links' => $links, 'attributes' => array('class' => array('links', 'inline', 'operations'))))), t('Links for orders are present'));
 
     $this->drupalGet('admin/commerce/orders/'. $this->order->order_id . '/view');
diff --git a/modules/payment/tests/commerce_payment_ui.test b/modules/payment/tests/commerce_payment_ui.test
index 7df2549..723f6bf 100644
--- a/modules/payment/tests/commerce_payment_ui.test
+++ b/modules/payment/tests/commerce_payment_ui.test
@@ -166,6 +166,8 @@ class CommercePaymentUITest extends CommerceBaseTestCase {
 
     // Reload the order.
     $order = reset(commerce_order_load_multiple(array($this->order->order_id), array(), TRUE));
+    // Reset the cache as we don't want to keep the lock.
+    entity_get_controller('commerce_order')->resetCache();
 
     // Check order balance, it should be half of total now.
     $new_balance = commerce_payment_order_balance($order);
