Skip to content
This repository has been archived by the owner on Oct 8, 2024. It is now read-only.

Commit

Permalink
A cross-process lock.
Browse files Browse the repository at this point in the history
  • Loading branch information
fmizzell committed Jun 25, 2019
0 parents commit 85cce7d
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 0 deletions.
26 changes: 26 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
version: 2.0
jobs:
build:
environment:
CC_TEST_REPORTER_ID: b0e2dc22a6c4744ffb6a4a8597af2062cce268cbfd8c9baaaa660d79ed33a803
docker:
- image: circleci/php:7-cli-node-browsers-legacy
working_directory: ~/repo
steps:
- checkout
- run:
name: Setup dependencies
command: |
sudo composer self-update
composer install -n --prefer-dist
- run:
name: Setup Code Climate test-reporter
command: |
curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
chmod +x ./cc-test-reporter
- run:
name: Run tests
command: |
./cc-test-reporter before-build
vendor/bin/phpunit --testsuite all --coverage-clover clover.xml
./cc-test-reporter after-build --coverage-input-type clover --exit-code $?
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Locker
![CircleCI (all branches)](https://img.shields.io/circleci/project/github/fmizzell/datastore.svg)
[![Maintainability](https://api.codeclimate.com/v1/badges/c04818119efc862221cd/maintainability)](https://codeclimate.com/github/fmizzell/locker/maintainability)
[![Test Coverage](https://api.codeclimate.com/v1/badges/c04818119efc862221cd/test_coverage)](https://codeclimate.com/github/fmizzell/locker/test_coverage)
[![GPLv3 license](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0.en.html)

A cross-process lock.
19 changes: 19 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "fmizzell/locker",
"description": "A cross-process lock.",
"license": "GPL-3.0-only",
"authors": [
{
"name": "fmizzell",
"email": "[email protected]"
}
],
"autoload": {
"psr-4": {
"Locker\\": "src/"
}
},
"require-dev": {
"phpunit/phpunit": "^7.4"
}
}
18 changes: 18 additions & 0 deletions phpunit.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/6.3/phpunit.xsd"
verbose="false">

<testsuites>
<testsuite name="all">
<directory suffix="Test.php" phpVersion="7.2" phpVersionOperator=">=">test</directory>
</testsuite>
</testsuites>

<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">src</directory>
</whitelist>
</filter>

</phpunit>
86 changes: 86 additions & 0 deletions src/Locker.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?php

namespace Locker;


class Locker
{

private $timeWaited = -1;
private $name;
private $expire;
private $wait;

/**
* Constructor.
*
* @param string $name
* The locks name.
*
* @param int $expire
* When the lock is set, it will automatically expire after
* this amount of time has passed if the lock is not released.
*
* @param int $wait
* Time we are willing to wait for the lock before we bail.
*/
public function __construct($name, $expire = 30, $wait = 5)
{
$this->name = $name;
$this->expire = $expire;
$this->wait = $wait;
}

/**
* Wait until we get the lock, or we get tired of waiting.
*/
public function getLock() {
do {
$this->wait();
if ($this->timeWaited > $this->wait) {
$this->timeWaited = -1;
throw new \Exception("The lock was not acquire after {$this->wait} second(s).");
}
$this->lockExpired();
} while (!$this->lockCreated());

$this->timeWaited = -1;
return TRUE;
}

/**
* Release the lock.
*/
public function releaseLock() {
$path = "/tmp/{$this->name}.lock";
if (file_exists($path)) {
array_map('unlink', glob("{$path}/*.*"));
rmdir($path);
}
}

private function wait() {
if ($this->timeWaited >= 0) {
sleep(1);
}
$this->timeWaited++;
}

private function lockExpired() {
$file = "/tmp/{$this->name}.lock/expire.txt";
if (file_exists($file) && file_get_contents($file) < time()) {
$this->releaseLock();
return TRUE;
}
return FALSE;
}

private function lockCreated() {
$lock_path = "/tmp/{$this->name}.lock";
if (@mkdir($lock_path, 0700)) {
file_put_contents("{$lock_path}/expire.txt", (time() + $this->expire));
return TRUE;
}
return FALSE;
}
}
38 changes: 38 additions & 0 deletions test/LockerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php


class LockerTest extends \PHPUnit\Framework\TestCase
{
private $locker;

protected function setUp()
{
$this->locker = new \Locker\Locker("test", 5, 1);
}

public function testReleaseAndWait() {
$this->assertTrue($this->locker->getLock());

try {
$this->locker->getLock();
}
catch(\Exception $e) {
$this->assertEquals($e->getMessage(), "The lock was not acquire after 1 second(s).");
}

$this->locker->releaseLock();
$this->assertTrue($this->locker->getLock());
}

public function testExpiration() {
$this->assertTrue($this->locker->getLock());
sleep(6);
$this->assertTrue($this->locker->getLock());
}

protected function tearDown()
{
$this->locker->releaseLock();
}

}

0 comments on commit 85cce7d

Please sign in to comment.