Search code examples
phpconcurrencydoctrine-ormdoctrine

php doctrine concurrency pessimistic_write is not preventing additional requests from getting old data


php: 8.1 doctrine: 2.9.2

I am having a problem implementing concurrency in doctrine. I've looked through the documentation and a few questions and I cannot see what my issue is.

My app is tracking inventory. There is a chance that multiple requests may update the stock at the same time. I put in a sleep to test. The 2nd request waits for the 1st request to finish, so the lock appears to be partially working. However, the 2nd request uses the starting stock instead of the adjusted stock of the 1st request.

  • Stock: 50
  • 1st request: 50+5 = 55
  • 2nd request: 50+5 = 55 (should be 55+5 = 60)

Here is the persistence code:

$this->em->beginTransaction();
    try{
        $inventoryID = $stockAdjustment->getInventory()->getId();
        $this->detach($stockAdjustment->getInventory());
        $inventory = $this->inventoryRepo->find($inventoryID, LockMode::PESSIMISTIC_WRITE);
        $this->refresh($inventory);
        sleep(15);
        $stockAdjustment->setInventory($inventory);
        $stockAdjustment->setPreAdjustmentStock($inventory->getStock());
        $inventory->modifyStock($stockAdjustment->getQuantity());

        $this->em->persist($inventory);
        $this->em->persist($stockAdjustment);
        $this->em->flush();
        $this->em->commit();
    }
    catch (\Throwable $e){
        $this->em->rollback();
        throw $e;
    }

Before this occurs, the inventory is found when hydrating the adjustment. I left in the detach and refresh lines to show that I've tried that. Normally, Inventory is mapped to InventoryAdjustment with cascade persist. In this case, I've removed the cascade persist and am saving the inventory directly.


Solution

  • It was a stupid mistake that I realized just after posting. Stock can be a couple different types depending on the type of inventory, so stock is not actually stored on the Inventory object. I have another class InventoryStock and putting the lock on that resolved the issue. InventoryStock has a discriminator map for a few sub classes, but putting the lock on InventoryStock worked.