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?
Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.
Hello! My app is kind of call center.
There are caller and agents, there is some stock of agents.
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';
});
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';
});
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
Please or to participate in this conversation.