Alexzz's avatar

Atomic Locks Not working

Hello, i am having race conditions issues and atomic locks have not been working it's really been a problem additionally I can't test it locally as it works as expected no matter how fast I try to click the endpoints, but on build, if click fast the receiver will get funded even if the sender has no cash

        $lock = Cache::lock('transferCash_'. $sender->id . '_lock', 10);
        try {

            if (!$lock->get()) {
                throw new Exception('Cant send cash at the moment, please try again later');
            }

            if ($lock->get()) {

                $lock->block(10);

                $sender->refresh();
                $receiver->refresh();

                $sender->wallet->refresh();
                $receiver->wallet->refresh();

                $this->logTransaction(
                    sender: $sender,
                    receiver: $receiver,
                    amount: $request['amount'],
                    action: ' locked send cash transaction for ',
                    state: "old"
                );

                $this->checkSenderHasEnoughBalance(
                    senderBalance: $sender->wallet->balance,
                    amountInNairaToBeSent: $request['amount']);

                $sender->transferCash(receiver: $receiver,
                    amount: $request['amount'],
                    description: 'Send cash to ' . $receiver->username,
                    type: 'send_cash');

            }

        } finally {
            $lock?->release();

            $this->logTransaction(
                sender: $sender,
                receiver: $receiver,
                amount: $request['amount'],
                action: ' released locked send cash transaction for ',
                state: "new"
            );
        }

// the function that checks acct balance
final public function checkSenderHasEnoughBalance(int $senderBalance , int $amountInNairaToBeSent): void
    {

        if (convert_to_cents($amountInNairaToBeSent) > $senderBalance) {
           


            throw new InsufficientCashException('You do not have enough balance to send cash');
        }

    }



Any help would be appreciated

0 likes
8 replies
Snapey's avatar

I'm bothered by this code

    if (!$lock->get()) {
        throw new Exception('Cant send cash at the moment, please try again later');
    }

    if ($lock->get()) {

In the first lock->get() this should succeed, but then the second will fail because you already got a lock. Not sure you are doing it this way? I would take out the second if entirely

and you are missing the catch in your try-catch

Following the laravel docs;

$lock = Cache::lock('transferCash_'. $sender->id . '_lock', 10);
 
try {
    $lock->block(5);
    $sender->refresh();
    $receiver->refresh();

    $sender->wallet->refresh();
    $receiver->wallet->refresh();

    $this->logTransaction(
             sender: $sender,
             receiver: $receiver,
             amount: $request['amount'],
             action: ' locked send cash transaction for ',
             state: "old"
    );

    $this->checkSenderHasEnoughBalance(
            senderBalance: $sender->wallet->balance,
                    amountInNairaToBeSent: $request['amount']);

   $sender->transferCash(receiver: $receiver,
                    amount: $request['amount'],
                    description: 'Send cash to ' . $receiver->username,
                    type: 'send_cash');
 
} catch (LockTimeoutException $e) {
    throw new Exception('Cant send cash at the moment, please try again later');
} finally {
    $lock?->release();

    $this->logTransaction(
        sender: $sender,
        receiver: $receiver,
        amount: $request['amount'],
        action: ' released locked send cash transaction for ',
        state: "new"
    );
}
Alexzz's avatar

ok so the code you highlighted was actually just me trying anything to see what would work, but I have removed it and will get back to you thanks, also do you have any suggestions on how to locally simulate this multiple calls ?

Snapey's avatar

@Adamzz what do you use as your local dev web server?

If using php artisan serve, it will only ever serve one request at a time (one thread).

You could add some sleep into the process so that it takes a couple of seconds and give you chance to hit the request again.

Alexzz's avatar

sorry for the late reply, and thanks for answering I will get back to you after testing fingers crossed it works

Alexzz's avatar

Same problem this still doesn't work race condition still happens , I am using laravel-wallet could that be something ?


        try {

                $lock->block(10);

                $sender->refresh();
                $receiver->refresh();

                $sender->wallet->refresh();
                $receiver->wallet->refresh();

                $this->logTransaction(
                    sender: $sender,
                    receiver: $receiver,
                    amount: $request['amount'],
                    action: ' locked send cash transaction for ',
                    state: "old"
                );

                $this->checkSenderHasEnoughBalance(
                    senderBalance: $sender->wallet->balance,
                    amountInNairaToBeSent: $request['amount']);

                $sender->transferCash(receiver: $receiver,
                    amount: $request['amount'],
                    description: 'Send cash to ' . $receiver->username,
                    type: 'send_cash');



        }catch (LockTimeoutException $e) {
            throw new Exception('Cant send cash at the moment, please try again later');
        }  finally {
            $lock?->release();

            $this->logTransaction(
                sender: $sender,
                receiver: $receiver,
                amount: $request['amount'],
                action: ' released locked send cash transaction for ',
                state: "new"
            );
        }

Alexzz's avatar

@snapey update this didn't stop it , we have some users who keep trying and race condition still exists

Snapey's avatar

do you see two transactions overlapped in your log?

Alexzz's avatar

yes sir i did and the record on the db show that

Please or to participate in this conversation.