RedisSimpleLock   A
last analyzed

Complexity

Total Complexity 11

Size/Duplication

Total Lines 77
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 0
Metric Value
wmc 11
lcom 1
cbo 3
dl 0
loc 77
rs 10
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 16 4
A acquire() 0 11 3
A release() 0 5 1
A __destruct() 0 4 1
A releaseClosure() 0 19 2
1
<?php
2
3
namespace TH\RedisLock;
4
5
use Exception;
6
use Predis\Client;
7
use Predis\Response\Status;
8
use Psr\Log\LoggerInterface;
9
use Psr\Log\NullLogger;
10
use TH\Lock\Lock;
11
12
class RedisSimpleLock implements Lock
13
{
14
    private $identifier;
15
    private $client;
16
    private $ttl;
17
    private $logger;
18
    private $id;
19
20
    /**
21
     * Create new RedisSimpleLock
22
     *
23
     * @param string               $identifier the redis lock key
24
     * @param Client               $client     the Predis client
25
     * @param integer              $ttl        lock time-to-live in milliseconds
26
     * @param LoggerInterface|null $logger
27
     * @param array                $ignoredSAPIs the server apis to ignore the pcntl_signal callback for
28
     */
29
    public function __construct($identifier, Client $client, $ttl = 10000, LoggerInterface $logger = null, array $ignoredSAPIs = [])
30
    {
31
        $this->identifier = $identifier;
32
        $this->client     = $client;
33
        $this->ttl        = $ttl;
34
        $this->logger     = $logger ?: new NullLogger;
35
        $this->id         = mt_rand();
36
        register_shutdown_function($closure = $this->releaseClosure());
37
38
        if (!in_array(php_sapi_name(), $ignoredSAPIs)) {
39
            if (!function_exists('pcntl_signal')) {
40
                throw new \RuntimeException("pcntl_signal() from the pcntl extension is not availlable, configure `$ignoredSAPIs = ['".php_sapi_name()."']` to skip catching SIGINT signal.");
41
            }
42
            pcntl_signal(SIGINT, $closure);
43
        }
44
    }
45
46
    public function acquire()
47
    {
48
        $log_data = ["identifier" => $this->identifier];
49
        $response = $this->client->set($this->identifier, $this->id, "PX", $this->ttl, "NX");
50
        if (!$response instanceof Status || $response->getPayload() !== "OK") {
51
            $this->logger->debug("could not acquire lock on {identifier}", $log_data);
52
53
            throw new Exception("Could not acquire lock on " . $this->identifier);
54
        }
55
        $this->logger->debug("lock acquired on {identifier}", $log_data);
56
    }
57
58
    public function release()
59
    {
60
        $closure = $this->releaseClosure();
61
        $closure();
62
    }
63
64
    public function __destruct()
65
    {
66
        $this->release();
67
    }
68
69
    private function releaseClosure()
70
    {
71
        $client = $this->client;
72
        $id = $this->id;
73
        $identifier = $this->identifier;
74
        $logger = $this->logger;
75
76
        $closure = function () use ($client, $identifier, $id, $logger) {
77
            $script = <<<LUA
78
    if redis.call("get", KEYS[1]) == ARGV[1] then
79
        return redis.call("del", KEYS[1])
80
    end
81
LUA;
82
            if ($client->eval($script, 1, $identifier, $id)) {
83
                $logger->debug("lock released on {identifier}", ["identifier" => $identifier]);
84
            }
85
        };
86
        return $closure->bindTo(null);
87
    }
88
}
89