File: PHPRedisMutex.php

package info (click to toggle)
php-malkusch-lock 2.2.1%2Bds-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 412 kB
  • sloc: php: 2,193; makefile: 19
file content (110 lines) | stat: -rw-r--r-- 3,761 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
<?php

declare(strict_types=1);

namespace malkusch\lock\mutex;

use malkusch\lock\exception\LockAcquireException;
use malkusch\lock\exception\LockReleaseException;
use Redis;
use RedisException;

/**
 * Mutex based on the Redlock algorithm using the phpredis extension.
 *
 * This implementation requires at least phpredis-4.0.0. If used together with
 * the lzf extension, and phpredis is configured to use lzf compression, at
 * least phpredis-4.3.0 is required! For reason, see github issue link.
 *
 * @see https://github.com/phpredis/phpredis/issues/1477
 *
 * @author Markus Malkusch <markus@malkusch.de>
 * @license WTFPL
 *
 * @link http://redis.io/topics/distlock
 * @link bitcoin:1P5FAZ4QhXCuwYPnLZdk3PJsqePbu1UDDA Donations
 */
class PHPRedisMutex extends RedisMutex
{
    /**
     * Sets the connected Redis APIs.
     *
     * The Redis APIs needs to be connected. I.e. Redis::connect() was
     * called already.
     *
     * @param array<\Redis|\RedisCluster> $redisAPIs The Redis connections.
     * @param string $name The lock name.
     * @param int $timeout The time in seconds a lock expires after. Default is
     *                     3 seconds.
     * @throws \LengthException The timeout must be greater than 0.
     */
    public function __construct(array $redisAPIs, string $name, int $timeout = 3)
    {
        parent::__construct($redisAPIs, $name, $timeout);
    }

    /**
     * @param \Redis|\RedisCluster $redisAPI The Redis or RedisCluster connection.
     * @throws LockAcquireException
     */
    protected function add($redisAPI, string $key, string $value, int $expire): bool
    {
        /** @var \Redis $redisAPI */
        try {
            //  Will set the key, if it doesn't exist, with a ttl of $expire seconds
            return $redisAPI->set($key, $value, ['nx', 'ex' => $expire]);
        } catch (RedisException $e) {
            $message = sprintf(
                "Failed to acquire lock for key '%s'",
                $key
            );
            throw new LockAcquireException($message, 0, $e);
        }
    }

    /**
     * @param \Redis|\RedisCluster $redis The Redis or RedisCluster connection.
     * @throws LockReleaseException
     */
    protected function evalScript($redis, string $script, int $numkeys, array $arguments)
    {
        for ($i = $numkeys; $i < count($arguments); $i++) {
            /*
             * If a serialization mode such as "php" or "igbinary" is enabled, the arguments must be
             * serialized by us, because phpredis does not do this for the eval command.
             *
             * The keys must not be serialized.
             */
            $arguments[$i] = $redis->_serialize($arguments[$i]);

            /*
             * If LZF compression is enabled for the redis connection and the runtime has the LZF
             * extension installed, compress the arguments as the final step.
             */
            if ($this->hasLzfCompression($redis)) {
                $arguments[$i] = lzf_compress($arguments[$i]);
            }
        }

        try {
            return $redis->eval($script, $arguments, $numkeys);
        } catch (RedisException $e) {
            throw new LockReleaseException('Failed to release lock', 0, $e);
        }
    }

    /**
     * Determines if lzf compression is enabled for the given connection.
     *
     * @param  \Redis|\RedisCluster $redis The Redis or RedisCluster connection.
     * @return bool TRUE if lzf compression is enabled, false otherwise.
     */
    private function hasLzfCompression($redis): bool
    {
        if (!\defined('Redis::COMPRESSION_LZF')) {
            return false;
        }

        return Redis::COMPRESSION_LZF === $redis->getOption(Redis::OPT_COMPRESSION);
    }
}