-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
14 changed files
with
562 additions
and
11 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,5 @@ | ||
/vendor/ | ||
composer.lock | ||
/composer.lock | ||
/examples/lock/temp | ||
/vendor/ | ||
/tests/Lock/lock | ||
/tests/Lock/temp |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace PHPSTORM_META; | ||
|
||
expectedArguments( | ||
\Redbitcz\Utils\Lock\Locker::__construct(), | ||
2, | ||
\Redbitcz\Utils\Lock\Locker::BLOCKING, | ||
\Redbitcz\Utils\Lock\Locker::NON_BLOCKING | ||
); |
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
use Redbitcz\Utils\Lock\Locker; | ||
|
||
require_once __DIR__ . '/../../vendor/autoload.php'; | ||
|
||
$tempDir = __DIR__ . '/temp'; | ||
|
||
if (!@mkdir($tempDir) && !is_dir($tempDir)) { | ||
throw new RuntimeException(sprintf('Directory "%s" was not created', $tempDir)); | ||
} | ||
|
||
$locker = new Locker($tempDir, basename(__FILE__), Locker::BLOCKING); | ||
|
||
echo "== Example od Blocking locker ==\n\n"; | ||
|
||
echo "Locker is lock following code of script to 30 seconds, try to run same script from another PHP process.\n"; | ||
|
||
$locker->lock(); | ||
|
||
echo "Locker is successfully locked.\n"; | ||
|
||
for ($i = 0; $i < 30; $i++) { | ||
echo "."; | ||
sleep(1); | ||
} | ||
|
||
$locker->unlock(); | ||
|
||
echo "\nLocker is unlocked. Done.\n"; |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
use Redbitcz\Utils\Lock\Exception\LockObtainException; | ||
use Redbitcz\Utils\Lock\Locker; | ||
|
||
require_once __DIR__ . '/../../vendor/autoload.php'; | ||
|
||
$tempDir = __DIR__ . '/temp'; | ||
|
||
if (!@mkdir($tempDir) && !is_dir($tempDir)) { | ||
throw new RuntimeException(sprintf('Directory "%s" was not created', $tempDir)); | ||
} | ||
|
||
$locker = new Locker($tempDir, basename(__FILE__), Locker::NON_BLOCKING); | ||
|
||
echo "== Example od Non-blocking locker ==\n\n"; | ||
|
||
echo "Locker is lock following code of script to 30 seconds, try to run same script from another PHP process.\n"; | ||
|
||
try { | ||
$locker->lock(); | ||
|
||
echo "Locker is successfully locked.\n"; | ||
|
||
for ($i = 0; $i < 30; $i++) { | ||
echo "."; | ||
sleep(1); | ||
} | ||
|
||
$locker->unlock(); | ||
|
||
echo "\nLocker is unlocked. Done.\n"; | ||
} catch (LockObtainException $e) { | ||
echo 'Error: lock is alreasy locked by another process! ' . $e->getMessage(); | ||
} |
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Redbitcz\Utils\Lock\Exception; | ||
|
||
use LogicException; | ||
|
||
class DirectoryNotFoundException extends LogicException | ||
|
||
{ | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Redbitcz\Utils\Lock\Exception; | ||
|
||
use RuntimeException; | ||
|
||
class LockObtainException extends RuntimeException | ||
{ | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Redbitcz\Utils\Lock; | ||
|
||
use Redbitcz\Utils\Lock\Exception\DirectoryNotFoundException; | ||
use Redbitcz\Utils\Lock\Exception\LockObtainException; | ||
|
||
class Locker | ||
{ | ||
/** | ||
* `BLOCKING` mode - Blocking lock is waiting to release previous lock. | ||
* Be careful, it may cause to deadlock of PHP processes, because lock at filesystem is not subject of | ||
* `max_execution_time` limit! | ||
*/ | ||
public const BLOCKING = 0; | ||
|
||
/** | ||
* `NON_BLOCKING` mode - Non blocking lock is fail immediately with the Exception when lock obtaining is failed. | ||
*/ | ||
public const NON_BLOCKING = LOCK_NB; | ||
|
||
/** @var string */ | ||
private $lockFile; | ||
|
||
/** @var resource|null */ | ||
private $lockHandle; | ||
|
||
/** @var int */ | ||
private $lockMode; | ||
|
||
/** | ||
* @param string $dir Path to lockfiles directory, must exists and must be writable. | ||
* @param string $name Lock name. Lockers with the same `$dir` and `$name` are interlocking. | ||
* @param int $blockMode `BLOCKING` is waiting to release previous lock, `NON_BLOCKING` doesn't wait, it fails immediately | ||
*/ | ||
public function __construct(string $dir, string $name, int $blockMode = self::NON_BLOCKING) | ||
{ | ||
if (!is_dir($dir)) { | ||
throw new DirectoryNotFoundException("Directory '$dir' not found."); | ||
} | ||
|
||
$this->lockFile = $this->getLockfileName($dir, $name); | ||
$this->lockMode = LOCK_EX | $blockMode; | ||
} | ||
|
||
public function __destruct() | ||
{ | ||
$this->unlock(); | ||
} | ||
|
||
/** | ||
* Try to obtain lock, throw `LockObtainException` when lock obtaining failed or wait to release previous lock | ||
* | ||
* @throws LockObtainException | ||
* @see `\Redbitcz\Utils\Lock\Locker::__construct()` documentation of `$blockMode` argument | ||
*/ | ||
public function lock(): void | ||
{ | ||
if ($this->lockHandle) { | ||
return; | ||
} | ||
|
||
$lockHandle = @fopen($this->lockFile, 'c+b'); // @ is escalated to exception | ||
if ($lockHandle === false) { | ||
$error = (error_get_last()['message'] ?? 'unknown error'); | ||
throw new LockObtainException("Unable to create lockfile '{$this->lockFile}', $error"); | ||
} | ||
|
||
if (@flock($lockHandle, $this->lockMode, $alreadyLocked) === false) { // @ is escalated to exception | ||
throw new LockObtainException("Unable to obtain exclusive lock on lockfile '{$this->lockFile}'"); | ||
} | ||
|
||
// Prevent leak already locked advisory lock | ||
if (($this->lockMode & self::BLOCKING) === 0 && $alreadyLocked === 1) { | ||
@flock($lockHandle, LOCK_UN); | ||
throw new LockObtainException( | ||
"Unable to obtain exclusive lock on lockfile '{$this->lockFile}', already locked" | ||
); | ||
} | ||
|
||
$this->lockHandle = $lockHandle; | ||
} | ||
|
||
/** | ||
* Unlock current lock and try to remove lockfile | ||
*/ | ||
public function unlock(): void | ||
{ | ||
if ($this->lockHandle === null) { | ||
return; | ||
} | ||
|
||
flock($this->lockHandle, LOCK_UN); | ||
fclose($this->lockHandle); | ||
$this->lockHandle = null; | ||
|
||
@unlink($this->lockFile); // @ file may not exists yet (purge tempdir, etc.) | ||
} | ||
|
||
private function getLockfileName(string $tempDir, string $namespace): string | ||
{ | ||
return $tempDir . '/rbt_' . urlencode($namespace) . '.lock'; | ||
} | ||
} |
Oops, something went wrong.