Prevent Concurrent Writes in Laravel
<p>I have a laravel 5.2 app which receives a POST from clients when they arrive at a particular location. On occasion, the client fires two identical requests at the same time, but uses the same record (the last event, that was not part of these duplicate requests) to decide how to be handled.</p>

<p>With each request, I'm trying to prevent duplicate checkins/checkouts by determining if the client has already signed in or out of a same location by checking the last event for that user.</p>

<p>Eg:</p>

<pre><code>&lt;?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Checkin;
use App\Models\Population;
use App\Http\Requests;

class CheckinApiController extends Controller {

    public function post(Request $request) {

        $uuid = $request-&gt;uuid;
        $isCheckin = $request-&gt;isCheckin;
        $locationId = $request-&gt;locationId;

        // get the last checkin for user matching this UUID
        $lastCheckin = Checkin::where("uuid", $uuid)
                               -&gt;orderBy('updated_at', 'desc')
                               -&gt;first();

        if ($isCheckin == $lastCheckin-&gt;isCheckin 
            &amp;&amp; $locationId == $lastCheckin-&gt;locationId) {
/* Scenario 1:
 * if this post event is the same as the last record's checkin type
 * (checkin/checkout), and the locationIds are the same, this is a 
 * duplicate. Return an error code and message
 * In this case, both the duplicate requests see the same record,
 * and both are handled as duplicates
 */
            abort(403, 'duplicate');
        } else if ($isCheckin == true 
                   &amp;&amp; $isCheckin == $lastCheckin-&gt;isCheckin 
                   &amp;&amp; $locationId != $lastCheckin-&gt;locationId) {

/* Scenario 2:
 * this is a checkin event, but the last event for this user was
 * a checkin at a different location
 * we create a mock 'checkout' for this request, and 
 * set the updated_at field as current time minus a few seconds
 * BUT... in the case of duplicates, both duplicates see the 
 * last event, so both duplicates handle this the same way, and i
 * get TWO $missedCheckin records.
 */

            $missedCheckin = Checkin::create([
                'locationId' =&gt; $lastCheckin-&gt;locationId,
                'isCheckin' =&gt; false,
                'uuid' =&gt; $uuid,
                'updated_at' =&gt; time() - 10,
            ]);
        }

        // write this checkin event
        $checkin = Checkin::create([
            'locationId' =&gt; $locationId,
            'isCheckin' =&gt; $isCheckin,
            'uuid' =&gt; $uuid
        ]);

        // adjust population on another model
        $population = Population::firstOrCreate([
            'location_id' =&gt; $locationId
        ]);
        // increment or decrement population based on 
        // if this is a checkin or checkout, 
        // omitted here but mentioned as it is another 
        // database transaction on a different model

        // $responseDataSet is a dictionary with info to tell the client to present to the user
        return response()-&gt;json($responseDataSet); 
    }
}
</code></pre>

<p>Is there a way I can suspend a possible duplicate, allow the first record to go through this process, and only then allow the second one to go through (which would be seen as a duplicate)?</p>

<p>I tried to put in the following to wait a random number of milliseconds before processing, but it seems that the random number generator returns the same random number during the identical requests:</p>

<pre><code>$msToSleep = 1 * random_int(500, 100000);
usleep($msToSleep);
</code></pre>

<p>The client does NOT necessarily need to know the result, so I don't need to return anything other than a 200 status code, though eventually I may want to return the valid checkin object that was created.</p>

<p>But I need the second request of a set of duplicate requests to see $lastCheckin being the result of the first of the duplicates, properly processed (sometimes it might be a duplicate, but separated by a few minutes), and processed accordingly.</p>