diff --git a/Changelog.md b/Changelog.md index 34e6f1e02a..27541b5753 100644 --- a/Changelog.md +++ b/Changelog.md @@ -5,7 +5,58 @@ All changes to phpredis will be documented in this file. We're basing this format on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and PhpRedis adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [5.3.4] - 2021-03-24 ([GitHub](https://github.com/phpredis/phpredis/releases/5.3.4), [PECL](https:/pecl.php.net/package/redis/5.3.4)) + +### Sponsors :sparkling_heart: + +- [Audiomack](https://audiomack.com) +- [Open LMS](https://openlms.net/) +- [BlueHost](https://bluehost.com) +- [Object Cache Pro for WordPress](https://objectcache.pro/) +- [Avtandil Kikabidze](https://github.com/akalongman) +- [Zaher Ghaibeh](https://github.com/zaherg) +- [BatchLabs](https://batch.com) + +### Fixed + +- Fix multi/pipeline segfault on Apple silicon [#1917](https://github.com/phpredis/phpredis/issues/1917) + [e0796d48](https://github.com/phpredis/phpredis/commit/e0796d48af18adac2b93982474e7df8de79ec854) + ([Michael Grunder](https://github.com/michael-grunder)) +- Pass compression flag on HMGET in RedisCluster [#1945](https://github.com/phpredis/phpredis/issues/1945) + [edc724e6](https://github.com/phpredis/phpredis/commit/edc724e6022620414abf4f90256522d03c3160fd) + ([Adam Olley](https://github.com/aolley)) +- Abide by ZSTD error return constants [#1936](https://github.com/phpredis/phpredis/issues/1936) + [8400ed1c](https://github.com/phpredis/phpredis/pull/1937/commits/8400ed1cb23a22f70727cb60e88ca5397ee10d23) + ([Michael Grunder](https://github.com/michael-grunder)) +- Fix timing related CI session tests + [9b986bf8](https://github.com/phpredis/phpredis/commit/9b986bf81859f5a5983cd148cb15ee6ce292d288) + ([Michael Grunder](https://github.com/michael-grunder)) + +## [5.3.3] - 2021-02-01 ([GitHub](https://github.com/phpredis/phpredis/releases/5.3.3), [PECL](https:/pecl.php.net/package/redis/5.3.3)) + +### Sponsors :sparkling_heart: + +- [Audiomack](https://audiomack.com) +- [BlueHost](https://bluehost.com) +- [Redis Cache Pro for WordPress](https://wprediscache.com) +- [Avtandil Kikabidze](https://github.com/akalongman) +- [Oleg Babushkin](https://github.com/olbabushkin) +- [Zaher Ghaibeh](https://github.com/zaherg) +- [BatchLabs](https://batch.com) + +### Fixed + +- Fixed Windows includes for PHP 8 + [270b4db8](https://www.github.com/phpredis//phpredis/commit/270b4db821fcbe9fb881eef83e046f87587c4110) + ([Jan-E](https://github.com/Jan-E)) +- Fix hash_ops for PHP 8.0.1 + [87297cbb](https://www.github.com/phpredis/phpredis/commit/87297cbb4000c88b07e729b9379de321ead74aa2) + ([defender-11](https://github.com/defender-11)) +- Disable clone for Redis and RedisCluster objects. Presently they segfault. + [cd05a344](https://www.github.com/phpredis/phpredis/commit/87297cbb4000c88b07e729b9379de321ead74aa2) + ([Michael Grunder](https://github.com/michael-grunder)) + +## [5.3.2] - 2020-10-22 ([GitHub](https://github.com/phpredis/phpredis/releases/5.3.2), [PECL](https://pecl.php.net/package/redis/5.3.2)) ## [5.3.2] - 2020-10-22 ([GitHub](https://github.com/phpredis/phpredis/releases/5.3.2), [PECL](https://pecl.php.net/package/redis/5.3.2)) diff --git a/README.markdown b/README.markdown index 4840f4cd78..f40e463ee2 100644 --- a/README.markdown +++ b/README.markdown @@ -21,7 +21,7 @@ You can also make a one-time contribution with one of the links below. [![Ethereum](https://en.cryptobadges.io/badge/micro/0x43D54E32357B96f68dFF0a6B46976d014Bd603E1)](https://en.cryptobadges.io/donate/0x43D54E32357B96f68dFF0a6B46976d014Bd603E1) ## Sponsors -Audiomack.comBluehost.com +Audiomack.comBluehost.comOpenLMS.net # Table of contents ----- diff --git a/common.h b/common.h index 2ad7c70865..42546a7abc 100644 --- a/common.h +++ b/common.h @@ -73,16 +73,19 @@ typedef enum _PUBSUB_TYPE { } PUBSUB_TYPE; /* options */ -#define REDIS_OPT_SERIALIZER 1 -#define REDIS_OPT_PREFIX 2 -#define REDIS_OPT_READ_TIMEOUT 3 -#define REDIS_OPT_SCAN 4 -#define REDIS_OPT_FAILOVER 5 -#define REDIS_OPT_TCP_KEEPALIVE 6 -#define REDIS_OPT_COMPRESSION 7 -#define REDIS_OPT_REPLY_LITERAL 8 -#define REDIS_OPT_COMPRESSION_LEVEL 9 -#define REDIS_OPT_NULL_MBULK_AS_NULL 10 +#define REDIS_OPT_SERIALIZER 1 +#define REDIS_OPT_PREFIX 2 +#define REDIS_OPT_READ_TIMEOUT 3 +#define REDIS_OPT_SCAN 4 +#define REDIS_OPT_FAILOVER 5 +#define REDIS_OPT_TCP_KEEPALIVE 6 +#define REDIS_OPT_COMPRESSION 7 +#define REDIS_OPT_REPLY_LITERAL 8 +#define REDIS_OPT_COMPRESSION_LEVEL 9 +#define REDIS_OPT_NULL_MBULK_AS_NULL 10 +#define REDIS_OPT_IGBINARY_NO_STRINGS 11 +#define REDIS_OPT_COMPRESSION_MIN_SIZE 12 +#define REDIS_OPT_COMPRESSION_MIN_RATIO 13 /* cluster options */ #define REDIS_FAILOVER_NONE 0 @@ -273,8 +276,11 @@ typedef struct { zend_string *persistent_id; redis_serializer serializer; + int no_strings; int compression; int compression_level; + int compression_min_size; + double compression_min_ratio; long dbNumber; zend_string *prefix; diff --git a/library.c b/library.c index 87b6bfa83e..2c6df88396 100644 --- a/library.c +++ b/library.c @@ -2162,6 +2162,10 @@ redis_sock_create(char *host, int host_len, int port, redis_sock->serializer = REDIS_SERIALIZER_NONE; redis_sock->compression = REDIS_COMPRESSION_NONE; + redis_sock->compression_level = 0; /* default */ + redis_sock->no_strings = 0; /* default */ + redis_sock->compression_min_size = -1; /* default */ + redis_sock->compression_min_ratio = 0; /* default */ redis_sock->mode = ATOMIC; return redis_sock; @@ -2819,6 +2823,9 @@ static uint8_t crc8(unsigned char *input, size_t len) { } #endif +#define PHP_REDIS_COMPRESSION_RATIO_CHECK(__x) \ + (redis_sock->compression_min_ratio > 0 && (((double) __x / (double) len) >= redis_sock->compression_min_ratio)) + PHP_REDIS_API int redis_pack(RedisSock *redis_sock, zval *z, char **val, size_t *val_len) { @@ -2827,7 +2834,7 @@ redis_pack(RedisSock *redis_sock, zval *z, char **val, size_t *val_len) size_t len; valfree = redis_serialize(redis_sock, z, &buf, &len); - if (redis_sock->compression == REDIS_COMPRESSION_NONE) { + if (redis_sock->compression == REDIS_COMPRESSION_NONE || (redis_sock->compression_min_size > 0 && redis_sock->compression_min_size >= len)) { *val = buf; *val_len = len; return valfree; @@ -2845,6 +2852,10 @@ redis_pack(RedisSock *redis_sock, zval *z, char **val, size_t *val_len) size = len + MIN(UINT_MAX - len, MAX(LZF_MARGIN, len / 25)); data = emalloc(size); if ((res = lzf_compress(buf, len, data, size)) > 0) { + if (PHP_REDIS_COMPRESSION_RATIO_CHECK(res)) { + efree(data); + break; + } if (valfree) efree(buf); *val = data; *val_len = res; @@ -2876,7 +2887,7 @@ redis_pack(RedisSock *redis_sock, zval *z, char **val, size_t *val_len) size = ZSTD_compressBound(len); data = emalloc(size); size = ZSTD_compress(data, size, buf, len, level); - if (!ZSTD_isError(size)) { + if (!ZSTD_isError(size) && !PHP_REDIS_COMPRESSION_RATIO_CHECK(size)) { if (valfree) efree(buf); data = erealloc(data, size); *val = data; @@ -2906,9 +2917,12 @@ redis_pack(RedisSock *redis_sock, zval *z, char **val, size_t *val_len) char *lz4buf, *lz4pos; lz4bound = LZ4_compressBound(len); - lz4buf = emalloc(REDIS_LZ4_HDR_SIZE + lz4bound); + lz4buf = emalloc(REDIS_LZ4_HDR_SIZE + lz4bound + 1); lz4pos = lz4buf; + /* Added LZ4 header */ + *lz4pos++ = '\4'; + /* Copy and move past crc8 length checksum */ memcpy(lz4pos, &crc, sizeof(crc)); lz4pos += sizeof(crc); @@ -2923,14 +2937,14 @@ redis_pack(RedisSock *redis_sock, zval *z, char **val, size_t *val_len) lz4len = LZ4_compress_HC(buf, lz4pos, old_len, lz4bound, redis_sock->compression_level); } - if (lz4len <= 0) { + if (lz4len <= 0 || PHP_REDIS_COMPRESSION_RATIO_CHECK(lz4len)) { efree(lz4buf); break; } if (valfree) efree(buf); *val = lz4buf; - *val_len = lz4len + REDIS_LZ4_HDR_SIZE; + *val_len = lz4len + REDIS_LZ4_HDR_SIZE + 1; return 1; } #endif @@ -2980,11 +2994,16 @@ redis_unpack(RedisSock *redis_sock, const char *val, int val_len, zval *z_ret) unsigned long long len; len = ZSTD_getFrameContentSize(val, val_len); + if ( + (redis_sock->compression_min_ratio > 0 || redis_sock->compression_min_size > 0) + && (len == ZSTD_CONTENTSIZE_UNKNOWN || len == ZSTD_CONTENTSIZE_ERROR || len <= 0) + ) { + return redis_unserialize(redis_sock, val, val_len, z_ret); + } if (len != ZSTD_CONTENTSIZE_ERROR && len != ZSTD_CONTENTSIZE_UNKNOWN && len <= INT_MAX) { size_t zlen; - data = emalloc(len); zlen = ZSTD_decompress(data, len, val, val_len); if (ZSTD_isError(zlen) || zlen != len) { @@ -3008,12 +3027,18 @@ redis_unpack(RedisSock *redis_sock, const char *val, int val_len, zval *z_ret) /* We must have at least enough bytes for our header, and can't have more than * INT_MAX + our header size. */ - if (val_len < REDIS_LZ4_HDR_SIZE || val_len > INT_MAX + REDIS_LZ4_HDR_SIZE) + if (val_len < REDIS_LZ4_HDR_SIZE || val_len > INT_MAX + REDIS_LZ4_HDR_SIZE) { break; + } + + /* check bit to ensure payload is LZ4 */ + if (*val != '\4') { + break; + } /* Operate on copies in case our CRC fails */ - const char *copy = val; - size_t copylen = val_len; + const char *copy = (val + 1); + size_t copylen = val_len - 1; /* Read in our header bytes */ memcpy(&lz4crc, copy, sizeof(uint8_t)); @@ -3022,8 +3047,9 @@ redis_unpack(RedisSock *redis_sock, const char *val, int val_len, zval *z_ret) copy += sizeof(int); copylen -= sizeof(int); /* Make sure our CRC matches (TODO: Maybe issue a docref error?) */ - if (crc8((unsigned char*)&datalen, sizeof(datalen)) != lz4crc) + if (crc8((unsigned char*)&datalen, sizeof(datalen)) != lz4crc) { break; + } /* Finally attempt decompression */ data = emalloc(datalen); @@ -3107,6 +3133,11 @@ redis_serialize(RedisSock *redis_sock, zval *z, char **val, size_t *val_len break; case REDIS_SERIALIZER_IGBINARY: #ifdef HAVE_REDIS_IGBINARY + if (Z_TYPE_P(z) == IS_STRING && redis_sock->no_strings) { + *val = Z_STRVAL_P(z); + *val_len = Z_STRLEN_P(z); + return 0; + } if(igbinary_serialize(&val8, (size_t *)&sz, z) == 0) { *val = (char*)val8; *val_len = sz; @@ -3178,6 +3209,10 @@ redis_unserialize(RedisSock* redis_sock, const char *val, int val_len, || (memcmp(val, "\x00\x00\x00\x01", 4) != 0 && memcmp(val, "\x00\x00\x00\x02", 4) != 0)) { + if (*val != '\0' && redis_sock->no_strings) { + ZVAL_STRINGL(z_ret, val, val_len); + return 1; + } /* This is most definitely not an igbinary string, so do not try to unserialize this as one. */ break; diff --git a/package.xml b/package.xml index 21339b3928..e1c9de5cfc 100644 --- a/package.xml +++ b/package.xml @@ -27,10 +27,10 @@ http://pear.php.net/dtd/package-2.0.xsd"> n.favrefelix@gmail.com no - 2020-10-22 + 2021-03-24 - 5.3.2 - 5.3.2 + 5.3.4 + 5.3.4 stable @@ -38,46 +38,26 @@ http://pear.php.net/dtd/package-2.0.xsd"> PHP - This release containse some bugfixes and small improvements. + phpredis 5.3.4 + + This release fixes a multi/pipeline segfault on apple silicon as well as + two small compression related bugs. + You can find a detailed list of changes in Changelog.md and package.xml + * Fix multi/pipeline segfault on Apple silicon [e0796d48] (Michael Grunder) + * Pass compression flag on HMGET in RedisCluster [edc724e6] (Adam Olley) + * Abide by ZSTD error return constants [8400ed1c] (Michael Grunder) + * Fix timing related CI session tests [9b986bf8] (Michael Grunder) + * Sponsors ~ Audiomack - https://audiomack.com + ~ Open LMS - https://openlms.net ~ BlueHost - https://bluehost.com - ~ Redis Cache Pro for WordPress - https://wprediscache.com + ~ Object Cache Pro for WordPress - https://objectcache.pro ~ Avtandil Kikabidze - https://github.com/akalongman - ~ Oleg Babushkin - https://github.com/olbabushkin - - phpredis 5.3.2 - - * Use "%.17g" sprintf format for doubles as done in Redis server. [32be3006] (Pavlo Yatsukhnenko) - * Allow to pass NULL as RedisCluster stream context options. [72024afe] (Pavlo Yatsukhnenko) - - --- - - phpredis 5.3.2RC2 - - --- - - * Verify SET options are strings before testing them as strings [514bc371] (Michael Grunder) - - --- - - phpredis 5.3.2RC1 - - --- - * Fix cluster segfault when dealing with NULL multi bulk replies in RedisCluster [950e8de8] (Michael Grunder, Alex Offshore) - * Fix xReadGroup() must return message id [500916a4] (Pavlo Yatsukhnenko) - * Fix memory leak in rediscluster session handler [b2cffffc] (Pavlo Yatsukhnenko) - * Fix XInfo() returns false if the stream is empty [5719c9f7, 566fdeeb] (Pavlo Yatsukhnenko, Michael Grunder) - * Relax requirements on set's expire argument [36458071] (Michael Grunder) - * Refactor redis_sock_check_liveness [c5950644] (Pavlo Yatsukhnenko) - * PHP8 compatibility [a7662da7, f4a30cb2, 17848791] (Pavlo Yatsukhnenko, Remi Collet) - * Update documentation [c9ed151d, 398c99d9] (Ali Alwash, Gregoire Pineau) - * Add Redis::OPT_NULL_MULTIBULK_AS_NULL setting to treat NULL multi bulk replies as NULL instead of []. [950e8de8] (Michael Grunder, Alex Offshore) - * Allow to specify stream context for rediscluster session handler [a8daaff8, 4fbe7df7] (Pavlo Yatsukhnenko) - * Add new parameter to RedisCluster to specify stream ssl/tls context. [f771ea16] (Pavlo Yatsukhnenko) - * Add new parameter to RedisSentinel to specify auth information [81c502ae] (Pavlo Yatsukhnenko) + ~ Zaher Ghaibeh - https://github.com/zaherg + ~ BatchLabs - https://batch.com @@ -152,6 +132,33 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + stablestable + 5.3.35.3.3 + 2021-03-23 + + phpredis 5.3.3 + + This release mostly includes just small PHP 8 Windows compatibility fixes + such that pecl.php.net can automatically build Windows DLLs. + + You can find a detailed list of changes in Changelog.md and package.xml + + * Fix PHP8 Windows includes [270b4db8] (Jan-E) + * Fix hash ops for php 8.0.1 [87297cbb] (defender-11) + * Disable cloning Redis and RedisCluster objects [cd05a344] + (Michael Grunder) + + * Sponsors + ~ Audiomack - https://audiomack.com + ~ BlueHost - https://bluehost.com + ~ Redis Cache Pro for WordPress - https://wprediscache.com + ~ Avtandil Kikabidze - https://github.com/akalongman + ~ Zaher Ghaibeh - https://github.com/zaherg + ~ BatchLabs - https://batch.com + + + stablestable 5.3.25.3.2 diff --git a/php_redis.h b/php_redis.h index 6b7b3be4ba..7fa7b0489e 100644 --- a/php_redis.h +++ b/php_redis.h @@ -23,7 +23,7 @@ #define PHP_REDIS_H /* phpredis version */ -#define PHP_REDIS_VERSION "5.3.2" +#define PHP_REDIS_VERSION "5.3.4" PHP_METHOD(Redis, __construct); PHP_METHOD(Redis, __destruct); diff --git a/redis.c b/redis.c index 230859dbc4..01b3113c61 100644 --- a/redis.c +++ b/redis.c @@ -604,6 +604,51 @@ free_redis_object(zend_object *object) } } +static zend_object * +clone_redis_object(zval *this_ptr) +{ + zend_object *old_object = Z_OBJ_P(this_ptr); + redis_object *old_redis = PHPREDIS_GET_OBJECT(redis_object, old_object); + redis_object *redis = ecalloc(1, sizeof(redis_object) + zend_object_properties_size(old_object->ce)); + + if (old_redis->sock) { + redis->sock = redis_sock_create(ZSTR_VAL(old_redis->sock->host), ZSTR_LEN(old_redis->sock->host), + old_redis->sock->port, + old_redis->sock->timeout, + old_redis->sock->read_timeout, + old_redis->sock->persistent, + ZSTR_VAL(old_redis->sock->persistent_id), + old_redis->sock->retry_interval); + + if (old_redis->sock->stream_ctx) { + redis_sock_set_stream_context(redis->sock, &old_redis->sock->stream_ctx->options); + } + + if (old_redis->sock->user) { + redis_sock_set_auth(redis->sock, old_redis->sock->user, old_redis->sock->pass); + } + + if (redis_sock_server_open(redis->sock) < 0) { + if (redis->sock->err) { + REDIS_THROW_EXCEPTION(ZSTR_VAL(redis->sock->err), 0); + } + redis_free_socket(redis->sock); + redis->sock = NULL; + } + } + + zend_object_std_init(&redis->std, old_object->ce); + object_properties_init(&redis->std, old_object->ce); + + memcpy(&redis_object_handlers, zend_get_std_object_handlers(), sizeof(redis_object_handlers)); + redis_object_handlers.offset = XtOffsetOf(redis_object, std); + redis_object_handlers.free_obj = free_redis_object; + redis_object_handlers.clone_obj = clone_redis_object; + redis->std.handlers = &redis_object_handlers; + + return &redis->std; +} + zend_object * create_redis_object(zend_class_entry *ce) { @@ -617,6 +662,7 @@ create_redis_object(zend_class_entry *ce) memcpy(&redis_object_handlers, zend_get_std_object_handlers(), sizeof(redis_object_handlers)); redis_object_handlers.offset = XtOffsetOf(redis_object, std); redis_object_handlers.free_obj = free_redis_object; + redis_object_handlers.clone_obj = clone_redis_object; redis->std.handlers = &redis_object_handlers; return &redis->std; @@ -715,12 +761,15 @@ static void add_class_constants(zend_class_entry *ce, int is_cluster) { zend_declare_class_constant_long(ce, ZEND_STRL("OPT_REPLY_LITERAL"), REDIS_OPT_REPLY_LITERAL); zend_declare_class_constant_long(ce, ZEND_STRL("OPT_COMPRESSION_LEVEL"), REDIS_OPT_COMPRESSION_LEVEL); zend_declare_class_constant_long(ce, ZEND_STRL("OPT_NULL_MULTIBULK_AS_NULL"), REDIS_OPT_NULL_MBULK_AS_NULL); + zend_declare_class_constant_long(ce, ZEND_STRL("OPT_COMPRESSION_MIN_SIZE"), REDIS_OPT_COMPRESSION_MIN_SIZE); + zend_declare_class_constant_long(ce, ZEND_STRL("OPT_COMPRESSION_MIN_RATIO"), REDIS_OPT_COMPRESSION_MIN_RATIO); /* serializer */ zend_declare_class_constant_long(ce, ZEND_STRL("SERIALIZER_NONE"), REDIS_SERIALIZER_NONE); zend_declare_class_constant_long(ce, ZEND_STRL("SERIALIZER_PHP"), REDIS_SERIALIZER_PHP); #ifdef HAVE_REDIS_IGBINARY zend_declare_class_constant_long(ce, ZEND_STRL("SERIALIZER_IGBINARY"), REDIS_SERIALIZER_IGBINARY); + zend_declare_class_constant_long(ce, ZEND_STRL("OPT_IGBINARY_NO_STRINGS"), REDIS_OPT_IGBINARY_NO_STRINGS); #endif #ifdef HAVE_REDIS_MSGPACK zend_declare_class_constant_long(ce, ZEND_STRL("SERIALIZER_MSGPACK"), REDIS_SERIALIZER_MSGPACK); diff --git a/redis_array.c b/redis_array.c index 9d6883c305..2cf10ee0bf 100644 --- a/redis_array.c +++ b/redis_array.c @@ -196,6 +196,7 @@ create_redis_array_object(zend_class_entry *ce) memcpy(&redis_array_object_handlers, zend_get_std_object_handlers(), sizeof(redis_array_object_handlers)); redis_array_object_handlers.offset = XtOffsetOf(redis_array_object, std); redis_array_object_handlers.free_obj = free_redis_array_object; + redis_array_object_handlers.clone_obj = NULL; obj->std.handlers = &redis_array_object_handlers; return &obj->std; diff --git a/redis_cluster.c b/redis_cluster.c index 402c23b7cc..90e8dd9b2d 100644 --- a/redis_cluster.c +++ b/redis_cluster.c @@ -333,6 +333,7 @@ zend_object * create_cluster_context(zend_class_entry *class_type) { memcpy(&RedisCluster_handlers, zend_get_std_object_handlers(), sizeof(RedisCluster_handlers)); RedisCluster_handlers.offset = XtOffsetOf(redisCluster, std); RedisCluster_handlers.free_obj = free_cluster_context; + RedisCluster_handlers.clone_obj = NULL; cluster->std.handlers = &RedisCluster_handlers; diff --git a/redis_commands.c b/redis_commands.c index 16bc8e8c7d..06bd6cc728 100644 --- a/redis_commands.c +++ b/redis_commands.c @@ -4283,10 +4283,18 @@ void redis_getoption_handler(INTERNAL_FUNCTION_PARAMETERS, switch(option) { case REDIS_OPT_SERIALIZER: RETURN_LONG(redis_sock->serializer); +#ifdef HAVE_REDIS_IGBINARY + case REDIS_OPT_IGBINARY_NO_STRINGS: + RETURN_LONG(redis_sock->no_strings); +#endif case REDIS_OPT_COMPRESSION: RETURN_LONG(redis_sock->compression); case REDIS_OPT_COMPRESSION_LEVEL: RETURN_LONG(redis_sock->compression_level); + case REDIS_OPT_COMPRESSION_MIN_SIZE: + RETURN_LONG(redis_sock->compression_min_size) + case REDIS_OPT_COMPRESSION_MIN_RATIO: + RETURN_DOUBLE(redis_sock->compression_min_ratio) case REDIS_OPT_PREFIX: if (redis_sock->prefix) { RETURN_STRINGL(ZSTR_VAL(redis_sock->prefix), ZSTR_LEN(redis_sock->prefix)); @@ -4346,6 +4354,12 @@ void redis_setoption_handler(INTERNAL_FUNCTION_PARAMETERS, val_long = zval_get_long(val); redis_sock->reply_literal = val_long != 0; RETURN_TRUE; +#ifdef HAVE_REDIS_IGBINARY + case REDIS_OPT_IGBINARY_NO_STRINGS: + val_long = zval_get_long(val); + redis_sock->no_strings = val_long != 0; + RETURN_TRUE; +#endif case REDIS_OPT_NULL_MBULK_AS_NULL: val_long = zval_get_long(val); redis_sock->null_mbulk_as_null = val_long != 0; @@ -4367,6 +4381,13 @@ void redis_setoption_handler(INTERNAL_FUNCTION_PARAMETERS, RETURN_TRUE; } break; + case REDIS_OPT_COMPRESSION_MIN_SIZE: + val_long = zval_get_long(val); + redis_sock->compression_min_size = val_long; + RETURN_TRUE; + case REDIS_OPT_COMPRESSION_MIN_RATIO: + redis_sock->compression_min_ratio = zval_get_double(val); + RETURN_TRUE; case REDIS_OPT_COMPRESSION_LEVEL: val_long = zval_get_long(val); redis_sock->compression_level = val_long; diff --git a/sentinel_library.c b/sentinel_library.c index 0fe64cc145..475ebb72a9 100644 --- a/sentinel_library.c +++ b/sentinel_library.c @@ -25,6 +25,7 @@ create_sentinel_object(zend_class_entry *ce) memcpy(&redis_sentinel_object_handlers, zend_get_std_object_handlers(), sizeof(redis_sentinel_object_handlers)); redis_sentinel_object_handlers.offset = XtOffsetOf(redis_sentinel_object, std); redis_sentinel_object_handlers.free_obj = free_redis_sentinel_object; + redis_sentinel_object_handlers.clone_obj = NULL; obj->std.handlers = &redis_sentinel_object_handlers; return &obj->std; diff --git a/tests/RedisSentinelTest.php b/tests/RedisSentinelTest.php index 4d941dd95a..bdc9aeaaf1 100644 --- a/tests/RedisSentinelTest.php +++ b/tests/RedisSentinelTest.php @@ -40,7 +40,7 @@ public function setUp() public function testCkquorum() { - $this->assertTrue($this->sentinel->ckquorum(self::NAME)); + $this->assertTrue(is_bool($this->sentinel->ckquorum(self::NAME))); } public function testFailover() diff --git a/tests/RedisTest.php b/tests/RedisTest.php index ed8b53f253..51114191de 100644 --- a/tests/RedisTest.php +++ b/tests/RedisTest.php @@ -138,6 +138,22 @@ public function testMinimumVersion() $this->assertTrue(version_compare($this->version, "2.4.0") >= 0); } + public function testClone() + { + if (get_class($this->redis) !== 'Redis') { + $this->markTestSkipped(); + return; + } + + $new = clone $this->redis; + /* check that connection works */ + $this->assertTrue($new->ping()); + /* confirm socket settings are disconnected from source object */ + $this->assertTrue($new->setOption(Redis::OPT_PREFIX, 'test')); + $this->assertEquals($new->getOption(Redis::OPT_PREFIX), 'test'); + $this->assertFalse($this->redis->getOption(Redis::OPT_PREFIX)); + } + public function testPing() { /* Reply literal off */ $this->assertTrue($this->redis->ping()); @@ -4702,6 +4718,18 @@ private function checkSerializer($mode) { $this->assertTrue($this->redis->getOption(Redis::OPT_SERIALIZER) === Redis::SERIALIZER_NONE); // get ok } + public function testIgbinaryNoStrings() + { + if (!defined('Redis::OPT_IGBINARY_NO_STRINGS')) { + $this->markTestSkipped(); + } + $this->assertTrue($this->redis->setOption(Redis::OPT_IGBINARY_NO_STRINGS, true)); + $this->assertTrue($this->redis->getOption(Redis::OPT_IGBINARY_NO_STRINGS)); + + $this->assertTrue($this->redis->set("no_binary", "test string")); + $this->assertEquals($this->redis->get('no_binary'), "test string"); + } + public function testCompressionLZF() { if (!defined('Redis::COMPRESSION_LZF')) { @@ -4710,6 +4738,56 @@ public function testCompressionLZF() $this->checkCompression(Redis::COMPRESSION_LZF, 0); } + public function testCompressionLZFLimited() + { + if (!defined('Redis::COMPRESSION_LZF')) { + $this->markTestSkipped(); + } + + $checks = [ + "test123" => strlen("test123"), + str_repeat('a', 101) => 9, + random_bytes(110) => 110, + ]; + + $this->limitedCompressionCheck($checks, Redis::COMPRESSION_LZF); + } + + public function testCompressionZSTDLimited() + { + if (!defined('Redis::COMPRESSION_ZSTD')) { + $this->markTestSkipped(); + } + + $checks = [ + "test123" => strlen("test123"), + str_repeat('a', 101) => 17, + random_bytes(110) => 110, + ]; + + $this->limitedCompressionCheck($checks, Redis::COMPRESSION_ZSTD); + } + + private function limitedCompressionCheck($checks, $mode) + { + $settings = [ + Redis::OPT_COMPRESSION => $mode, + Redis::OPT_COMPRESSION_MIN_SIZE => 100, + Redis::OPT_COMPRESSION_MIN_RATIO => 0.3, + Redis::OPT_COMPRESSION_LEVEL => 0 + ]; + foreach ($settings as $k => $v) { + $this->assertTrue($this->redis->setOption($k, $v)); + $this->assertEquals($this->redis->getOption($k), $v); + } + + foreach ($checks as $v => $len) { + $this->assertTrue($this->redis->set('foo', $v)); + $this->assertEquals($this->redis->get('foo'), $v); + $this->assertEquals($this->redis->strlen('foo'), $len); + } + } + public function testCompressionZSTD() { if (!defined('Redis::COMPRESSION_ZSTD')) { @@ -4737,6 +4815,21 @@ public function testCompressionLZ4() $this->checkCompression(Redis::COMPRESSION_LZ4, 9); } + public function testCompressionLZ4Limited() + { + if (!defined('Redis::COMPRESSION_LZ4')) { + $this->markTestSkipped(); + } + + $checks = [ + "test123" => strlen("test123"), + str_repeat('a', 101) => 17, + random_bytes(110) => 110, + ]; + + $this->limitedCompressionCheck($checks, Redis::COMPRESSION_ZSTD); + } + private function checkCompression($mode, $level) { $this->assertTrue($this->redis->setOption(Redis::OPT_COMPRESSION, $mode) === TRUE); // set ok @@ -6304,6 +6397,8 @@ public function testInvalidAuthArgs() { try { if (is_array($arr_arg)) { @call_user_func_array([$obj_new, 'auth'], $arr_arg); + } else { + @call_user_func([$obj_new, 'auth']); } } catch (Exception $ex) { unset($ex); /* Suppress intellisense warning */ @@ -6466,8 +6561,9 @@ public function testSession_lockKeyCorrect() ini_get('redis.session.lock_retries') / 1000000.00); - $exist = $this->waitForSessionLockKey($sessionId, $maxwait); + $exist = $this->waitForSessionLockKey($sessionId, $maxwait + 1); $this->assertTrue($exist); + $this->redis->del($this->sessionPrefix . $sessionId . '_LOCK'); } public function testSession_lockingDisabledByDefault() @@ -6492,8 +6588,7 @@ public function testSession_lockReleasedOnClose() $this->setSessionHandler(); $sessionId = $this->generateSessionId(); $this->startSessionProcess($sessionId, 1, true); - $sleep = ini_get('redis.session.lock_wait_time') * ini_get('redis.session.lock_retries'); - usleep($sleep + 10000); + $this->waitForProcess('startSession.php', 5); $this->assertFalse($this->redis->exists($this->sessionPrefix . $sessionId . '_LOCK')); } @@ -6921,6 +7016,29 @@ private function waitForSessionLockKey($session_id, $max_wait_sec) { return $exists || $this->redis->exists($key); } + /** + * @param string $str_search pattern to look for in ps + * @param int $timeout Maximum amount of time to wait + * + * Small helper function to wait until we no longer detect a running process. + * This is an attempt to fix timing related false failures on session tests + * when running in CI. + */ + function waitForProcess($str_search, $timeout = 0.0) { + $st = microtime(true); + + do { + $str_procs = shell_exec("ps aux|grep $str_search|grep -v grep"); + $arr_procs = array_filter(explode("\n", $str_procs)); + if (count($arr_procs) == 0) + return true; + + usleep(10000); + $elapsed = microtime(true) - $st; + } while ($timeout < 0 || $elapsed < $timeout); + + return false; + } /** * @param string $sessionId diff --git a/tests/check-quorum.sh b/tests/check-quorum.sh new file mode 100755 index 0000000000..3980b1f8b5 --- /dev/null +++ b/tests/check-quorum.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +for try in $(seq 0 3); do + RES=$(redis-cli -p 26379 sentinel ckquorum mymaster 2>/dev/null); + if [[ "$RES" == OK* ]]; then + echo "Quorum detected, exiting"; + break; + else + echo "No Quorum - $RES"; + fi; + sleep 1; +done