Manager::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 5
cts 5
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 3
crap 1
1
<?php
2
namespace AutoLock;
3
use AutoLock\Exception\ManagerCompareException;
4
5
/**
6
 * Class Manager is using to create lock and release lock
7
 * using pool.
8
 *
9
 * @package AutoLock
10
 * @author Liu Lu <[email protected]>
11
 * @since 0.1
12
 */
13
class Manager
14
{
15
    const MIN_DRIFT = 2;
16
17
    const UNLOCK_SCRIPT = '
18
            if redis.call("GET", KEYS[1]) == ARGV[1] then
19
                return redis.call("DEL", KEYS[1])
20
            else
21
                return 0
22
            end
23
    ';
24
25
    /**
26
     * @var int Delay millisecond after fail to get lock
27
     */
28
    private $retryDelay;
29
    /**
30
     * @var int Number of times after fail to get lock
31
     */
32
    private $retryCount;
33
    /**
34
     * @var float
35
     * Clock drift coefficient,
36
     * to prevent redis and web time to pass the speed of different,
37
     * resulting in lock early failure of the insurance value
38
     */
39
    private $clockDriftFactor = 0.01;
40
    /**
41
     * @var Pool
42
     */
43
    private $pool;
44
45
46 21
    public function __construct(Pool $pool, $retryDelay = 200, $retryCount = 3)
47
    {
48 21
        $this->pool = $pool;
49 21
        $this->retryDelay = $retryDelay;
50 21
        $this->retryCount = $retryCount;
51 21
    }
52
53
    /**
54
     * $ttl's unit is milliseconds, min $ttl is 2 milliseconds which means you will aways
55
     * get a lock which is expired
56
     * @param string $resource
57
     * @param int $ttl
58
     * @param bool $autoRelease
59
     * @return Lock|bool
60
     */
61 5
    public function lock($resource, $ttl, $autoRelease = false)
62
    {
63
        //@todo 验证$ttl > 0
64 5
        $ttl = (int)$ttl;
65 5
        $resource = (string)$resource;
66 5
        $token = uniqid();
67 5
        $retry = $this->retryCount;
68
        do {
69 5
            $n = 0;
70 5
            $startTime = microtime(true) * 1000;
71 5
            foreach ($this->pool as $server) {
72
                /**
73
                 * @var $server Server
74
                 */
75 5
                if ($server->set($resource, $token, array('NX', 'PX' => $ttl))) {
76 5
                    $n++;
77
                }
78
            }
79
            # Add 2 milliseconds to the drift to account for Redis expires
80
            # precision, which is 1 millisecond, plus 1 millisecond min drift
81
            # for small TTLs.
82 5
            $drift = ($ttl * $this->clockDriftFactor) + self::MIN_DRIFT;
83 5
            $validityTime = $ttl - (microtime(true) * 1000 - $startTime) - $drift;
84 5
            if ($this->pool->checkQuorum($n) && $validityTime > 0) {
85 3
                return new Lock($this, $validityTime, $resource, $token, $autoRelease);
86
            } else {
87 2
                foreach ($this->pool as $server) {
88
                    /**
89
                     * @var $server Server
90
                     */
91 2
                    $this->unlockServer($server, $resource, $token);
92
                }
93
            }
94
            // Wait a random delay before to retry
95 2
            $delay = mt_rand(floor($this->retryDelay / 2), $this->retryDelay);
96 2
            usleep($delay * 1000);
97 2
            $retry--;
98 2
        } while ($retry > 0);
99 2
        return false;
100
    }
101
102
    /**
103
     * @param Lock $lock
104
     * @throws ManagerCompareException
105
     */
106 6
    public function unlock(Lock $lock)
107
    {
108 6
        if ($lock->getManager() !== $this) {
109 1
            throw new ManagerCompareException('You must unlock with the manager who create it');
110
        }
111 5
        $resource = $lock->getResource();
112 5
        $token = $lock->getToken();
113 5
        foreach ($this->pool as $server) {
114 5
            self::unlockServer($server, $resource, $token);
115
        }
116 5
    }
117
118 7
    private static function unlockServer(Server $server, $resource, $token)
119
    {
120 7
        $script = self::UNLOCK_SCRIPT;
121 7
        $server->evalScript($script, array($resource, $token));
122 7
    }
123
124 10
    public function available()
125
    {
126 10
        $onLineServersNumber = 0;
127 10
        foreach ($this->pool as $server) {
128
            /**
129
             * @var $server Server
130
             */
131 9
            if ($server->available()) {
132 9
                $onLineServersNumber++;
133
            }
134
        }
135 10
        if ($this->pool->checkQuorum($onLineServersNumber)) {
136 4
            return true;
137
        } else {
138 6
            return false;
139
        }
140
    }
141
}