Skip to content

hyperunknown/lock

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

48 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

This library helps executing critical code in concurrent situations.

Installation

Use Composer:

composer require malkusch/lock

Usage

The package is in the namespace malkusch\lock.

Mutex

The Mutex provides the API for this library.

Mutex::synchronized()

Mutex::synchronized() executes code exclusively. This method guarantees that the code is only executed by one process at once. Other processes have to wait until the mutex is available. The critical code may throw an exception, which would release the lock as well.

Example:

$mutex->synchronized(function () use ($bankAccount, $amount) {
    $balance = $bankAccount->getBalance();
    $balance -= $amount;
    if ($balance < 0) {
        throw new \DomainException("You have no credit.");

    }
    $bankAccount->setBalance($balance);
});

Mutex::check()

Mutex::check() performs a double-checked locking pattern. I.e. if the check fails, no lock was acquired. Else if the check was true, a lock will be acquired and the check will be perfomed as well together with the critical code.

Example:

$mutex->check(function () use ($bankAccount, $amount) {
    return $bankAccount->getBalance() >= $amount;

})->then(function () use ($bankAccount, $amount) {
    $balance = $bankAccount->getBalance();
    $balance -= $amount;
    $bankAccount->setBalance($balance);
});

Implementations

The Mutex is an abstract class. You will have to chose an implementation:

CASMutex

The CASMutex has to be used with a Compare-and-swap operation. This mutex is lock free. It will repeat executing the code until the CAS operation was successful. The code should therefore notify the mutex by calling CASMutex::notify().

As the mutex keeps executing the critical code, it must not have any side effects as long as the CAS operation was not successful.

Example:

$mutex = new CASMutex();
$mutex->synchronized(function () use ($memcached, $mutex, $amount) {
    $balance = $memcached->get("balance", null, $casToken);
    $balance -= $amount;
    if (!$memcached->cas($casToken, "balance", $balance)) {
        return;

    }
    $mutex->notify();
});

FlockMutex

The FlockMutex is a lock implementation based on flock().

Example:

$mutex = new FlockMutex(fopen(__FILE__, "r"));
$mutex->synchronized(function () use ($bankAccount, $amount) {
    $balance = $bankAccount->getBalance();
    $balance -= $amount;
    if ($balance < 0) {
        throw new \DomainException("You have no credit.");

    }
    $bankAccount->setBalance($balance);
});

MemcacheMutex

The MemcacheMutex is a spinlock implementation which uses the Memcache API.

Example:

$memcache = new \Memcache();
$memcache->connect("localhost");

$mutex = new MemcacheMutex("balance", $memcache);
$mutex->synchronized(function () use ($bankAccount, $amount) {
    $balance = $bankAccount->getBalance();
    $balance -= $amount;
    if ($balance < 0) {
        throw new \DomainException("You have no credit.");

    }
    $bankAccount->setBalance($balance);
});

MemcachedMutex

The MemcachedMutex is a spinlock implementation which uses the Memcached API.

Example:

$memcache = new \Memcached();
$memcache->addServer("localhost", 11211);

$mutex = new MemcachedMutex("balance", $memcache);
$mutex->synchronized(function () use ($bankAccount, $amount) {
    $balance = $bankAccount->getBalance();
    $balance -= $amount;
    if ($balance < 0) {
        throw new \DomainException("You have no credit.");

    }
    $bankAccount->setBalance($balance);
});

PHPRedisMutex

The PHPRedisMutex is the distributed lock implementation of RedLock which uses the phpredis extension.

This implementation requires at least phpredis-2.2.4.

Example:

$redis = new Redis();
$redis->connect("localhost");

$mutex = new PHPRedisMutex([$redis], "balance");
$mutex->synchronized(function () use ($bankAccount, $amount) {
    $balance = $bankAccount->getBalance();
    $balance -= $amount;
    if ($balance < 0) {
        throw new \DomainException("You have no credit.");

    }
    $bankAccount->setBalance($balance);
});

PredisMutex

The PredisMutex is the distributed lock implementation of RedLock which uses the Predis API.

Example:

$redis = new Client("redis://localhost");

$mutex = new PredisMutex([$redis], "balance");
$mutex->synchronized(function () use ($bankAccount, $amount) {
    $balance = $bankAccount->getBalance();
    $balance -= $amount;
    if ($balance < 0) {
        throw new \DomainException("You have no credit.");

    }
    $bankAccount->setBalance($balance);
});

SemaphoreMutex

The SemaphoreMutex is a lock implementation based on Semaphore.

Example:

$semaphore = sem_get(ftok(__FILE__, "a"));
$mutex     = new SemaphoreMutex($semaphore);
$mutex->synchronized(function () use ($bankAccount, $amount) {
    $balance = $bankAccount->getBalance();
    $balance -= $amount;
    if ($balance < 0) {
        throw new \DomainException("You have no credit.");

    }
    $bankAccount->setBalance($balance);
});

TransactionalMutex

The TransactionalMutex delegates the serialization to the DBS. The exclusive code is executed within a transaction. It's up to you to set the correct transaction isolation level. However if the transaction fails (i.e. a PDOException was thrown), the code will be executed again in a new transaction. Therefore the code must not have any side effects besides SQL statements. Also the isolation level should be conserved for the repeated transaction. If the code throws an exception, the transaction is rolled back and not replayed again.

Example:

$mutex = new TransactionalMutex($pdo);
$mutex->synchronized(function () use ($pdo, $accountId, $amount) {
    $select = $pdo->prepare("SELECT balance FROM account WHERE id = ? FOR UPDATE");
    $select->execute([$accountId]);
    $balance = $select->fetchColumn();

    $balance -= $amount;
    if ($balance < 0) {
        throw new \DomainException("You have no credit.");

    }
    $pdo->prepare("UPDATE account SET balance = ? WHERE id = ?")
        ->execute([$balance, $accountId]);
});

License and authors

This project is free and under the WTFPL. Responsible for this project is Markus Malkusch markus@malkusch.de.

Donations

If you like this project and feel generous donate a few Bitcoins here: 1335STSwu9hST4vcMRppEPgENMHD2r1REK

Build Status

About

Lock library to provide serialized execution of PHP code.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • PHP 100.0%