Prevent Race Condition in Symfony – Best Practices
Race conditions can quietly erode data integrity and security in Symfony apps. Here’s how to prevent them effectively using Symfony components and Doctrine's locking mechanisms.
๐ What’s a Race Condition?
A race condition occurs when multiple processes read and write shared resources simultaneously without synchronization — leading to unpredictable and inconsistent outcomes. In web apps, this often happens when users trigger duplicate requests before previous ones finish.
1. Use Symfony Lock Component
Symfony’s Lock component ensures only one process enters a critical section at a time.
Installation
composer require symfony/lock
Basic Usage
use Symfony\Component\Lock\LockFactory;
$lock = $lockFactory->createLock('cart_add_'.$userId);
if (!$lock->acquire()) {
// another process is in progress
return;
}
// critical section: add to cart
$cartService->addItem($userId, $productId);
$lock->release();
This prevents simultaneous cart modifications — ideal for actions like “Add to Cart” races.
Configuring Storage
In config/packages/lock.yaml
, configure persistent stores:
framework:
lock:
- 'redis://localhost'
- 'flock:///var/lock/app'
Supports flock, semaphore, Redis, MySQL, ZooKeeper, etc.
2. Doctrine Locking Strategies
Use Doctrine’s locking for entity consistency during concurrent updates.
Optimistic Locking
Add a version field to your entity:
#[ORM\Version, ORM\Column(type:'integer')]
private int $version;
On flush()
, Doctrine checks the version and throws OptimisticLockException
on mismatch.
Example
try {
$em->flush();
} catch (\Doctrine\ORM\OptimisticLockException $e) {
// reload entity, reapply changes, retry or notify user
}
Pessimistic Locking
Lock the entity directly in the database:
$em->getConnection()->beginTransaction();
$user = $em->find(User::class, $id, LockMode::PESSIMISTIC_WRITE);
// now safe to update
$em->flush();
$em->getConnection()->commit();
This uses SQL SELECT … FOR UPDATE
, blocking other transactions.
When to Use Which?
Scenario | Optimistic | Pessimistic |
---|---|---|
Rare conflicts | ✅ | ❌ (overhead) |
Frequent conflicts or high consistency required | ❌ | ✅ |
3. Session-Level Locking (When Storing Sessions in DB)
If sessions are stored in DB, use lock_mode: LOCK_TRANSACTIONAL
to avoid session race conditions under concurrent requests:
framework:
session:
handler_id: 'session.handler.pdo'
cookie_secure: auto
cookie_samesite: lax
lock_mode: LOCK_TRANSACTIONAL
๐งช Code-Driven Example: Prevent Duplicate Order Processing
// src/Controller/OrderController.php
use Symfony\Component\Lock\LockFactory;
use Symfony\Component\HttpFoundation\Request;
use Doctrine\DBAL\LockMode;
public function placeOrder(Request $request, LockFactory $lockFactory, EntityManagerInterface $em) {
$userId = $this->getUser()->getId();
$lock = $lockFactory->createLock('order_place_'.$userId);
if (!$lock->acquire()) {
return new JsonResponse(['error' => 'Order in progress'], 429);
}
$em->getConnection()->beginTransaction();
try {
$account = $em->find(Account::class, $userId, LockMode::PESSIMISTIC_WRITE);
// check balance and adjust
$account->withdraw($amount);
$em->flush();
$em->getConnection()->commit();
} catch (\Throwable $e) {
$em->getConnection()->rollBack();
throw $e;
} finally {
$lock->release();
}
return new JsonResponse(['success' => true]);
}
This guards both the app and DB layer from duplicates.
๐งฉ Your Free Security Tool
Use our Website Vulnerability Scanner to validate your defenses.
Here’s a screenshot of the tool’s interface:
![]() |
Screenshot of the free tools webpage where you can access security assessment tools. |
And a sample vulnerability assessment report generated by our tool to check Website Vulnerability:
![]() |
An Example of a vulnerability assessment report generated with our free tool, providing insights into possible vulnerabilities. |
Explore our blog for more best practices: Pentest Testing Corp.
๐ผ Additional Web Security Services
-
Web App Penetration Testing: Learn more
-
Offer Cybersecurity to Clients: Details here
Follow & Subscribe
Stay updated on security insights: Subscribe on LinkedIn
✅ Final Tips
-
Lock at the right level (application vs. DB).
-
Track versions for long-lived entities.
-
Choose between optimistic and pessimistic based on contention.
-
Don’t forget persistent lock stores (Redis, DB, semaphore).
-
Test with concurrency, not just unit tests.
Race conditions are sneaky but manageable. Using Symfony Lock + Doctrine safeguards, you can maintain consistency, security, and performance.
Comments
Post a Comment