Skip to content

Commit

Permalink
Added lock for when checking and updating pids
Browse files Browse the repository at this point in the history
  • Loading branch information
jamielsharief committed Mar 4, 2021
1 parent 2173a2b commit d93f609
Show file tree
Hide file tree
Showing 3 changed files with 202 additions and 0 deletions.
10 changes: 10 additions & 0 deletions src/Schedule/Event.php
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,16 @@ public function pids(): array

$pidsFile = $this->lockFile();

/**
* Lock this process but does not stop race if you call schedule:run twice at the same time
* which you should not.
*/
$lock = new Lock('event-' . $this->id());

if (! $lock->acquire()) {
throw new RuntimeException('Error getting lock');
}

if (file_exists($pidsFile)) {
$contents = file_get_contents($pidsFile);

Expand Down
118 changes: 118 additions & 0 deletions src/Schedule/Lock.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<?php
/**
* OriginPHP Framework
* Copyright 2018 - 2021 Jamiel Sharief.
*
* Licensed under The MIT License
* The above copyright notice and this permission notice shall be included in all copies or substantial
* portions of the Software.
*
* @copyright Copyright (c) Jamiel Sharief
* @link https://www.originphp.com
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
declare(strict_types = 1);
namespace Origin\Schedule;

use Exception;
use LogicException;
use RuntimeException;

/**
* New class, this might be eventually moved into own namespace or into filesystem, not
* sure yet.
*/
class Lock
{
const BLOCKING = LOCK_EX;
const NON_BLOCKING = LOCK_EX | LOCK_NB;

/**
* Path to lock file
*
* @var string
*/
private $path;

/**
* File pointer
*
* @var resource
*/
private $fp = null;

/**
* @param string $path
*/
public function __construct(string $name)
{
$this->path = sys_get_temp_dir() . "/{$name}.lock";
}

/**
* Acquires a lock, if it cannot get a lock it will return false.
*
* @param boolean $block
* @return boolean
*/
public function acquire(bool $block = true): bool
{
if ($this->fp) {
throw new LogicException('Lock has already been acquired');
}

touch($this->path); // Ensure file exists

$this->fp = fopen($this->path, 'r+');
if (! $this->fp) {
throw new RuntimeException('Error opening file');
}

if (! flock($this->fp, $block ? self::BLOCKING : self::NON_BLOCKING)) {
$this->closeFile();

return false;
}

return ftruncate($this->fp, 0) && fwrite($this->fp, (string) getmypid()) && fflush($this->fp);
}

/**
* Releases the lock
*
* @return void
*/
public function release(): void
{
if (! $this->fp) {
throw new LogicException('Lock was not acquired');
}

if (! flock($this->fp, LOCK_UN)) {
throw new RuntimeException('Error releasing lock');
}

$this->closeFile();
}

/**
* Close the file if its still open
*/
public function __destruct()
{
$this->closeFile();
}

/**
* Closes the file and cleans up
*
* @return void
*/
private function closeFile(): void
{
if ($this->fp && ! fclose($this->fp)) {
throw new Exception('Error closing file');
}
$this->fp = null;
}
}
74 changes: 74 additions & 0 deletions tests/TestCase/Schedule/LockTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php
/**
* OriginPHP Framework
* Copyright 2018 - 2021 Jamiel Sharief.
*
* Licensed under The MIT License
* The above copyright notice and this permission notice shall be included in all copies or substantial
* portions of the Software.
*
* @copyright Copyright (c) Jamiel Sharief
* @link https://www.originphp.com
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Origin\Test\TestCase\Schedule;

use LogicException;
use Origin\Schedule\Lock;

class LockTest extends \PHPUnit\Framework\TestCase
{
public function testAcquire()
{
$lock = new Lock('test');
$file = sys_get_temp_dir() . '/test.lock';
$this->assertFileDoesNotExist($file);
$this->assertTrue($lock->acquire());
$this->assertFileExists($file);
$this->assertEquals((string) getmypid(), trim(file_get_contents($file)));
}

public function testAcquireAlreadyLocked()
{
$lock = new Lock('test');
$this->assertTrue($lock->acquire());

$this->expectException(LogicException::class);
$lock->acquire();
}

public function testAcquireUnableToGetLock()
{
$lock1 = new Lock('test');
$lock2 = new Lock('test');

$this->assertTrue($lock1->acquire());
$this->assertFalse($lock2->acquire(false));
}

public function testRelease()
{
$lock1 = new Lock('test');
$lock2 = new Lock('test');

$this->assertTrue($lock1->acquire());
$this->assertFalse($lock2->acquire(false));
$lock1->release();
$this->assertTrue($lock2->acquire(false));
}

public function testReleaseNotAcquired()
{
$lock = new Lock('test');
$this->expectException(LogicException::class);
$lock->release();
}

protected function setUp(): void
{
$file = sys_get_temp_dir() . '/test.lock';
if (file_exists($file)) {
unlink($file);
}
}
}

0 comments on commit d93f609

Please sign in to comment.