Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .git-blame-ignore-revs
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,5 @@ bb927a60f808d44d10b6ffbd796982a3e5022c44
33a2233c7f0bfab90033796d51cbb722e2b5340e
d0097bd14bf964bf4c283d07432ca52bfefa6391
0dc3de71ae8b40a7050bd1a106d31c216b9fc7d3
430e41cb40dcd692360456fdd390c898bba8bd97
529667d80354f37d222563c9b37cff8b2e9cc3e5
23 changes: 23 additions & 0 deletions src/Exceptions/LockTimeoutException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

/*
* Copyright (c) 2026. Encore Digital Group.
* All Rights Reserved.
*/

namespace EncoreDigitalGroup\StdLib\Exceptions;

use EncoreDigitalGroup\StdLib\Objects\Filesystem\ExitCode;
use Throwable;

class LockTimeoutException extends BaseException
{
public function __construct(string $lockName, int $timeout, ?Throwable $previous = null)
{
parent::__construct(
"Failed to acquire lock '{$lockName}' within {$timeout} second(s)",
ExitCode::GENERAL_ERROR,
$previous
);
}
}
65 changes: 65 additions & 0 deletions src/Objects/Support/Lock.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

/*
* Copyright (c) 2026. Encore Digital Group.
* All Rights Reserved.
*/

namespace EncoreDigitalGroup\StdLib\Objects\Support;

use Closure;
use EncoreDigitalGroup\StdLib\Exceptions\LockTimeoutException;
use Illuminate\Support\Sleep;

class Lock
{
/** @var array<string, bool> */
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;
}
}