Skip to content

Instantly share code, notes, and snippets.

@ircmaxell
Created February 12, 2018 18:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ircmaxell/0f0d7014a9653ba57b7284b9b59d4642 to your computer and use it in GitHub Desktop.
Save ircmaxell/0f0d7014a9653ba57b7284b9b59d4642 to your computer and use it in GitHub Desktop.
<?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