Address Race Condition in sendThankcards using pessimistic locking (lockForUpdate)
This commit is contained in:
@@ -47,9 +47,23 @@ class UserService implements UserServiceInterface
|
|||||||
$startOfMonth = Carbon::now()->startOfMonth();
|
$startOfMonth = Carbon::now()->startOfMonth();
|
||||||
$endOfMonth = Carbon::now()->endOfMonth();
|
$endOfMonth = Carbon::now()->endOfMonth();
|
||||||
|
|
||||||
$cardsSentToThisUserThisMonth = Administration::where('msnv', $sender->msnv)
|
DB::transaction(function () use ($sender, $receiverMsnv, $amount, $startOfMonth, $endOfMonth) {
|
||||||
|
// Lock sender record to prevent concurrent transaction modifications
|
||||||
|
$lockedSender = User::where('id', $sender->id)->lockForUpdate()->first();
|
||||||
|
|
||||||
|
if ($lockedSender->flag_send == config('constants.FLAG_SEND_DISABLED')) {
|
||||||
|
throw new \App\Exceptions\ThankCardException(__('messages.error.no_send_permission'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($lockedSender->card < $amount) {
|
||||||
|
throw new \App\Exceptions\ThankCardException(__('messages.error.not_enough_cards'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock & check total sent cards to the receiver this month
|
||||||
|
$cardsSentToThisUserThisMonth = Administration::where('msnv', $lockedSender->msnv)
|
||||||
->where('receiver', $receiverMsnv)
|
->where('receiver', $receiverMsnv)
|
||||||
->whereBetween('date', [$startOfMonth, $endOfMonth])
|
->whereBetween('date', [$startOfMonth, $endOfMonth])
|
||||||
|
->lockForUpdate()
|
||||||
->sum('sent');
|
->sum('sent');
|
||||||
|
|
||||||
if ($cardsSentToThisUserThisMonth + $amount > Administration::MAX_SEND_CARD_PER_MONTH) {
|
if ($cardsSentToThisUserThisMonth + $amount > Administration::MAX_SEND_CARD_PER_MONTH) {
|
||||||
@@ -61,22 +75,17 @@ class UserService implements UserServiceInterface
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($sender->card < $amount) {
|
|
||||||
throw new \App\Exceptions\ThankCardException(__('messages.error.not_enough_cards'));
|
|
||||||
}
|
|
||||||
|
|
||||||
DB::transaction(function () use ($sender, $receiverMsnv, $amount) {
|
|
||||||
Administration::create([
|
Administration::create([
|
||||||
'msnv' => $receiverMsnv,
|
'msnv' => $receiverMsnv,
|
||||||
'received' => $amount,
|
'received' => $amount,
|
||||||
'sender' => $sender->msnv,
|
'sender' => $lockedSender->msnv,
|
||||||
'sent' => 0,
|
'sent' => 0,
|
||||||
'receiver' => null,
|
'receiver' => null,
|
||||||
'date' => Carbon::today(),
|
'date' => Carbon::today(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
Administration::create([
|
Administration::create([
|
||||||
'msnv' => $sender->msnv,
|
'msnv' => $lockedSender->msnv,
|
||||||
'received' => 0,
|
'received' => 0,
|
||||||
'sender' => null,
|
'sender' => null,
|
||||||
'sent' => $amount,
|
'sent' => $amount,
|
||||||
@@ -84,12 +93,16 @@ class UserService implements UserServiceInterface
|
|||||||
'date' => Carbon::today(),
|
'date' => Carbon::today(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$sender->card -= $amount;
|
$lockedSender->card -= $amount;
|
||||||
if ($sender->card <= 0) {
|
if ($lockedSender->card <= 0) {
|
||||||
$sender->card = 0;
|
$lockedSender->card = 0;
|
||||||
$sender->flag_send = config('constants.FLAG_SEND_DISABLED');
|
$lockedSender->flag_send = config('constants.FLAG_SEND_DISABLED');
|
||||||
}
|
}
|
||||||
$sender->save();
|
$lockedSender->save();
|
||||||
|
|
||||||
|
// Synchronize variables back to the original model instance
|
||||||
|
$sender->card = $lockedSender->card;
|
||||||
|
$sender->flag_send = $lockedSender->flag_send;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user