handle race condition

dev-main 2025-05-28 18:03 UTC

This package is auto-updated.

Last update: 2025-06-28 18:25:12 UTC


README

CoolRace is a PHP library designed to handle distributed locking in a Laravel application. It provides a flexible and robust way to manage locks for critical sections of code, ensuring that only one process can execute a given block of code at a time. The library supports various locking drivers and includes features like context prefixes, retries, and model-specific locking.

Features

  • Distributed Locking: Prevent race conditions in distributed systems.
  • Context Prefixing: Add prefixes to lock keys for better organization.
  • Retry Mechanism: Automatically retry acquiring locks with configurable delays.
  • Model Locking: Lock operations based on Eloquent model instances.
  • Multiple Locks: Acquire multiple locks in a sorted order to avoid deadlocks.
  • Customizable Drivers: Use different locking backends (e.g., file-based locking with FileLockDriver).
  • Timeout Handling: Handle lock acquisition failures gracefully with timeouts.

Installation

To use CoolRace in your Laravel project, follow these steps:

  1. Require the Package Ensure your project uses PHP 7.4+ and Laravel 8.x or higher. Install the package via Composer:

    composer require wordcoolframework/coolrace
  2. Set Up the Lock Driver Configure the lock driver (e.g., FileLockDriver) by providing a storage path for lock files. Update your configuration or service provider as needed.

  3. Register the Service If not automatically registered, bind CoolRace to your Laravel service container:

    $this->app->singleton(CoolRace::class, function () {
        return new CoolRace(new FileLockDriver(storage_path('locks')));
    });

Usage

The CoolRace class provides several methods to manage locks. Below are the primary methods and their usage.

1. Basic Lock

The lock method acquires a lock for a given key, executes a callback, and releases the lock afterward. If the lock cannot be acquired within the timeout, it throws a LockTimeoutException.

use Wordcoolframework\CoolRace\CoolRace;

$coolRace = new CoolRace(new FileLockDriver(storage_path('locks')));

$result = $coolRace->lock('my-resource', function () {
    // Critical section
    return 'Operation completed';
}, 10);

2. Try Lock

The tryLock method attempts to acquire a lock but returns null instead of throwing an exception if the lock cannot be acquired.

$result = $coolRace->tryLock('my-resource', function () {
    return 'Operation completed';
}, 5);

if ($result === null) {
    echo "Could not acquire lock";
} else {
    echo $result;
}

3. Lock with Retry

The lockWithRetry method attempts to acquire a lock multiple times with a delay between attempts. If all retries fail, it throws a LockTimeoutException.

$result = $coolRace->lockWithRetry('my-resource', function () {
    return 'Operation completed';
}, 10, 3, 500);

4. Model Locking

The lockForModel method creates a lock based on an Eloquent model's class name and primary key.

use App\Models\User;

$user = User::find(1);

$result = $coolRace->lockForModel($user, function () use ($user) {
    $user->balance += 100;
    $user->save();
    return 'Balance updated';
});

5. Multiple Locks

The lockMultiple method acquires multiple locks in a sorted order to avoid deadlocks, executes the callback, and releases all locks.

$result = $coolRace->lockMultiple(['resource1', 'resource2'], function () {
    return 'Multiple resources locked';
}, 10);

6. Context Prefix

The withContext method creates a new instance with a prefix for all lock keys, useful for scoping locks.

$scopedCoolRace = $coolRace->withContext('user:123');
$result = $scopedCoolRace->lock('action', function () {
    return 'Scoped operation';
});

7. Check Lock Status

The isLocked method checks if a lock is currently held.

if ($coolRace->isLocked('my-resource')) {
    echo "Resource is locked";
} else {
    echo "Resource is available";
}

8. Manual Lock Management

Use acquire and release for manual lock management without a callback.

if ($coolRace->acquire('my-resource', 10)) {
    try {
        // Critical section
    } finally {
        $coolRace->release('my-resource');
    }
}

9. Release All Locks by Prefix

The forceReleaseAllByPrefix method releases all locks matching a given prefix (if supported by the driver).

$coolRace->forceReleaseAllByPrefix('user:123');

10. Lock Until a Specific Time

The lockUntil method acquires a lock with a timeout based on a DateTimeInterface instance.

$until = new \DateTime('+1 minute');
$result = $coolRace->lockUntil('my-resource', function () {
    return 'Operation completed';
}, $until);

Example: Preventing Double Payments

Here’s a practical example of using CoolRace to prevent double payments for an order:

use Wordcoolframework\CoolRace\CoolRace;
use App\Models\Order;

$coolRace = app(CoolRace::class);
$order = Order::find(123);

$result = $coolRace->lockForModel($order, function () use ($order) {
    if ($order->is_paid) {
        throw new Exception('Order already paid');
    }
    // Process payment
    $order->is_paid = true;
    $order->save();
    return 'Payment processed';
});

echo $result;

FileLockDriver

The FileLockDriver is a simple file-based locking driver included with CoolRace. It stores lock files in a specified directory and uses file creation as a locking mechanism.

Configuration

use Wordcoolframework\CoolRace\Drivers\FileLockDriver;

$driver = new FileLockDriver(storage_path('locks'));
$coolRace = new CoolRace($driver);

Notes

  • Ensure the lock directory is writable by the PHP process.
  • The driver sanitizes lock keys to prevent invalid file names.
  • The releaseAllByPrefix method deletes all lock files matching the given prefix.

Error Handling

  • LockTimeoutException: Thrown by lock and lockWithRetry when a lock cannot be acquired within the timeout or retries.
  • Handle exceptions in your application logic to gracefully manage lock failures.
try {
    $result = $coolRace->lock('my-resource', function () {
        return 'Operation completed';
    });
} catch (LockTimeoutException $e) {
    echo "Failed to acquire lock: " . $e->getMessage();
}

Contributing

Contributions are welcome! Please submit pull requests or issues to the GitHub repository.

License

This project is licensed under the MIT License.