Skip to content

Commit a7fa877

Browse files
author
Willem Stuursma-Ruwen
authored
Merge pull request #32 from TheLevti/develop
Improve phpredis support
2 parents 093f389 + 8eb660c commit a7fa877

File tree

6 files changed

+105
-38
lines changed

6 files changed

+105
-38
lines changed

.travis.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ cache:
99
php:
1010
- 7.1
1111
- 7.2
12+
- 7.3
1213
- nightly
1314

1415
env:
@@ -32,13 +33,15 @@ matrix:
3233
- php: nightly
3334

3435
before_install:
36+
- pecl update-channels
37+
- yes | pecl install -f igbinary lzf redis
3538
- phpenv config-add tests/php-travis.ini
3639
- redis-server --port 63791 &
3740
- redis-server --port 63792 &
3841
- redis-server --port 63793 &
3942

4043
install:
41-
- composer install --no-scripts --no-suggest --no-interaction
44+
- composer install --no-progress --no-scripts --no-suggest --no-interaction
4245

4346
before_script:
4447
- mysql -e 'create database test;'

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ php-lock/lock follows semantic versioning. Read more on [semver.org][1].
2525
- Optionally the [php-pcntl][3] extension to enable locking with `flock()` without
2626
busy waiting in CLI scripts.
2727
- Optionally `flock()`, `ext-redis`, `ext-pdo_mysql`, `ext-pdo_sqlite`, `ext-pdo_pgsql` or `ext-memcached` can be used as a backend for locks. See examples below.
28+
- If `ext-redis` is used for locking and is configured to use igbinary for serialization or
29+
lzf for compression, additionally `ext-igbinary` and/or `ext-lzf` have to be installed.
2830

2931
----
3032

classes/mutex/PHPRedisMutex.php

Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,19 @@
22

33
namespace malkusch\lock\mutex;
44

5-
use Redis;
6-
use RedisException;
75
use malkusch\lock\exception\LockAcquireException;
86
use malkusch\lock\exception\LockReleaseException;
7+
use Redis;
8+
use RedisException;
99

1010
/**
1111
* Mutex based on the Redlock algorithm using the phpredis extension.
1212
*
13-
* This implementation requires at least phpredis-2.2.4.
13+
* This implementation requires at least phpredis-4.0.0. If used together with
14+
* the lzf extension, and phpredis is configured to use lzf compression, at
15+
* least phpredis-4.3.0 is required! For reason, see github issue link.
16+
*
17+
* @see https://github.com/phpredis/phpredis/issues/1477
1418
*
1519
* @author Markus Malkusch <[email protected]>
1620
* @license WTFPL
@@ -23,12 +27,13 @@ class PHPRedisMutex extends RedisMutex
2327
/**
2428
* Sets the connected Redis APIs.
2529
*
26-
* The Redis APIs needs to be connected yet. I.e. Redis::connect() was
30+
* The Redis APIs needs to be connected. I.e. Redis::connect() was
2731
* called already.
2832
*
29-
* @param Redis[] $redisAPIs The Redis connections.
30-
* @param string $name The lock name.
31-
* @param int $timeout The time in seconds a lock expires, default is 3.
33+
* @param array<\Redis|\RedisCluster> $redisAPIs The Redis connections.
34+
* @param string $name The lock name.
35+
* @param int $timeout The time in seconds a lock expires after.
36+
* Default is 3.
3237
*
3338
* @throws \LengthException The timeout must be greater than 0.
3439
*/
@@ -42,7 +47,7 @@ public function __construct(array $redisAPIs, string $name, int $timeout = 3)
4247
*/
4348
protected function add($redisAPI, string $key, string $value, int $expire): bool
4449
{
45-
/** @var Redis $redisAPI */
50+
/** @var \Redis $redisAPI */
4651
try {
4752
// Will set the key, if it doesn't exist, with a ttl of $expire seconds
4853
return $redisAPI->set($key, $value, ["nx", "ex" => $expire]);
@@ -55,18 +60,28 @@ protected function add($redisAPI, string $key, string $value, int $expire): bool
5560
}
5661
}
5762

63+
/**
64+
* @param \Redis|\RedisCluster $redis The Redis or RedisCluster connection.
65+
* @throws LockReleaseException
66+
*/
5867
protected function evalScript($redis, string $script, int $numkeys, array $arguments)
5968
{
60-
/** @var Redis $redis */
61-
62-
/*
63-
* If a serializion mode such as "php" or "igbinary" is enabled, the arguments must be serialized but the keys
64-
* must not.
65-
*
66-
* @issue 14
67-
*/
68-
for ($i = $numkeys, $iMax = \count($arguments); $i < $iMax; $i++) {
69+
for ($i = $numkeys; $i < \count($arguments); $i++) {
70+
/*
71+
* If a serialization mode such as "php" or "igbinary" is enabled, the arguments must be
72+
* serialized by us, because phpredis does not do this for the eval command.
73+
*
74+
* The keys must not be serialized.
75+
*/
6976
$arguments[$i] = $redis->_serialize($arguments[$i]);
77+
78+
/*
79+
* If LZF compression is enabled for the redis connection and the runtime has the LZF
80+
* extension installed, compress the arguments as the final step.
81+
*/
82+
if ($this->hasLzfCompression($redis)) {
83+
$arguments[$i] = lzf_compress($arguments[$i]);
84+
}
7085
}
7186

7287
try {
@@ -75,4 +90,19 @@ protected function evalScript($redis, string $script, int $numkeys, array $argum
7590
throw new LockReleaseException("Failed to release lock", 0, $e);
7691
}
7792
}
93+
94+
/**
95+
* Determines if lzf compression is enabled for the given connection.
96+
*
97+
* @param \Redis|\RedisCluster $redis Redis connection.
98+
* @return bool TRUE if lzf compression is enabled, false otherwise.
99+
*/
100+
private function hasLzfCompression($redis): bool
101+
{
102+
if (!\defined('Redis::COMPRESSION_LZF')) {
103+
return false;
104+
}
105+
106+
return Redis::COMPRESSION_LZF === $redis->getOption(Redis::OPT_COMPRESSION);
107+
}
78108
}

composer.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@
4646
"psr/log": "^1"
4747
},
4848
"require-dev": {
49+
"ext-igbinary": "*",
50+
"ext-lzf": "*",
4951
"ext-memcached": "*",
5052
"ext-pcntl": "*",
5153
"ext-pdo_mysql": "*",
@@ -62,9 +64,11 @@
6264
"squizlabs/php_codesniffer": "^3.3"
6365
},
6466
"suggest": {
67+
"ext-igbinary": "To use this library with PHP Redis igbinary serializer enabled.",
68+
"ext-lzf": "To use this library with PHP Redis lzf compression enabled.",
6569
"ext-pnctl": "Enables locking with flock without busy waiting in CLI scripts.",
66-
"ext-sysvsem": "Enables locking using semaphores.",
6770
"ext-redis": "To use this library with the PHP Redis extension.",
71+
"ext-sysvsem": "Enables locking using semaphores.",
6872
"predis/predis": "To use this library with predis."
6973
},
7074
"archive": {

tests/mutex/PHPRedisMutexTest.php

Lines changed: 47 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,7 @@ protected function setUp()
4141
$uri = parse_url($redisUri);
4242

4343
// original Redis::set and Redis::eval calls will reopen the connection
44-
$connection = new class extends Redis
45-
{
44+
$connection = new class extends Redis {
4645
private $is_closed = false;
4746

4847
public function close()
@@ -100,6 +99,9 @@ private function closeMinorityConnections()
10099
}
101100

102101
$numberToClose = ceil(count($this->connections) / 2) - 1;
102+
if (0 >= $numberToClose) {
103+
return;
104+
}
103105

104106
foreach ((array) array_rand($this->connections, $numberToClose) as $keyToClose) {
105107
$this->connections[$keyToClose]->close();
@@ -132,27 +134,33 @@ public function testEvalScriptFails()
132134
}
133135

134136
/**
135-
* @param $serialization
136-
* @dataProvider dpSerializationModes
137+
* @dataProvider serializationAndCompressionModes
137138
*/
138-
public function testSynchronizedWorks($serialization)
139+
public function testSerializersAndCompressors($serializer, $compressor)
139140
{
140141
foreach ($this->connections as $connection) {
141-
$connection->setOption(Redis::OPT_SERIALIZER, $serialization);
142+
$connection->setOption(Redis::OPT_SERIALIZER, $serializer);
143+
$connection->setOption(Redis::OPT_COMPRESSION, $compressor);
142144
}
143145

144-
$this->assertSame("test", $this->mutex->synchronized(function (): string {
145-
return "test";
146-
}));
146+
$this->assertSame(
147+
"test",
148+
$this->mutex->synchronized(function (): string {
149+
return "test";
150+
})
151+
);
147152
}
148153

149154
public function testResistantToPartialClusterFailuresForAcquiringLock()
150155
{
151156
$this->closeMinorityConnections();
152157

153-
$this->assertSame("test", $this->mutex->synchronized(function (): string {
154-
return "test";
155-
}));
158+
$this->assertSame(
159+
"test",
160+
$this->mutex->synchronized(function (): string {
161+
return "test";
162+
})
163+
);
156164
}
157165

158166
public function testResistantToPartialClusterFailuresForReleasingLock()
@@ -163,21 +171,42 @@ public function testResistantToPartialClusterFailuresForReleasingLock()
163171
}));
164172
}
165173

166-
public function dpSerializationModes()
174+
public function serializationAndCompressionModes()
167175
{
168176
if (!class_exists(Redis::class)) {
169177
return [];
170178
}
171179

172-
$serializers = [
173-
[Redis::SERIALIZER_NONE],
174-
[Redis::SERIALIZER_PHP],
180+
$options = [
181+
[Redis::SERIALIZER_NONE, Redis::COMPRESSION_NONE],
182+
[Redis::SERIALIZER_PHP, Redis::COMPRESSION_NONE],
175183
];
176184

177185
if (defined("Redis::SERIALIZER_IGBINARY")) {
178-
$serializers[] = [constant("Redis::SERIALIZER_IGBINARY")];
186+
$options[] = [
187+
constant("Redis::SERIALIZER_IGBINARY"),
188+
Redis::COMPRESSION_NONE
189+
];
190+
}
191+
192+
if (defined("Redis::COMPRESSION_LZF")) {
193+
$options[] = [
194+
Redis::SERIALIZER_NONE,
195+
constant("Redis::COMPRESSION_LZF")
196+
];
197+
$options[] = [
198+
Redis::SERIALIZER_PHP,
199+
constant("Redis::COMPRESSION_LZF")
200+
];
201+
202+
if (defined("Redis::SERIALIZER_IGBINARY")) {
203+
$options[] = [
204+
constant("Redis::SERIALIZER_IGBINARY"),
205+
constant("Redis::COMPRESSION_LZF")
206+
];
207+
}
179208
}
180209

181-
return $serializers;
210+
return $options;
182211
}
183212
}

tests/php-travis.ini

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
extension = "memcached.so"
2-
extension = "redis.so"

0 commit comments

Comments
 (0)