Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

avrahamm's avatar

DB Transaction and race conditions

Hello! My app is kind of call center.

There are caller and agents, there is some stock of agents.

1st code section

A scheduled task runs on every minute selects and reserves agents to customer.

Agent row has free/busy status column.

My app calls agents in serial manner till agent answers and connected to customer.

DB::transaction(function () {   
//reserve new free agent for next call
    $newFreeAgent =  Agent::sharedLock()->where('status' => 'free')->first();
    $newFreeAgent->status = 'busy';
});

2nd section

Yet when agent doesn't answer after some timeout or turns off manually,

there is an 'no answer' asynchronous web hook notification.

In this code section, app will try to reserve free agent for next call as well.

Pretty similar code

DB::transaction(function (Agent $currentAgent) {   
// first free current agent to avoid dead lock 
     $currentAgent->status = free;
     $currentAgent->save();
//reserve new free agent for next call
    $newFreeAgent =  Agent::sharedLock()->where('status' => 'free')->first();
    $newFreeAgent->status = 'busy';
});

Question:

I added DB::transaction to solve race conditions.

As by https://laravel.com/docs/9.x/database#database-transactions

and

https://laravel.com/docs/9.x/queries#pessimistic-locking

Yet I am not quite sure what happens when 2nd code section runs.

If 'no answer' asynchronous web hook notification arrives

when agents rows are locked by scheduled task, what happens?

Will an exception be thrown?

Will a code just wait till unlocked?

If you know a good Laravel DB transaction and race conditions tutorial,

either Laracasts or external, please recommend.

Thank you, Avraham

0 likes
3 replies
Snapey's avatar

Your main issue might be

$currentAgent->status = free;
$currentAgent->save();

current agent which was fetched some time earlier but you set their status and save without knowing if the status has already been changed in another thread?

1 like
avrahamm's avatar

@Snapey Thank you for your reply.

Agent status is set to free only in several places do not overlap.

I am worried what happens if current agent row is still being locked by scheduled task

when tries to

$currentAgent->status = free;
$currentAgent->save();
avrahamm's avatar

Today I saw

Laravel: Avoid Race Conditions with Atomic Locks in Cache

by Laravel Daily.

https://www.youtube.com/watch?v=zybiFatkoCo

It tries to present the issue.

By one of the comments there,

default Lock wait time is 50sec - it is InnoDB setting.

So in my example, I suppose locks are released much faster than 50 seconds.

So even if other thread is trying to reserve the agent when locked,

it will just wait without Exception, and get the Lock when previous done.

Please or to participate in this conversation.