Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
<?php
interface Lock {
public function advisory(string $identifier): LockContext;
public function exclusive(string $identifier): LockContext;
public function waitForAdvisory(string $identifier, int $timeout = 0): \Generator;
public function waitForExclusive(string $identifier, int $timeout = 0): \Generator;
}
interface LockContext {
public function exec(callable $cb);
public function release();
}
class LockContentionException extends \RuntimeException {}
<?php
class InMemoryLock implements Lock {
private $advisory = [];
private $exclusive = [];
public function advisory(string $identifier): LockContext {
if (isset($this->exclusive[$identifier])) {
throw new LockContentionException("$identifier not available");
}
$this->advisory[$identifier] = $this->advisory[$identifier] ?? 0;
$this->advisory[$identifier]++;
return new CallbackLockContext(function() use ($identifier) {
$this->release($identifier);
});
}
public function exclusive(string $identifier): LockContext {
if (isset($this->exclusive[$identifier]) || isset($this->advisory[$identifier])) {
throw new LockContentionException("$identifier not available");
}
$this->exclusive[$identifier] = true;
return new CallbackLockContext(function() use ($identifier) {
$this->release($identifier);
});
}
public function waitForAdvisory(string $identifier, int $timeout = 0): \Generator {
$start = microtime(true);
while (isset($this->exclusive[$identifier])) {
if ($timeout !== 0 && microtime(true) - $start > $timeout * 1000) {
throw new LockContentionException("Timeout locking $identifier: not available");
}
yield;
}
return $this->advisory($identifier);
}
public function waitForExclusive(string $identifier, int $timeout = 0): \Generator {
$start = microtime(true);
while (isset($this->exclusive[$identifier]) || isset($this->advisory[$identifier])) {
if ($timeout !== 0 && microtime(true) - $start > $timeout * 1000) {
throw new LockContentionException("Timeout locking $identifier: not available");
}
yield;
}
return $this->exclusive($identifier);
}
private function release(string $identifier) {
if (isset($this->exclusive[$identifier])) {
unset($this->exclusive[$identifier]);
}
if (isset($this->advisory[$identifier])) {
$this->advisory[$identifier]--;
if ($this->advisory[$identifier] <= 0) {
unset($this->advisory[$identifier]);
}
}
}
}
class CallbackLockContext implements LockContext {
private $cb;
private $released = false;
public function __construct(callable $cb) {
$this->cb = $cb;
}
public function __destruct() {
$this->release();
}
public function exec(callable $cb) {
try {
return $cb();
} finally {
$this->release();
}
}
public function release() {
if ($this->released) {
return;
}
$this->released = true;
($this->cb)();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.