diff options
-rw-r--r-- | PHPINFO | 2 | ||||
-rw-r--r-- | REFLECTION | 8 | ||||
-rw-r--r-- | php-pecl-redis5.spec | 16 | ||||
-rw-r--r-- | redis-php8.patch | 658 |
4 files changed, 13 insertions, 671 deletions
@@ -2,7 +2,7 @@ redis Redis Support => enabled -Redis Version => 5.3.1 +Redis Version => 5.3.2RC1 Redis Sentinel Version => 0.1 Available serializers => php, json, igbinary, msgpack Available compression => lzf, zstd, lz4 @@ -1,4 +1,4 @@ -Extension [ <persistent> extension #114 redis version 5.3.1 ] { +Extension [ <persistent> extension #115 redis version 5.3.2RC1 ] { - Dependencies { Dependency [ igbinary (Required) ] @@ -100,7 +100,7 @@ Extension [ <persistent> extension #114 redis version 5.3.1 ] { - Classes [6] { Class [ <internal:redis> class Redis ] { - - Constants [36] { + - Constants [37] { Constant [ public int REDIS_NOT_FOUND ] { 0 } Constant [ public int REDIS_STRING ] { 1 } Constant [ public int REDIS_SET ] { 2 } @@ -118,6 +118,7 @@ Extension [ <persistent> extension #114 redis version 5.3.1 ] { Constant [ public int OPT_COMPRESSION ] { 7 } Constant [ public int OPT_REPLY_LITERAL ] { 8 } Constant [ public int OPT_COMPRESSION_LEVEL ] { 9 } + Constant [ public int OPT_NULL_MULTIBULK_AS_NULL ] { 10 } Constant [ public int SERIALIZER_NONE ] { 0 } Constant [ public int SERIALIZER_PHP ] { 1 } Constant [ public int SERIALIZER_IGBINARY ] { 2 } @@ -2302,7 +2303,7 @@ Extension [ <persistent> extension #114 redis version 5.3.1 ] { Class [ <internal:redis> class RedisCluster ] { - - Constants [40] { + - Constants [41] { Constant [ public int REDIS_NOT_FOUND ] { 0 } Constant [ public int REDIS_STRING ] { 1 } Constant [ public int REDIS_SET ] { 2 } @@ -2319,6 +2320,7 @@ Extension [ <persistent> extension #114 redis version 5.3.1 ] { Constant [ public int OPT_COMPRESSION ] { 7 } Constant [ public int OPT_REPLY_LITERAL ] { 8 } Constant [ public int OPT_COMPRESSION_LEVEL ] { 9 } + Constant [ public int OPT_NULL_MULTIBULK_AS_NULL ] { 10 } Constant [ public int SERIALIZER_NONE ] { 0 } Constant [ public int SERIALIZER_PHP ] { 1 } Constant [ public int SERIALIZER_IGBINARY ] { 2 } diff --git a/php-pecl-redis5.spec b/php-pecl-redis5.spec index 0e2850f..3cfe52f 100644 --- a/php-pecl-redis5.spec +++ b/php-pecl-redis5.spec @@ -25,19 +25,17 @@ # after 20-json, 40-igbinary and 40-msgpack %global ini_name 50-%{pecl_name}.ini -%global upstream_version 5.3.1 -#global upstream_prever RC2 +%global upstream_version 5.3.2 +%global upstream_prever RC1 Summary: Extension for communicating with the Redis key-value store Name: %{?sub_prefix}php-pecl-redis5 Version: %{upstream_version}%{?upstream_prever:~%{upstream_prever}} -Release: 4%{?dist}%{!?scl:%{!?nophptag:%(%{__php} -r 'echo ".".PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;')}} +Release: 1%{?dist}%{!?scl:%{!?nophptag:%(%{__php} -r 'echo ".".PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;')}} License: PHP URL: https://pecl.php.net/package/redis Source0: https://pecl.php.net/get/%{pecl_name}-%{upstream_version}%{?upstream_prever}.tgz -Patch0: %{pecl_name}-php8.patch - BuildRequires: %{?dtsprefix}gcc BuildRequires: %{?scl_prefix}php-devel > 7 BuildRequires: %{?scl_prefix}php-pear @@ -138,10 +136,6 @@ sed -e 's/role="test"/role="src"/' \ -i package.xml cd NTS -%if "%{php_version}" > "8.0" -%patch0 -p1 -b.php8 -%endif - # Use system library rm -r liblzf @@ -359,6 +353,10 @@ fi %changelog +* Wed Oct 7 2020 Remi Collet <remi@remirepo.net> - 5.3.2~RC1-1 +- update to 5.3.2RC1 +- drop patch merged upstream + * Wed Sep 30 2020 Remi Collet <remi@remirepo.net> - 5.3.1-4 - rebuild for PHP 8.0.0RC1 diff --git a/redis-php8.patch b/redis-php8.patch deleted file mode 100644 index 1227349..0000000 --- a/redis-php8.patch +++ /dev/null @@ -1,658 +0,0 @@ -diff --git a/cluster_library.c b/cluster_library.c -index 98ba9c2..3720f72 100644 ---- a/cluster_library.c -+++ b/cluster_library.c -@@ -634,8 +634,12 @@ cluster_node_create(redisCluster *c, char *host, size_t host_len, - zend_llist_init(&node->slots, sizeof(redisSlotRange), NULL, 0); - - // Attach socket -- node->sock = redis_sock_create(host, host_len, port, c->timeout, -- c->read_timeout, c->persistent, NULL, 0); -+ node->sock = redis_sock_create(host, host_len, port, -+ c->flags->timeout, c->flags->read_timeout, -+ c->flags->persistent, NULL, 0); -+ -+ /* Stream context */ -+ node->sock->stream_ctx = c->flags->stream_ctx; - - redis_sock_set_auth(node->sock, c->flags->user, c->flags->pass); - -@@ -818,12 +822,12 @@ PHP_REDIS_API redisCluster *cluster_create(double timeout, double read_timeout, - - /* Initialize flags and settings */ - c->flags = ecalloc(1, sizeof(RedisSock)); -+ c->flags->timeout = timeout; -+ c->flags->read_timeout = read_timeout; -+ c->flags->persistent = persistent; - c->subscribed_slot = -1; - c->clusterdown = 0; -- c->timeout = timeout; -- c->read_timeout = read_timeout; - c->failover = failover; -- c->persistent = persistent; - c->err = NULL; - - /* Set up our waitms based on timeout */ -@@ -993,9 +997,12 @@ void cluster_init_cache(redisCluster *c, redisCachedCluster *cc) { - - /* Create socket */ - sock = redis_sock_create(ZSTR_VAL(cm->host.addr), ZSTR_LEN(cm->host.addr), cm->host.port, -- c->timeout, c->read_timeout, c->persistent, -+ c->flags->timeout, c->flags->read_timeout, c->flags->persistent, - NULL, 0); - -+ /* Stream context */ -+ sock->stream_ctx = c->flags->stream_ctx; -+ - /* Add to seed nodes */ - zend_hash_str_update_ptr(c->seeds, key, keylen, sock); - -@@ -1027,7 +1034,8 @@ void cluster_init_cache(redisCluster *c, redisCachedCluster *cc) { - * seeds array and know we have a non-empty array of strings all in - * host:port format. */ - PHP_REDIS_API void --cluster_init_seeds(redisCluster *cluster, zend_string **seeds, uint32_t nseeds) { -+cluster_init_seeds(redisCluster *c, zend_string **seeds, uint32_t nseeds) -+{ - RedisSock *sock; - char *seed, *sep, key[1024]; - int key_len, i, *map; -@@ -1044,19 +1052,22 @@ cluster_init_seeds(redisCluster *cluster, zend_string **seeds, uint32_t nseeds) - ZEND_ASSERT(sep != NULL); - - // Allocate a structure for this seed -- sock = redis_sock_create(seed, sep - seed, -- (unsigned short)atoi(sep+1), cluster->timeout, -- cluster->read_timeout, cluster->persistent, NULL, 0); -+ sock = redis_sock_create(seed, sep - seed, atoi(sep + 1), -+ c->flags->timeout, c->flags->read_timeout, -+ c->flags->persistent, NULL, 0); -+ -+ /* Stream context */ -+ sock->stream_ctx = c->flags->stream_ctx; - - /* Credentials */ -- redis_sock_set_auth(sock, cluster->flags->user, cluster->flags->pass); -+ redis_sock_set_auth(sock, c->flags->user, c->flags->pass); - - // Index this seed by host/port - key_len = snprintf(key, sizeof(key), "%s:%u", ZSTR_VAL(sock->host), - sock->port); - - // Add to our seed HashTable -- zend_hash_str_update_ptr(cluster->seeds, key, key_len, sock); -+ zend_hash_str_update_ptr(c->seeds, key, key_len, sock); - } - - efree(map); -@@ -1807,7 +1818,6 @@ PHP_REDIS_API void cluster_sub_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster * - zval z_ret, z_args[4]; - sctx->cb.retval = &z_ret; - sctx->cb.params = z_args; -- sctx->cb.no_separation = 0; - - /* We're in a subscribe loop */ - c->subscribed_slot = c->cmd_slot; -diff --git a/cluster_library.h b/cluster_library.h -index de9d171..98e9b0e 100644 ---- a/cluster_library.h -+++ b/cluster_library.h -@@ -186,12 +186,8 @@ typedef struct clusterFoldItem clusterFoldItem; - /* RedisCluster implementation structure */ - typedef struct redisCluster { - -- /* Timeout and read timeout (for normal operations) */ -- double timeout; -- double read_timeout; -- -- /* Are we using persistent connections */ -- int persistent; -+ /* One RedisSock struct for serialization and prefix information */ -+ RedisSock *flags; - - /* How long in milliseconds should we wait when being bounced around */ - long waitms; -@@ -241,9 +237,6 @@ typedef struct redisCluster { - /* The slot where we're subscribed */ - short subscribed_slot; - -- /* One RedisSock struct for serialization and prefix information */ -- RedisSock *flags; -- - /* The first line of our last reply, not including our reply type byte - * or the trailing \r\n */ - char line_reply[1024]; -diff --git a/library.c b/library.c -index 2f95aea..6aef627 100644 ---- a/library.c -+++ b/library.c -@@ -464,7 +464,6 @@ PHP_REDIS_API int redis_subscribe_response(INTERNAL_FUNCTION_PARAMETERS, - zval z_ret, z_args[4]; - sctx->cb.retval = &z_ret; - sctx->cb.params = z_args; -- sctx->cb.no_separation = 0; - - /* Multibulk response, {[pattern], type, channel, payload } */ - while(1) { -@@ -686,19 +685,6 @@ redis_sock_read(RedisSock *redis_sock, int *buf_len) - return NULL; - } - --static int redis_sock_read_cmp(RedisSock *redis_sock, const char *cmp, int cmplen) { -- char *resp; -- int len, rv = FAILURE; -- -- if ((resp = redis_sock_read(redis_sock, &len)) == NULL) -- return FAILURE; -- -- rv = len == cmplen && !memcmp(resp, cmp, cmplen) ? SUCCESS : FAILURE; -- -- efree(resp); -- return rv; --} -- - /* A simple union to store the various arg types we might handle in our - * redis_spprintf command formatting function */ - union resparg { -@@ -1519,17 +1505,21 @@ redis_read_stream_messages(RedisSock *redis_sock, int count, zval *z_ret - * the multi-bulk header for field and values */ - if ((read_mbulk_header(redis_sock, &mhdr) < 0 || mhdr != 2) || - ((id = redis_sock_read(redis_sock, &idlen)) == NULL) || -- (read_mbulk_header(redis_sock, &fields) < 0 || fields % 2 != 0)) -+ (read_mbulk_header(redis_sock, &fields) < 0 || -+ (fields > 0 && fields % 2 != 0))) - { - if (id) efree(id); - return -1; - } - -- array_init(&z_message); -- -- redis_mbulk_reply_loop(redis_sock, &z_message, fields, UNSERIALIZE_VALS); -- array_zip_values_and_scores(redis_sock, &z_message, SCORE_DECODE_NONE); -- add_assoc_zval_ex(z_ret, id, idlen, &z_message); -+ if (fields < 0) { -+ add_assoc_null_ex(z_ret, id, idlen); -+ } else { -+ array_init(&z_message); -+ redis_mbulk_reply_loop(redis_sock, &z_message, fields, UNSERIALIZE_VALS); -+ array_zip_values_and_scores(redis_sock, &z_message, SCORE_DECODE_NONE); -+ add_assoc_zval_ex(z_ret, id, idlen, &z_message); -+ } - efree(id); - } - -@@ -1727,7 +1717,10 @@ redis_read_xinfo_response(RedisSock *redis_sock, zval *z_ret, int elements) - switch (type) { - case TYPE_BULK: - if ((data = redis_sock_read_bulk_reply(redis_sock, li)) == NULL) { -- goto failure; -+ if (!key) goto failure; -+ add_assoc_null_ex(z_ret, key, len); -+ efree(key); -+ key = NULL; - } else if (key) { - add_assoc_stringl_ex(z_ret, key, len, data, li); - efree(data); -@@ -2130,16 +2123,21 @@ static int redis_stream_liveness_check(php_stream *stream) { - static int - redis_sock_check_liveness(RedisSock *redis_sock) - { -- char id[64], ok; -+ char id[64], inbuf[4096]; - int idlen, auth; - smart_string cmd = {0}; -+ size_t len; - - /* Short circuit if we detect the stream has gone bad or if the user has - * configured persistent connection "YOLO mode". */ -- if (redis_stream_liveness_check(redis_sock->stream) != SUCCESS) -- return FAILURE; -- else if (!INI_INT("redis.pconnect.echo_check_liveness")) -+ if (redis_stream_liveness_check(redis_sock->stream) != SUCCESS) { -+ goto failure; -+ } -+ -+ redis_sock->status = REDIS_SOCK_STATUS_CONNECTED; -+ if (!INI_INT("redis.pconnect.echo_check_liveness")) { - return SUCCESS; -+ } - - /* AUTH (if we need it) */ - auth = redis_sock_append_auth(redis_sock, &cmd); -@@ -2150,12 +2148,55 @@ redis_sock_check_liveness(RedisSock *redis_sock) - redis_cmd_append_sstr(&cmd, id, idlen); - - /* Send command(s) and make sure we can consume reply(ies) */ -- ok = (redis_sock_write(redis_sock, cmd.c, cmd.len) >= 0) && -- (!auth || redis_sock_read_ok(redis_sock) == SUCCESS) && -- (redis_sock_read_cmp(redis_sock, id, idlen) == SUCCESS); -- -+ if (redis_sock_write(redis_sock, cmd.c, cmd.len) < 0) { -+ smart_string_free(&cmd); -+ goto failure; -+ } - smart_string_free(&cmd); -- return ok ? SUCCESS : FAILURE; -+ -+ if (redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len) < 0) { -+ goto failure; -+ } -+ -+ if (auth) { -+ if (strncmp(inbuf, "+OK", 3) == 0 || strncmp(inbuf, "-ERR Client sent AUTH", 21) == 0) { -+ /* successfully authenticated or authentication isn't required */ -+ if (redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len) < 0) { -+ goto failure; -+ } -+ } else if (strncmp(inbuf, "-NOAUTH", 7) == 0) { -+ /* connection is fine but authentication failed, next command must fails too */ -+ if (redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len) < 0 || strncmp(inbuf, "-NOAUTH", 7) != 0) { -+ goto failure; -+ } -+ return SUCCESS; -+ } else { -+ goto failure; -+ } -+ redis_sock->status = REDIS_SOCK_STATUS_READY; -+ } else { -+ if (strncmp(inbuf, "-NOAUTH", 7) == 0) { -+ /* connection is fine but authentication required */ -+ return SUCCESS; -+ } -+ } -+ -+ /* check echo response */ -+ if (*inbuf != TYPE_BULK || atoi(inbuf + 1) != idlen || -+ redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len) < 0 || -+ strncmp(inbuf, id, idlen) != 0 -+ ) { -+ goto failure; -+ } -+ -+ return SUCCESS; -+failure: -+ redis_sock->status = REDIS_SOCK_STATUS_DISCONNECTED; -+ if (redis_sock->stream) { -+ php_stream_pclose(redis_sock->stream); -+ redis_sock->stream = NULL; -+ } -+ return FAILURE; - } - - /** -@@ -2165,9 +2206,9 @@ PHP_REDIS_API int redis_sock_connect(RedisSock *redis_sock) - { - struct timeval tv, read_tv, *tv_ptr = NULL; - zend_string *persistent_id = NULL, *estr = NULL; -- char host[1024], *pos, *address, *schema = NULL; -+ char host[1024], *pos, *address, *scheme = NULL; - const char *fmtstr = "%s://%s:%d"; -- int host_len, usocket = 0, err = 0, tcp_flag = 1; -+ int host_len, usocket = 0, err = 0, tcp_flag = 1, scheme_free = 0; - ConnectionPool *p = NULL; - - if (redis_sock->stream != NULL) { -@@ -2175,9 +2216,12 @@ PHP_REDIS_API int redis_sock_connect(RedisSock *redis_sock) - } - - address = ZSTR_VAL(redis_sock->host); -- if ((pos = strstr(address, "://")) != NULL) { -- schema = estrndup(address, pos - address); -+ if ((pos = strstr(address, "://")) == NULL) { -+ scheme = redis_sock->stream_ctx ? "ssl" : "tcp"; -+ } else { -+ scheme = estrndup(address, pos - address); - address = pos + sizeof("://") - 1; -+ scheme_free = 1; - } - if (address[0] == '/' && redis_sock->port < 1) { - host_len = snprintf(host, sizeof(host), "unix://%s", address); -@@ -2193,9 +2237,9 @@ PHP_REDIS_API int redis_sock_connect(RedisSock *redis_sock) - fmtstr = "%s://[%s]:%d"; - } - #endif -- host_len = snprintf(host, sizeof(host), fmtstr, schema ? schema : "tcp", address, redis_sock->port); -- if (schema) efree(schema); -+ host_len = snprintf(host, sizeof(host), fmtstr, scheme, address, redis_sock->port); - } -+ if (scheme_free) efree(scheme); - - if (redis_sock->persistent) { - if (INI_INT("redis.pconnect.pooling_enabled")) { -@@ -2205,11 +2249,7 @@ PHP_REDIS_API int redis_sock_connect(RedisSock *redis_sock) - zend_llist_remove_tail(&p->list); - - if (redis_sock_check_liveness(redis_sock) == SUCCESS) { -- redis_sock->status = REDIS_SOCK_STATUS_READY; - return SUCCESS; -- } else if (redis_sock->stream) { -- php_stream_pclose(redis_sock->stream); -- redis_sock->stream = NULL; - } - p->nb_active--; - } -diff --git a/redis_array_impl.c b/redis_array_impl.c -index a427f67..8d8cece 100644 ---- a/redis_array_impl.c -+++ b/redis_array_impl.c -@@ -1134,7 +1134,6 @@ zval_rehash_callback(zend_fcall_info *z_cb, zend_fcall_info_cache *z_cb_cache, - z_cb->params = z_args; - z_cb->retval = z_ret; - -- z_cb->no_separation = 0; - z_cb->param_count = 2; - - /* run cb(hostname, count) */ -diff --git a/redis_cluster.c b/redis_cluster.c -index 3233b65..5cb453b 100644 ---- a/redis_cluster.c -+++ b/redis_cluster.c -@@ -351,7 +351,7 @@ void free_cluster_context(zend_object *object) { - /* Attempt to connect to a Redis cluster provided seeds and timeout options */ - static void redis_cluster_init(redisCluster *c, HashTable *ht_seeds, double timeout, - double read_timeout, int persistent, zend_string *user, -- zend_string *pass) -+ zend_string *pass, zval *context) - { - zend_string *hash = NULL, **seeds; - redisCachedCluster *cc; -@@ -369,10 +369,13 @@ static void redis_cluster_init(redisCluster *c, HashTable *ht_seeds, double time - c->flags->user = zend_string_copy(user); - if (pass && ZSTR_LEN(pass)) - c->flags->pass = zend_string_copy(pass); -+ if (context) { -+ redis_sock_set_stream_context(c->flags, context); -+ } - -- c->timeout = timeout; -- c->read_timeout = read_timeout; -- c->persistent = persistent; -+ c->flags->timeout = timeout; -+ c->flags->read_timeout = read_timeout; -+ c->flags->persistent = persistent; - c->waitms = timeout * 1000L; - - /* Attempt to load slots from cache if caching is enabled */ -@@ -450,7 +453,7 @@ void redis_cluster_load(redisCluster *c, char *name, int name_len) { - } - - /* Attempt to create/connect to the cluster */ -- redis_cluster_init(c, ht_seeds, timeout, read_timeout, persistent, user, pass); -+ redis_cluster_init(c, ht_seeds, timeout, read_timeout, persistent, user, pass, NULL); - - /* Clean up */ - zval_dtor(&z_seeds); -@@ -464,38 +467,36 @@ void redis_cluster_load(redisCluster *c, char *name, int name_len) { - - /* Create a RedisCluster Object */ - PHP_METHOD(RedisCluster, __construct) { -- zval *object, *z_seeds = NULL, *z_auth = NULL; -+ zval *object, *z_seeds = NULL, *z_auth = NULL, *context = NULL; - zend_string *user = NULL, *pass = NULL; - double timeout = 0.0, read_timeout = 0.0; - size_t name_len; - zend_bool persistent = 0; -- redisCluster *context = GET_CONTEXT(); -+ redisCluster *c = GET_CONTEXT(); - char *name; - - // Parse arguments - if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), -- "Os!|addbz", &object, redis_cluster_ce, &name, -+ "Os!|addbza", &object, redis_cluster_ce, &name, - &name_len, &z_seeds, &timeout, &read_timeout, -- &persistent, &z_auth) == FAILURE) -+ &persistent, &z_auth, &context) == FAILURE) - { - RETURN_FALSE; - } - -- // Require a name -- if (name_len == 0 && ZEND_NUM_ARGS() < 2) { -- CLUSTER_THROW_EXCEPTION("You must specify a name or pass seeds!", 0); -- } -- - /* If we've got a string try to load from INI */ - if (ZEND_NUM_ARGS() < 2) { -- redis_cluster_load(context, name, name_len); -+ if (name_len == 0) { // Require a name -+ CLUSTER_THROW_EXCEPTION("You must specify a name or pass seeds!", 0); -+ } -+ redis_cluster_load(c, name, name_len); - return; - } - - /* The normal case, loading from arguments */ - redis_extract_auth_info(z_auth, &user, &pass); -- redis_cluster_init(context, Z_ARRVAL_P(z_seeds), timeout, read_timeout, -- persistent, user, pass); -+ redis_cluster_init(c, Z_ARRVAL_P(z_seeds), timeout, read_timeout, -+ persistent, user, pass, context); - - if (user) zend_string_release(user); - if (pass) zend_string_release(pass); -diff --git a/redis_commands.c b/redis_commands.c -index 3b19194..d6bb2bc 100644 ---- a/redis_commands.c -+++ b/redis_commands.c -@@ -793,7 +793,7 @@ int redis_subscribe_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, - zval *z_arr, *z_chan; - HashTable *ht_chan; - smart_string cmdstr = {0}; -- subscribeContext *sctx = emalloc(sizeof(subscribeContext)); -+ subscribeContext *sctx = ecalloc(1, sizeof(*sctx)); - size_t key_len; - int key_free; - char *key; -@@ -854,7 +854,7 @@ int redis_unsubscribe_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, - zval *z_arr, *z_chan; - HashTable *ht_arr; - smart_string cmdstr = {0}; -- subscribeContext *sctx = emalloc(sizeof(subscribeContext)); -+ subscribeContext *sctx = ecalloc(1, sizeof(*sctx)); - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "a", &z_arr) == FAILURE) { - efree(sctx); -@@ -1299,6 +1299,34 @@ int redis_blocking_pop_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, - * have specific processing (argument validation, etc) that make them unique - */ - -+/* Attempt to pull a long expiry from a zval. We're more restrictave than zval_get_long -+ * because that function will return integers from things like open file descriptors -+ * which should simply fail as a TTL */ -+static int redis_try_get_expiry(zval *zv, zend_long *lval) { -+ double dval; -+ -+ /* Success on an actual long or double */ -+ if (Z_TYPE_P(zv) == IS_LONG || Z_TYPE_P(zv) == IS_DOUBLE) { -+ *lval = zval_get_long(zv); -+ return SUCCESS; -+ } -+ -+ /* Automatically fail if we're not a string */ -+ if (Z_TYPE_P(zv) != IS_STRING) -+ return FAILURE; -+ -+ /* Attempt to get a long from the string */ -+ switch (is_numeric_string(Z_STRVAL_P(zv), Z_STRLEN_P(zv), lval, &dval, 0)) { -+ case IS_DOUBLE: -+ *lval = dval; -+ /* fallthrough */ -+ case IS_LONG: -+ return SUCCESS; -+ default: -+ return FAILURE; -+ } -+} -+ - /* SET */ - int redis_set_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, - char **cmd, int *cmd_len, short *slot, void **ctx) -@@ -1306,7 +1334,8 @@ int redis_set_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, - smart_string cmdstr = {0}; - zval *z_value, *z_opts=NULL; - char *key = NULL, *exp_type = NULL, *set_type = NULL; -- long expire = -1, exp_set = 0, keep_ttl = 0; -+ long exp_set = 0, keep_ttl = 0; -+ zend_long expire = -1; - size_t key_len; - - // Make sure the function is being called correctly -@@ -1316,14 +1345,6 @@ int redis_set_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, - return FAILURE; - } - -- /* Our optional argument can either be a long (to support legacy SETEX */ -- /* redirection), or an array with Redis >= 2.6.12 set options */ -- if (z_opts && Z_TYPE_P(z_opts) != IS_LONG && Z_TYPE_P(z_opts) != IS_ARRAY -- && Z_TYPE_P(z_opts) != IS_NULL) -- { -- return FAILURE; -- } -- - // Check for an options array - if (z_opts && Z_TYPE_P(z_opts) == IS_ARRAY) { - HashTable *kt = Z_ARRVAL_P(z_opts); -@@ -1355,8 +1376,12 @@ int redis_set_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, - set_type = Z_STRVAL_P(v); - } - } ZEND_HASH_FOREACH_END(); -- } else if (z_opts && Z_TYPE_P(z_opts) == IS_LONG) { -- expire = Z_LVAL_P(z_opts); -+ } else if (z_opts && Z_TYPE_P(z_opts) != IS_NULL) { -+ if (redis_try_get_expiry(z_opts, &expire) == FAILURE) { -+ php_error_docref(NULL, E_WARNING, "Expire must be a long, double, or a numeric string"); -+ return FAILURE; -+ } -+ - exp_set = 1; - } - -@@ -1386,7 +1411,7 @@ int redis_set_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, - - if (exp_type) { - redis_cmd_append_sstr(&cmdstr, exp_type, strlen(exp_type)); -- redis_cmd_append_sstr_long(&cmdstr, expire); -+ redis_cmd_append_sstr_long(&cmdstr, (long)expire); - } - - if (set_type) -diff --git a/redis_session.c b/redis_session.c -index 6f2c5c5..4165fff 100644 ---- a/redis_session.c -+++ b/redis_session.c -@@ -854,7 +854,7 @@ static char *cluster_session_key(redisCluster *c, const char *key, int keylen, - - PS_OPEN_FUNC(rediscluster) { - redisCluster *c; -- zval z_conf, *zv; -+ zval z_conf, *zv, *context; - HashTable *ht_conf, *ht_seeds; - double timeout = 0, read_timeout = 0; - int persistent = 0, failover = REDIS_FAILOVER_NONE; -@@ -907,6 +907,7 @@ PS_OPEN_FUNC(rediscluster) { - - #define CLUSTER_SESSION_CLEANUP() \ - if (hash) zend_string_release(hash); \ -+ if (failstr) zend_string_release(failstr); \ - if (prefix) zend_string_release(prefix); \ - if (user) zend_string_release(user); \ - if (pass) zend_string_release(pass); \ -@@ -918,7 +919,6 @@ PS_OPEN_FUNC(rediscluster) { - if (seeds == NULL) { - php_error_docref(NULL, E_WARNING, "No valid seeds detected"); - CLUSTER_SESSION_CLEANUP(); -- zval_dtor(&z_conf); - return FAILURE; - } - -@@ -932,6 +932,10 @@ PS_OPEN_FUNC(rediscluster) { - - redis_sock_set_auth(c->flags, user, pass); - -+ if ((context = REDIS_HASH_STR_FIND_TYPE_STATIC(ht_conf, "stream", IS_ARRAY)) != NULL) { -+ redis_sock_set_stream_context(c->flags, context); -+ } -+ - /* First attempt to load from cache */ - if (CLUSTER_CACHING_ENABLED()) { - hash = cluster_hash_seeds(seeds, nseeds); -diff --git a/tests/RedisTest.php b/tests/RedisTest.php -index c7a403f..3c84885 100644 ---- a/tests/RedisTest.php -+++ b/tests/RedisTest.php -@@ -371,9 +371,14 @@ class Redis_Test extends TestSuite - $this->assertEquals($this->redis->get('foo'), 'bar'); - $this->assertEquals($this->redis->ttl('foo'), 20); - -+ /* Should coerce doubles into long */ -+ $this->assertTrue($this->redis->set('foo', 'bar-20.5', 20.5)); -+ $this->assertEquals($this->redis->ttl('foo'), 20); -+ $this->assertEquals($this->redis->get('foo'), 'bar-20.5'); -+ - /* Invalid third arguments */ -- $this->assertFalse($this->redis->set('foo','bar','baz')); -- $this->assertFalse($this->redis->set('foo','bar',new StdClass())); -+ $this->assertFalse(@$this->redis->set('foo','bar','baz')); -+ $this->assertFalse(@$this->redis->set('foo','bar',new StdClass())); - - /* Set if not exist */ - $this->redis->del('foo'); -@@ -6064,6 +6069,21 @@ class Redis_Test extends TestSuite - } - } - -+ /* Regression test for issue-1831 (XINFO STREAM on an empty stream) */ -+ public function testXInfoEmptyStream() { -+ /* Configure an empty stream */ -+ $this->redis->del('s'); -+ $this->redis->xAdd('s', '*', ['foo' => 'bar']); -+ $this->redis->xTrim('s', 0); -+ -+ $arr_info = $this->redis->xInfo('STREAM', 's'); -+ -+ $this->assertTrue(is_array($arr_info)); -+ $this->assertEquals(0, $arr_info['length']); -+ $this->assertEquals(NULL, $arr_info['first-entry']); -+ $this->assertEquals(NULL, $arr_info['last-entry']); -+ } -+ - public function testInvalidAuthArgs() { - $obj_new = $this->newInstance(); - -@@ -6082,9 +6102,15 @@ class Redis_Test extends TestSuite - - foreach ($arr_args as $arr_arg) { - try { -- @call_user_func_array([$obj_new, 'auth'], $arr_arg); -+ 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 */ -+ } catch (ArgumentCountError $ex) { -+ unset($ex); /* Suppress intellisense warning */ - } - } - } -diff --git a/tests/startSession.php b/tests/startSession.php -index c0ae188..f82c17e 100644 ---- a/tests/startSession.php -+++ b/tests/startSession.php -@@ -22,11 +22,11 @@ ini_set('redis.session.lock_retries', $lock_retries); - ini_set('redis.session.lock_expire', $lock_expire); - ini_set('session.gc_maxlifetime', $sessionLifetime); - --if (isset($argv[8])) { -+if (isset($argv[10])) { - ini_set('redis.session.locking_enabled', $argv[10]); - } - --if (isset($argv[9])) { -+if (isset($argv[11])) { - ini_set('redis.session.lock_wait_time', $argv[11]); - } - |