What is an Atomic Lock?
The atomic lock allows synchronization of the async/multiple processes sharing common resources. In Laravel Atomic Lock is a part of the caching module. This lock helps in handling data safely with concurrent requests. Atomic locks are also useful in reducing system loads by limiting resource-intensive tasks.
Use of Atomic Locks
Atomic locks can be used in the following scenarios.
- Seat Booking Applications – Bus, Train, Hotels, Plane, Theater, etc seat bookings.
- Synchronizing Tasks – Avoid overlapping of cron execution.
- Avoid Duplicate Jobs – Avoid processing the same job again.
- Limit Resource Usage – prevent multiple resource-intensive tasks running at the same time.
How Atomic Lock Works?
Atomic lock works by locking a key in the cache for a duration. We can release the lock after the completion of the required process. Let’s take a look in detail.
Get Lock Instance
The lock instance requires a string that should be unique to identify the process. Other optional parameters require the time in seconds (default is 0) and owner. The Owner is the unique identifier of the current process user. If the owner is passed as null then the System will generate a random string.
use Illuminate\Support\Facades\Cache; $lock = Cache::lock("lock-key", 10); /** * Cache::lock(string $name, int $seconds = 0, string|null $owner = null) * returns \Illuminate\Contracts\Cache\Lock instance * i.e. * \Illuminate\Cache\DatabaseLock, * \Illuminate\Cache\RedisLock * etc. * based on Cache configuration */
Acquire Lock Using get
Method
Before using this method, look at the function’s internal code.
namespace Illuminate\Cache; ... abstract class Lock implements LockContract { ... /** * Attempt to acquire the lock. * * @param callable|null $callback * @return mixed */ public function get($callback = null) { $result = $this->acquire(); if ($result && is_callable($callback)) { try { return $callback(); } finally { $this->release(); } } return $result; } ... }
So, get
accepts one optional parameter callback
. The aquire
function returns bollean
the value (true
if the lock is acquired successfully else false
). If the callback is callable, then the lock will be released after execution of the callback function.
Now, we will try to acquire a lock with the callback.
use Illuminate\Support\Facades\Cache; $lock = Cache::lock("lock-key", 10); $result = $lock->get(function() { // Code under this function will be executed only if the lock is aquired. }); // if result is false. The function failed to aquire lock.
Then, acquire lock without callback.
use Illuminate\Support\Facades\Cache; $lock = Cache::lock("lock-key", 10); if($lock->get()){ // Code here will be executed if lock aquired. } else { // Code here will be executed if failed to aquire lock. };
Acquire Lock Using block
Method
The block
method is similar to the get
method but it accepts one more parameter as waiting time in seconds. We will take a look at the internal code.
namespace Illuminate\Cache; ... abstract class Lock implements LockContract { ... /** * Attempt to acquire the lock for the given number of seconds. * * @param int $seconds * @param callable|null $callback * @return mixed * * @throws \Illuminate\Contracts\Cache\LockTimeoutException */ public function block($seconds, $callback = null) { $starting = $this->currentTime(); while (! $this->acquire()) { Sleep::usleep($this->sleepMilliseconds * 1000); if ($this->currentTime() - $seconds >= $starting) { throw new LockTimeoutException; } } if (is_callable($callback)) { try { return $callback(); } finally { $this->release(); } } return true; } ... }
The difference between block
over get
is,
- This method will re-attempt to acquire a lock unless it completes the waiting time.
- It will take sleep of
sleepMilliseconds
before attempting again. - We can set this attribute to
$lock
variable by usingbetweenBlockedAttemptsSleepFor
function (like$lock->betweenBlockedAttemptsSleepFor(10)
).
Role of Owner
We have seen the acquire
method locks the key for an owner and returns true
or false
. The same owner can successfully lock the same key multiple times. Let’s take an example.
use Illuminate\Support\Facades\Cache; $lock_owner_1 = Cache::lock("lock-key", 10); $lock_owner_2 = Cache::lock("lock-key", 10); $lock_owner_1->get() // true $lock_owner_2->get() // false $lock_owner_1->get() // true $lock_owner_1->release() // true $lock_owner_2->get() // true
So, what happened here,
$lock_owner_1
and$lock_owner_2
are separate owners and can not obtain a lock if another owner uses the same key.- But
$lock_owner_1
can obtain the lock again because the lock already belongs to the owner. - if
$lock_owner_1
releases the lock or the lock expires then only$lock_owner_2
can acquire the lock.
A scenario where setting Owner Can be helpful
Take an example of a ticket booking application.
- The user selects a seat and proceeds to the payment gateway.
- If the Payment gateway reverts with a success then book the ticket.
- If failed release the seat for other users.
- If time exceeds release the seat for other users.
Payment Gateway interacts with the application using webhooks
. So, the default owner of the lock
(i.e. randomly generated) will be different. But we can not release a key without the same owner.
Now, consider the request received for the seat booking.
// validate request $lock_key = "$prefix-$vehicle-$seat"; // create key as a unique identifier for seat $lock = Cache::lock($lock_key, 10); try{ $lock->block(10) // aquire lock $metadata = [...prepare your metadata for gateway] $metadata["owner"] = $lock->owner(); // get current owner key $metadata["lock_key"] = $lock_key; // init payment procedure } catch (LockTimeoutException $e) { // revert with seat is not available for now. }
Then, the webhook reverts with the confirmation.
// from metadata extract owner and lock_key $lock = Cache::lock($metadata["lock_key"], 10, $metadata["owner"]); // now owner of this lock is the same as before try{ $lock->block(10) // aquire lock // perform database activities // revert with the sucess } catch (LockTimeoutException $e) { // if payment is succeded then start for refund as the booking is not completed. }
Conclusion
We discussed the usefulness and technical aspects of the Atomic lock in the Laravel Caceh module. The lock is important for handling concurrent requests. You can read more about Atomic Lock in the Larave Official Documentation.
If you want to read more on Laravel
, visit our blogs related to Laravel.