Skip to content

Commit 1edbe8d

Browse files
committed
initial commit
0 parents  commit 1edbe8d

10 files changed

+736
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
composer.lock
2+
composer.phar
3+
vendor

.travis.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
language: php
2+
3+
sudo: required
4+
5+
php:
6+
- 7.4
7+
- nightly
8+
- master
9+
10+
env:
11+
- TARANTOOL_VERSION=1.10
12+
- TARANTOOL_VERSION=2.0
13+
- TARANTOOL_VERSION=2.1
14+
- TARANTOOL_VERSION=2.2
15+
16+
matrix:
17+
fast_finish: true
18+
allow_failures:
19+
- php: nightly
20+
- php: master
21+
22+
services:
23+
- docker
24+
25+
before_script:
26+
- docker run -d -p 3301:3301 progaudi/tarantool:$TARANTOOL_VERSION
27+
- composer install --no-interaction --prefer-source --optimize-autoloader
28+
29+
script: vendor/bin/phpunit
30+
31+
after_script:
32+
- if [[ -f coverage.clover ]]; then
33+
curl -sSOL https://scrutinizer-ci.com/ocular.phar &&
34+
php ocular.phar code-coverage:upload --format=php-clover coverage.clover;
35+
fi

README.md

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# Tarantool symfony-lock
2+
[![License](https://poser.pugx.org/tarantool/symfony-lock/license.png)](https://packagist.org/packages/tarantool/symfony-lock)
3+
[![Build Status](https://travis-ci.org/tarantool-php/symfony-lock.svg?branch=master)](https://travis-ci.org/tarantool-php/symfony-lock)
4+
[![Latest Version](https://img.shields.io/github/release/tarantool-php/symfony-lock.svg?style=flat-square)](https://github.com/tarantool-php/symfony-lock/releases)
5+
[![Total Downloads](https://img.shields.io/packagist/dt/tarantool/symfony-lock.svg?style=flat-square)](https://packagist.org/packages/tarantool/symfony-lock)
6+
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/tarantool-php/symfony-lock/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/tarantool-php/symfony-lock/?branch=master)
7+
[![Code Coverage](https://scrutinizer-ci.com/g/tarantool-php/symfony-lock/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/tarantool-php/symfony-lock/?branch=master)
8+
9+
# About
10+
11+
The `TarantoolStore` implements symfony `PersistingStoreInterface` and persist locks using Tarantool Database.
12+
13+
# Installation
14+
15+
The recommended way to install the library is through [Composer](http://getcomposer.org):
16+
```
17+
$ composer require tarantool/symfony-lock
18+
```
19+
20+
# Prepare Tarantool
21+
22+
To start working on locking you need to create schema, the package contains schema manager that can be used for that. For additional documentation on client configuration see [tarantool/client repository](https://github.com/tarantool-php/client#creating-a-client).
23+
24+
```php
25+
use Tarantool\Client\Client;
26+
use Tarantool\SymfonyLock\SchemaManager;
27+
28+
$client = Client::fromDefaults();
29+
$schema = new SchemaManager($client);
30+
31+
// create spaces and indexes
32+
$schema->setup();
33+
34+
// later if you want to cleanup lock space, use
35+
$schema->tearDown();
36+
37+
```
38+
39+
# Using Store
40+
41+
For additional examples on lock factory usage follow [symfony/lock docs](https://symfony.com/doc/current/components/lock.html)
42+
43+
```php
44+
use Symfony\Component\Lock\LockFactory;
45+
use Tarantool\Client\Client;
46+
use Tarantool\SymfonyLock\TarantoolStore;
47+
48+
$client = Client::fromDefaults();
49+
$store = new TarantoolStore($client);
50+
$factory = new LockFactory($store);
51+
52+
$lock = $factory->createLock('pdf-invoice-generation');
53+
54+
if ($lock->acquire()) {
55+
// The resource "pdf-invioce-generation" is locked.
56+
// You can compute and generate invoice safely here.
57+
$lock->release();
58+
}
59+
60+
```
61+
62+
# Expiration helper
63+
64+
When key is expired it will be removed on acquiring new lock with same name. If your key names are not unique, you can use expiration daemon to cleanup your database.
65+
66+
```php
67+
use Tarantool\Client\Client;
68+
use Tarantool\SymfonyLock\ExpirationDaemon;
69+
70+
$client = Client::fromDefaults();
71+
$expiration = new ExpirationDaemon($client);
72+
73+
// cleanup keys that are expired
74+
$expiration->process();
75+
76+
// by default expiration daemon will clean upto 100 items
77+
// you can override it via optional configuration
78+
$expiration = new ExpirationDaemon($client, [
79+
'limit' => 10,
80+
]);
81+
82+
```
83+
84+
# Customization
85+
Out of the box all classes are using space named `lock`. If you want - you can override it via options configuration. All available options and default values are listed below:
86+
87+
```php
88+
use Tarantool\Client\Client;
89+
use Tarantool\SymfonyLock\ExpirationDaemon;
90+
use Tarantool\SymfonyLock\SchemaManager;
91+
use Tarantool\SymfonyLock\TarantoolStore;
92+
93+
$client = Client::fromDefaults();
94+
$expiration = new ExpirationDaemon($client, [
95+
'space' => 'lock',
96+
'limit' => '100',
97+
]);
98+
99+
$schema = new SchemaManager($client, [
100+
'engine' => 'memtx',
101+
'space' => 'lock',
102+
]);
103+
104+
$store = new TarantoolStore($client, [
105+
'space' => 'lock',
106+
'initialTtl' => 300,
107+
]);
108+
```

composer.json

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"name": "tarantool/symfony-lock",
3+
"description": "Symfony lock Tarantool store",
4+
"keywords": ["tarantool", "client", "pure", "nosql", "mapper"],
5+
"type": "library",
6+
"license": "MIT",
7+
"require": {
8+
"php": "^7.4",
9+
"symfony/lock": "^5.0",
10+
"tarantool/client": "^0.7"
11+
},
12+
"suggest": {
13+
"ext-msgpack": "For using PeclPacker.",
14+
"rybakit/msgpack": "For using PurePacker."
15+
},
16+
"require-dev": {
17+
"phpunit/phpunit": "^6.5.4",
18+
"rybakit/msgpack": "^0.6.1"
19+
},
20+
"autoload-dev": {
21+
"psr-4": {
22+
"": "tests"
23+
},
24+
"classmap": ["tests"]
25+
},
26+
"autoload": {
27+
"psr-4": {
28+
"Tarantool\\SymfonyLock\\": "src/"
29+
}
30+
},
31+
"authors": [
32+
{
33+
"name": "Dmitry Krokhin",
34+
"email": "[email protected]"
35+
}
36+
]
37+
}

phpunit.xml.dist

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<phpunit backupGlobals="false"
4+
backupStaticAttributes="false"
5+
colors="true"
6+
convertErrorsToExceptions="true"
7+
convertNoticesToExceptions="true"
8+
convertWarningsToExceptions="true"
9+
processIsolation="false"
10+
stopOnFailure="false"
11+
syntaxCheck="false"
12+
verbose="true"
13+
bootstrap="./vendor/autoload.php"
14+
>
15+
<php>
16+
<env name="TARANTOOL_CONNECTION_HOST" value="127.0.0.1" />
17+
<env name="TARANTOOL_CONNECTION_PORT" value="3301" />
18+
</php>
19+
20+
<testsuites>
21+
<testsuite name="Tests">
22+
<directory>./tests</directory>
23+
</testsuite>
24+
</testsuites>
25+
26+
<logging>
27+
<log type="coverage-clover" target="coverage.clover"/>
28+
</logging>
29+
30+
<filter>
31+
<whitelist>
32+
<directory>src</directory>
33+
</whitelist>
34+
</filter>
35+
</phpunit>

src/ExpirationDaemon.php

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
namespace Tarantool\SymfonyLock;
4+
5+
use Tarantool\Client\Client;
6+
use InvalidArgumentException;
7+
8+
class ExpirationDaemon
9+
{
10+
use OptionalConstructor;
11+
12+
/**
13+
* Dropped rows counter limit
14+
*/
15+
protected int $limit = 100;
16+
17+
/**
18+
* Space name
19+
*/
20+
protected string $space = 'lock';
21+
22+
protected function validateOptions()
23+
{
24+
if ($this->limit <= 0) {
25+
$message = sprintf(
26+
'limit expects a strictly positive TTL. Got %d.',
27+
$this->limit
28+
);
29+
throw new InvalidArgumentException($message);
30+
}
31+
if ($this->space == '') {
32+
throw new InvalidArgumentException("Space should be defined");
33+
}
34+
}
35+
36+
public function process(): int
37+
{
38+
$script = <<<LUA
39+
local limit, timestamp = ...
40+
local counter = 0
41+
box.begin()
42+
box.space.$this->space.index.expire:pairs()
43+
:take_while(function(tuple) return counter < limit end)
44+
:take_while(function(tuple) return tuple.expire <= timestamp end)
45+
:each(function(tuple)
46+
box.space.$this->space:delete( tuple.key )
47+
counter = counter + 1
48+
end)
49+
box.commit()
50+
return counter
51+
LUA;
52+
53+
[$counter] = $this->client->evaluate($script, $this->limit, microtime(true));
54+
55+
return $counter;
56+
}
57+
}

src/OptionalConstructor.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace Tarantool\SymfonyLock;
4+
5+
use Tarantool\Client\Client;
6+
7+
trait OptionalConstructor
8+
{
9+
private Client $client;
10+
11+
public function __construct(Client $client, array $options = [])
12+
{
13+
foreach ($options as $k => $v) {
14+
if (property_exists($this, $k)) {
15+
$this->$k = $options[$k];
16+
}
17+
}
18+
19+
$this->client = $client;
20+
21+
$this->validateOptions();
22+
}
23+
}

src/SchemaManager.php

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<?php
2+
3+
namespace Tarantool\SymfonyLock;
4+
5+
use Tarantool\Client\Client;
6+
use InvalidArgumentException;
7+
8+
class SchemaManager
9+
{
10+
use OptionalConstructor;
11+
12+
/**
13+
* what engine should be used
14+
*/
15+
protected string $engine = 'memtx';
16+
17+
/**
18+
* space name
19+
*/
20+
protected string $space = 'lock';
21+
22+
public function validateOptions()
23+
{
24+
if ($this->engine == '') {
25+
throw new InvalidArgumentException("Engine should be defined");
26+
}
27+
if ($this->space == '') {
28+
throw new InvalidArgumentException("Space should be defined");
29+
}
30+
}
31+
/**
32+
* setup database schema
33+
*/
34+
public function setup()
35+
{
36+
$this->client->evaluate('box.schema.create_space(...)', $this->space, [
37+
'engine' => $this->engine,
38+
'if_not_exists' => true,
39+
'format' => [
40+
[
41+
'name' => 'key',
42+
'type' => 'string',
43+
],
44+
[
45+
'name' => 'token',
46+
'type' => 'string',
47+
],
48+
[
49+
'name' => 'expire',
50+
'type' => 'number',
51+
],
52+
],
53+
]);
54+
55+
$this->client->call("box.space.{$this->space}:create_index", "key", [
56+
'type' => 'hash',
57+
'if_not_exists' => true,
58+
'unique' => true,
59+
'parts' => ['key'],
60+
]);
61+
62+
$this->client->call("box.space.{$this->space}:create_index", "token_key", [
63+
'type' => 'hash',
64+
'if_not_exists' => true,
65+
'unique' => true,
66+
'parts' => ['token', 'key'],
67+
]);
68+
69+
$this->client->call("box.space.{$this->space}:create_index", "expire", [
70+
'type' => 'tree',
71+
'if_not_exists' => true,
72+
'unique' => false,
73+
'parts' => ['expire'],
74+
]);
75+
}
76+
77+
/**
78+
* rollback lock store schema
79+
*/
80+
public function tearDown()
81+
{
82+
$script = <<<LUA
83+
if box.space.$this->space then
84+
box.space.$this->space:drop()
85+
end
86+
LUA;
87+
88+
$this->client->evaluate($script);
89+
}
90+
}

0 commit comments

Comments
 (0)