Created
February 12, 2018 18:58
-
-
Save ircmaxell/0f0d7014a9653ba57b7284b9b59d4642 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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 {} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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