Avoid race condition

I’m working on a plugin to reserve and pay for a seat at an event. I’d like to assign an id, a ticket number of some sorts, to each reservation, and I’d like the ids to be short and sequential. Starting at 1000, I would store the latest id in the user/data folder, and increment and save the id with each order.

Although the number of reservations would probably be very low (1 or 2 per day), there’s always the slight chance of two people reserving at the same time. How do I avoid a situation whereby a reservation reads the latest id, before it had the chance to be incremented by the previous order? Is there a way?

@TheDancingCode,

Although I have never dealt with this situation using PHP, I can think of the following:

  • Request an exclusive lock before reading/writing the file(s). See flock()
  • Use sqlite with autogenerated primary key.
    There is no need to read the last ID and increment it, that will be handled by the db. The database will be exlusively locked on writing.

Just some thoughts…

1 Like

Thank you for pointing me to flock.

Would this be correct?

$file = File::instance($filename);
$file->lock();
$data = Yaml::parse($file->content());
$id = data['id'];
data['id']++;
$file->save(Yaml::dump($data));
$file->unlock();

@TheDancingCode, As said, I have never dealt with this in PHP…

Below is (a part of) the definition of class AbstractFile (see ‘/system/src/Grav/Framework/File/AbstractFile.php’) , which is the parent of class File:

public function lock(bool $block = true): bool
{
    ...

    $lock = $block ? LOCK_EX : LOCK_EX | LOCK_NB;

    // Some filesystems do not support file locks, only fail if another process holds the lock.
    $this->locked = flock($this->handle, $lock, $wouldblock) || !$wouldblock;

    return $this->locked;
}

File::lock(bool $block = true) uses flock() to lock a file and, by default, blocks the thread until it has an exclusive lock acquired.

So I guess your code should prevent the race condition.

Just a thought… Maybe using File::lock(false) might be a better option with the code trying 2-3 times to acquire a lock. This could prevent issues when the lock is not released by some failure. In case of a failure to acquiring a lock, you can notify the user.