Passed
Pull Request — master (#17)
by Evgeniy
02:21
created

MysqlMutex::isFreeLock()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 7
ccs 5
cts 5
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Mutex\Mysql;
6
7
use InvalidArgumentException;
8
use PDO;
9
use RuntimeException;
10
use Yiisoft\Mutex\MutexInterface;
11
use Yiisoft\Mutex\RetryAcquireTrait;
12
13
use function sha1;
14
15
/**
16
 * MysqlMutex implements mutex "lock" mechanism via MySQL locks.
17
 */
18
final class MysqlMutex implements MutexInterface
19
{
20
    use RetryAcquireTrait;
21
22
    private string $name;
23
    private PDO $connection;
24
25
    /**
26
     * @param string $name Mutex name.
27
     * @param PDO $connection PDO connection instance to use.
28
     */
29 7
    public function __construct(string $name, PDO $connection)
30
    {
31 7
        $this->name = $name;
32 7
        $this->connection = $connection;
33
34
        /** @var string $driverName */
35 7
        $driverName = $connection->getAttribute(PDO::ATTR_DRIVER_NAME);
36
37 7
        if ($driverName !== 'mysql') {
38
            throw new InvalidArgumentException("MySQL connection instance should be passed. Got $driverName.");
39
        }
40 7
    }
41
42 7
    public function __destruct()
43
    {
44 7
        $this->release();
45 7
    }
46
47
    /**
48
     * {@inheritdoc}
49
     *
50
     * @see https://dev.mysql.com/doc/refman/8.0/en/locking-functions.html#function_get-lock
51
     * @see https://dev.mysql.com/doc/refman/8.0/en/locking-functions.html#function_is-free-lock
52
     */
53 7
    public function acquire(int $timeout = 0): bool
54
    {
55 7
        $name = $this->hashLockName();
56
57 7
        return $this->retryAcquire($timeout, function () use ($name, $timeout): bool {
58 7
            if (!$this->isFreeLock()) {
59 4
                return false;
60
            }
61
62 7
            $statement = $this->connection->prepare('SELECT GET_LOCK(:name, :timeout)');
63 7
            $statement->bindValue(':name', $name);
64 7
            $statement->bindValue(':timeout', $timeout);
65 7
            $statement->execute();
66
67 7
            return (bool) $statement->fetchColumn();
68 7
        });
69
    }
70
71
    /**
72
     * {@inheritdoc}
73
     *
74
     * @see https://dev.mysql.com/doc/refman/8.0/en/locking-functions.html#function_release-lock
75
     * @see https://dev.mysql.com/doc/refman/8.0/en/locking-functions.html#function_is-free-lock
76
     */
77 7
    public function release(): void
78
    {
79 7
        if ($this->isFreeLock()) {
80 6
            return;
81
        }
82
83 7
        $statement = $this->connection->prepare('SELECT RELEASE_LOCK(:name)');
84 7
        $statement->bindValue(':name', $this->hashLockName());
85 7
        $statement->execute();
86
87 7
        if (!$statement->fetchColumn()) {
88
            throw new RuntimeException("Unable to release lock \"$this->name\".");
89
        }
90 7
    }
91
92
    /**
93
     * Generates hash for the lock name to avoid exceeding lock name length limit.
94
     *
95
     * @return string The generated hash for the lock name.
96
     *
97
     * @see https://github.com/yiisoft/yii2/pull/16836
98
     */
99 7
    private function hashLockName(): string
100
    {
101 7
        return sha1($this->name);
102
    }
103
104
    /**
105
     * Checks whether the lock is free to use (that is, not locked).
106
     *
107
     * @return bool Whether the lock is free to use.
108
     *
109
     * @see https://dev.mysql.com/doc/refman/8.0/en/locking-functions.html#function_is-free-lock
110
     */
111 7
    private function isFreeLock(): bool
112
    {
113 7
        $statement = $this->connection->prepare('SELECT IS_FREE_LOCK(:name)');
114 7
        $statement->bindValue(':name', $this->hashLockName());
115 7
        $statement->execute();
116
117 7
        return (bool) $statement->fetchColumn();
118
    }
119
}
120