summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRemi Collet <remi@remirepo.net>2020-10-07 14:22:58 +0200
committerRemi Collet <remi@remirepo.net>2020-10-07 14:22:58 +0200
commitc917d5c08f25133bf9ebf112080dc24f1b7602aa (patch)
tree11d4a57688ded889190159a1bb9a36e802f124fc
parenteec33fe39161d8dc489e9c9b2901242bec5fbb44 (diff)
update to 5.3.2RC1
drop patch merged upstream
-rw-r--r--PHPINFO2
-rw-r--r--REFLECTION8
-rw-r--r--php-pecl-redis5.spec16
-rw-r--r--redis-php8.patch658
4 files changed, 13 insertions, 671 deletions
diff --git a/PHPINFO b/PHPINFO
index 5f1b1e2..7698e99 100644
--- a/PHPINFO
+++ b/PHPINFO
@@ -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
diff --git a/REFLECTION b/REFLECTION
index 2e30a18..4d2f21c 100644
--- a/REFLECTION
+++ b/REFLECTION
@@ -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]);
- }
-