diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 060e962..e07c587 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -61,3 +61,5 @@ bb927a60f808d44d10b6ffbd796982a3e5022c44 33a2233c7f0bfab90033796d51cbb722e2b5340e d0097bd14bf964bf4c283d07432ca52bfefa6391 0dc3de71ae8b40a7050bd1a106d31c216b9fc7d3 +430e41cb40dcd692360456fdd390c898bba8bd97 +529667d80354f37d222563c9b37cff8b2e9cc3e5 diff --git a/src/Exceptions/LockTimeoutException.php b/src/Exceptions/LockTimeoutException.php new file mode 100644 index 0000000..d0eb80e --- /dev/null +++ b/src/Exceptions/LockTimeoutException.php @@ -0,0 +1,23 @@ + */ + private static array $locks = []; + + /** + * Execute a callback while holding a named lock + * + * @template T + * + * @param string $name The name of the lock to acquire + * @param int $timeout Maximum number of seconds to wait for the lock + * @param Closure(): T $callback The callback to execute while holding the lock + * @return T + */ + public static function block(string $name, int $timeout, Closure $callback): mixed + { + self::acquire($name, $timeout); + + try { + return $callback(); + } finally { + self::release($name); + } + } + + private static function acquire(string $name, int $timeout): void + { + $attempts = 0; + + while (self::isLocked($name) && $attempts < $timeout) { + Sleep::for(1)->seconds(); + $attempts++; + } + + if (self::isLocked($name)) { + throw new LockTimeoutException($name, $timeout); + } + + self::$locks[$name] = true; + } + + private static function release(string $name): void + { + unset(self::$locks[$name]); + } + + private static function isLocked(string $name): bool + { + return self::$locks[$name] ?? false; + } +} \ No newline at end of file