Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/phpredis/phpredis.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormichael-grunder <michael.grunder@gmail.com>2019-03-19 21:30:14 +0300
committermichael-grunder <michael.grunder@gmail.com>2019-03-19 21:30:14 +0300
commit2761c607e2a273283e2aca32933f929ab067ef46 (patch)
tree4bc4a26dc7387a4882d824e06471d3b518bc6a12
parent8e2ff3fc8eedc42e58e323059470b6466b642c54 (diff)
parent0f38afa2e2ed56ae3cbdfaf0f6a245cdb9a8eff5 (diff)
Merge remote-tracking branch 'cthulhu/issue.1448-require_php7' into issue.1448-require_php7
-rw-r--r--.travis.yml3
-rw-r--r--cluster_library.c186
-rw-r--r--cluster_library.h59
-rw-r--r--common.h1
-rw-r--r--redis.c14
-rw-r--r--redis_array.c18
-rw-r--r--redis_array.h2
-rw-r--r--redis_array_impl.c46
-rw-r--r--redis_array_impl.h3
-rw-r--r--redis_cluster.c130
10 files changed, 397 insertions, 65 deletions
diff --git a/.travis.yml b/.travis.yml
index 4829561f..5815e0f7 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -26,8 +26,7 @@ addons:
packages: clang
before_install:
- phpize
- - pecl install igbinary
- - ./configure --enable-redis-igbinary --enable-redis-lzf CFLAGS=-Wall
+ - pecl install igbinary-2.0.8 && ./configure --enable-redis-igbinary --enable-redis-lzf || ./configure --enable-redis-lzf
install: make install
before_script:
- gem install redis
diff --git a/cluster_library.c b/cluster_library.c
index 43e0c1cf..cc1ef16e 100644
--- a/cluster_library.c
+++ b/cluster_library.c
@@ -651,6 +651,9 @@ cluster_node_create(redisCluster *c, char *host, size_t host_len,
node->slave = slave;
node->slaves = NULL;
+ /* Initialize our list of slot ranges */
+ 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);
@@ -690,10 +693,11 @@ cluster_node_add_slave(redisClusterNode *master, redisClusterNode *slave)
/* Use the output of CLUSTER SLOTS to map our nodes */
static int cluster_map_slots(redisCluster *c, clusterReply *r) {
+ redisClusterNode *pnode, *master, *slave;
+ redisSlotRange range;
int i,j, hlen, klen;
short low, high;
clusterReply *r2, *r3;
- redisClusterNode *pnode, *master, *slave;
unsigned short port;
char *host, key[1024];
@@ -746,6 +750,10 @@ static int cluster_map_slots(redisCluster *c, clusterReply *r) {
for (j = low; j<= high; j++) {
c->master[j] = master;
}
+
+ /* Append to our list of slot ranges */
+ range.low = low; range.high = high;
+ zend_llist_add_element(&master->slots, &range);
}
// Success
@@ -758,7 +766,10 @@ PHP_REDIS_API void cluster_free_node(redisClusterNode *node) {
zend_hash_destroy(node->slaves);
efree(node->slaves);
}
+
+ zend_llist_destroy(&node->slots);
redis_free_socket(node->sock);
+
efree(node);
}
@@ -802,6 +813,23 @@ static void ht_free_node(zval *data) {
cluster_free_node(node);
}
+/* zend_llist of slot ranges -> persistent array */
+static redisSlotRange *slot_range_list_clone(zend_llist *src, size_t *count) {
+ redisSlotRange *dst, *range;
+ size_t i = 0;
+
+ *count = zend_llist_count(src);
+ dst = pemalloc(*count * sizeof(*dst), 1);
+
+ range = zend_llist_get_first(src);
+ while (range) {
+ memcpy(&dst[i++], range, sizeof(*range));
+ range = zend_llist_get_next(src);
+ }
+
+ return dst;
+}
+
/* Construct a redisCluster object */
PHP_REDIS_API redisCluster *cluster_create(double timeout, double read_timeout,
int failover, int persistent)
@@ -860,10 +888,49 @@ cluster_free(redisCluster *c, int free_ctx TSRMLS_DC)
/* Free any error we've got */
if (c->err) zend_string_release(c->err);
+ /* Invalidate our cache if we were redirected during operation */
+ if (c->cache_key) {
+ if (c->redirections) {
+ zend_hash_del(&EG(persistent_list), c->cache_key);
+ }
+ zend_string_release(c->cache_key);
+ }
+
/* Free structure itself */
if (free_ctx) efree(c);
}
+/* Create a cluster slot cache structure */
+PHP_REDIS_API
+redisCachedCluster *cluster_cache_create(zend_string *hash, HashTable *nodes) {
+ redisCachedCluster *cc;
+ redisCachedMaster *cm;
+ redisClusterNode *node;
+
+ cc = pecalloc(1, sizeof(*cc), 1);
+ cc->hash = zend_string_dup(hash, 1);
+
+ /* Copy nodes */
+ cc->master = pecalloc(zend_hash_num_elements(nodes), sizeof(*cc->master), 1);
+ ZEND_HASH_FOREACH_PTR(nodes, node) {
+ /* Skip slaves */
+ if (node->slave) continue;
+
+ cm = &cc->master[cc->count];
+
+ /* Duplicate host/port and clone slot ranges */
+ cm->host.addr = zend_string_dup(node->sock->host, 1);
+ cm->host.port = node->sock->port;
+
+ /* Copy over slot ranges */
+ cm->slot = slot_range_list_clone(&node->slots, &cm->slots);
+
+ cc->count++;
+ } ZEND_HASH_FOREACH_END();
+
+ return cc;
+}
+
/* Takes our input hash table and returns a straigt C array with elements,
* which have been randomized. The return value needs to be freed. */
static zval **cluster_shuffle_seeds(HashTable *seeds, int *len) {
@@ -892,6 +959,110 @@ static zval **cluster_shuffle_seeds(HashTable *seeds, int *len) {
return z_seeds;
}
+static void cluster_free_cached_master(redisCachedMaster *cm) {
+ size_t i;
+
+ /* Free each slave entry */
+ for (i = 0; i < cm->slaves; i++) {
+ zend_string_release(cm->slave[i].addr);
+ }
+
+ /* Free other elements */
+ zend_string_release(cm->host.addr);
+ pefree(cm->slave, 1);
+ pefree(cm->slot, 1);
+}
+
+static redisClusterNode*
+cached_master_clone(redisCluster *c, redisCachedMaster *cm) {
+ redisClusterNode *node;
+ size_t i;
+
+ node = cluster_node_create(c, ZSTR_VAL(cm->host.addr), ZSTR_LEN(cm->host.addr),
+ cm->host.port, cm->slot[0].low, 0);
+
+ /* Now copy in our slot ranges */
+ for (i = 0; i < cm->slots; i++) {
+ zend_llist_add_element(&node->slots, &cm->slot[i]);
+ }
+
+ return node;
+}
+
+/* Destroy a persistent cached cluster */
+PHP_REDIS_API void cluster_cache_free(redisCachedCluster *rcc) {
+ size_t i;
+
+ /* Free masters */
+ for (i = 0; i < rcc->count; i++) {
+ cluster_free_cached_master(&rcc->master[i]);
+ }
+
+ /* Free hash key */
+ zend_string_release(rcc->hash);
+ pefree(rcc->master, 1);
+ pefree(rcc, 1);
+}
+
+/* Initialize cluster from cached slots */
+PHP_REDIS_API
+void cluster_init_cache(redisCluster *c, redisCachedCluster *cc) {
+ RedisSock *sock;
+ redisClusterNode *mnode, *slave;
+ redisCachedMaster *cm;
+ char key[HOST_NAME_MAX];
+ size_t keylen, i, j, s;
+ int *map;
+
+ /* Randomize seeds */
+ map = emalloc(sizeof(*map) * cc->count);
+ for (i = 0; i < cc->count; i++) map[i] = i;
+ fyshuffle(map, cc->count);
+
+ /* Iterate over masters */
+ for (i = 0; i < cc->count; i++) {
+ /* Attach cache key */
+ c->cache_key = cc->hash;
+
+ /* Grab the next master */
+ cm = &cc->master[map[i]];
+
+ /* Hash our host and port */
+ keylen = snprintf(key, sizeof(key), "%s:%u", ZSTR_VAL(cm->host.addr),
+ cm->host.port);
+
+ /* 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,
+ NULL, 0);
+
+ /* Add to seed nodes */
+ zend_hash_str_update_ptr(c->seeds, key, keylen, sock);
+
+ /* Create master node */
+ mnode = cached_master_clone(c, cm);
+
+ /* Add our master */
+ zend_hash_str_update_ptr(c->nodes, key, keylen, mnode);
+
+ /* Attach any slaves */
+ for (s = 0; s < cm->slaves; s++) {
+ zend_string *host = cm->slave[s].addr;
+ slave = cluster_node_create(c, ZSTR_VAL(host), ZSTR_LEN(host), cm->slave[s].port, 0, 1);
+ cluster_node_add_slave(mnode, slave);
+ }
+
+ /* Hook up direct slot access */
+ for (j = 0; j < cm->slots; j++) {
+ for (s = cm->slot[j].low; s <= cm->slot[j].high; s++) {
+ c->master[s] = mnode;
+ }
+ }
+ }
+
+ efree(map);
+}
+
/* Initialize seeds */
PHP_REDIS_API int
cluster_init_seeds(redisCluster *cluster, HashTable *ht_seeds) {
@@ -908,6 +1079,7 @@ cluster_init_seeds(redisCluster *cluster, HashTable *ht_seeds) {
if ((z_seed = z_seeds[i]) == NULL) continue;
ZVAL_DEREF(z_seed);
+
/* Has to be a string */
if (Z_TYPE_P(z_seed) != IS_STRING) continue;
@@ -940,7 +1112,7 @@ cluster_init_seeds(redisCluster *cluster, HashTable *ht_seeds) {
efree(z_seeds);
// Success if at least one seed seems valid
- return zend_hash_num_elements(cluster->seeds) > 0 ? 0 : -1;
+ return zend_hash_num_elements(cluster->seeds) > 0 ? SUCCESS : FAILURE;
}
/* Initial mapping of our cluster keyspace */
@@ -978,7 +1150,7 @@ PHP_REDIS_API int cluster_map_keyspace(redisCluster *c TSRMLS_DC) {
return -1;
}
- return 0;
+ return SUCCESS;
}
/* Parse the MOVED OR ASK redirection payload when we get such a response
@@ -1046,8 +1218,10 @@ static int cluster_check_response(redisCluster *c, REDIS_REPLY_TYPE *reply_type
}
// Check for MOVED or ASK redirection
- if ((moved = IS_MOVED(inbuf)) || IS_ASK(inbuf)) {
- // Set our redirection information
+ if ((moved = IS_MOVED(inbuf)) || IS_ASK(inbuf)) { // Set our redirection information
+ /* We'll want to invalidate slot cache if we're using one */
+ c->redirections++;
+
if (cluster_set_redirection(c,inbuf,moved) < 0) {
return -1;
}
@@ -1374,7 +1548,7 @@ PHP_REDIS_API int cluster_send_slot(redisCluster *c, short slot, char *cmd,
/* Send a command to given slot in our cluster. If we get a MOVED or ASK error
* we attempt to send the command to the node as directed. */
PHP_REDIS_API short cluster_send_command(redisCluster *c, short slot, const char *cmd,
- int cmd_len TSRMLS_DC)
+ int cmd_len TSRMLS_DC)
{
int resp, timedout = 0;
long msstart;
diff --git a/cluster_library.h b/cluster_library.h
index 62f7f9da..45223308 100644
--- a/cluster_library.h
+++ b/cluster_library.h
@@ -143,22 +143,43 @@ typedef enum CLUSTER_REDIR_TYPE {
/* MULTI BULK response callback typedef */
typedef int (*mbulk_cb)(RedisSock*,zval*,long long, void* TSRMLS_DC);
-/* Specific destructor to free a cluster object */
-// void redis_destructor_redis_cluster(zend_resource *rsrc TSRMLS_DC);
+/* A list of covered slot ranges */
+typedef struct redisSlotRange {
+ unsigned short low;
+ unsigned short high;
+} redisSlotRange;
+
+/* Simple host/port information for our cache */
+typedef struct redisCachedHost {
+ zend_string *addr;
+ unsigned short port;
+} redisCachedHost;
+
+/* Storage for a cached master node */
+typedef struct redisCachedMaster {
+ redisCachedHost host;
+
+ redisSlotRange *slot; /* Slots and count */
+ size_t slots;
+
+ redisCachedHost *slave; /* Slaves and their count */
+ size_t slaves;
+} redisCachedMaster;
+
+typedef struct redisCachedCluster {
+ // int rsrc_id; /* Zend resource ID */
+ zend_string *hash; /* What we're cached by */
+ redisCachedMaster *master; /* Array of masters */
+ size_t count; /* Number of masters */
+} redisCachedCluster;
/* A Redis Cluster master node */
typedef struct redisClusterNode {
- /* Our Redis socket in question */
- RedisSock *sock;
-
- /* A slot where one of these lives */
- short slot;
-
- /* Is this a slave node */
- unsigned short slave;
-
- /* A HashTable containing any slaves */
- HashTable *slaves;
+ RedisSock *sock; /* Our Redis socket in question */
+ short slot; /* One slot we believe this node serves */
+ zend_llist slots; /* List of all slots we believe this node serves */
+ unsigned short slave; /* Are we a slave */
+ HashTable *slaves; /* Hash table of slaves */
} redisClusterNode;
/* Forward declarations */
@@ -208,6 +229,11 @@ typedef struct redisCluster {
/* Flag for when we get a CLUSTERDOWN error */
short clusterdown;
+ /* Key to our persistent list cache and number of redirections we've
+ * received since construction */
+ zend_string *cache_key;
+ uint64_t redirections;
+
/* The last ERROR we encountered */
zend_string *err;
@@ -362,6 +388,13 @@ PHP_REDIS_API int cluster_init_seeds(redisCluster *c, HashTable *ht_seeds);
PHP_REDIS_API int cluster_map_keyspace(redisCluster *c TSRMLS_DC);
PHP_REDIS_API void cluster_free_node(redisClusterNode *node);
+/* Functions for interacting with cached slots maps */
+PHP_REDIS_API redisCachedCluster *cluster_cache_create(zend_string *hash, HashTable *nodes);
+PHP_REDIS_API void cluster_cache_free(redisCachedCluster *rcc);
+PHP_REDIS_API void cluster_init_cache(redisCluster *c, redisCachedCluster *rcc);
+
+/* Functions to facilitate cluster slot caching */
+
PHP_REDIS_API char **cluster_sock_read_multibulk_reply(RedisSock *redis_sock,
int *len TSRMLS_DC);
diff --git a/common.h b/common.h
index 884e9764..3fa078c5 100644
--- a/common.h
+++ b/common.h
@@ -6,6 +6,7 @@
#define PHPREDIS_NOTUSED(v) ((void)v)
+#include "zend_llist.h"
#include <ext/standard/php_var.h>
#include <ext/standard/php_math.h>
#include <zend_smart_str.h>
diff --git a/redis.c b/redis.c
index e7499adb..d94b5a6d 100644
--- a/redis.c
+++ b/redis.c
@@ -50,12 +50,15 @@ extern zend_class_entry *redis_cluster_exception_ce;
zend_class_entry *redis_ce;
zend_class_entry *redis_exception_ce;
+extern int le_cluster_slot_cache;
+
extern zend_function_entry redis_array_functions[];
extern zend_function_entry redis_cluster_functions[];
PHP_INI_BEGIN()
/* redis arrays */
PHP_INI_ENTRY("redis.arrays.algorithm", "", PHP_INI_ALL, NULL)
+ PHP_INI_ENTRY("redis.arrays.auth", "", PHP_INI_ALL, NULL)
PHP_INI_ENTRY("redis.arrays.autorehash", "0", PHP_INI_ALL, NULL)
PHP_INI_ENTRY("redis.arrays.connecttimeout", "0", PHP_INI_ALL, NULL)
PHP_INI_ENTRY("redis.arrays.distributor", "", PHP_INI_ALL, NULL)
@@ -71,6 +74,7 @@ PHP_INI_BEGIN()
PHP_INI_ENTRY("redis.arrays.consistent", "0", PHP_INI_ALL, NULL)
/* redis cluster */
+ PHP_INI_ENTRY("redis.clusters.cache_slots", "0", PHP_INI_ALL, NULL)
PHP_INI_ENTRY("redis.clusters.auth", "", PHP_INI_ALL, NULL)
PHP_INI_ENTRY("redis.clusters.persistent", "0", PHP_INI_ALL, NULL)
PHP_INI_ENTRY("redis.clusters.read_timeout", "0", PHP_INI_ALL, NULL)
@@ -555,6 +559,12 @@ free_reply_callbacks(RedisSock *redis_sock)
redis_sock->current = NULL;
}
+/* Passthru for destroying cluster cache */
+static void cluster_cache_dtor(zend_resource *rsrc) {
+ redisCachedCluster *rcc = (redisCachedCluster*)rsrc->ptr;
+ cluster_cache_free(rcc);
+}
+
void
free_redis_object(zend_object *object)
{
@@ -737,6 +747,10 @@ PHP_MINIT_FUNCTION(redis)
redis_cluster_ce = zend_register_internal_class(&redis_cluster_class_entry TSRMLS_CC);
redis_cluster_ce->create_object = create_cluster_context;
+ /* Register our cluster cache list item */
+ le_cluster_slot_cache = zend_register_list_destructors_ex(NULL, cluster_cache_dtor,
+ "Redis cluster slot cache",
+ module_number);
/* Base Exception class */
#if HAVE_SPL
diff --git a/redis_array.c b/redis_array.c
index c332f6fc..67e117dd 100644
--- a/redis_array.c
+++ b/redis_array.c
@@ -160,7 +160,7 @@ redis_array_free(RedisArray *ra)
zval_dtor(&ra->z_dist);
/* Hashing algorithm */
- zval_dtor(&ra->z_algo);
+ if (ra->algorithm) zend_string_release(ra->algorithm);
/* Delete pur commands */
zend_hash_destroy(ra->pure_cmds);
@@ -239,13 +239,14 @@ ra_call_user_function(HashTable *function_table, zval *object, zval *function_na
Public constructor */
PHP_METHOD(RedisArray, __construct)
{
- zval *z0, z_fun, z_dist, z_algo, *zpData, *z_opts = NULL;
+ zval *z0, z_fun, z_dist, *zpData, *z_opts = NULL;
RedisArray *ra = NULL;
zend_bool b_index = 0, b_autorehash = 0, b_pconnect = 0, consistent = 0;
HashTable *hPrev = NULL, *hOpts = NULL;
long l_retry_interval = 0;
zend_bool b_lazy_connect = 0;
double d_connect_timeout = 0, read_timeout = 0.0;
+ zend_string *algorithm = NULL, *auth = NULL;
redis_array_object *obj;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|a", &z0, &z_opts) == FAILURE) {
@@ -254,7 +255,6 @@ PHP_METHOD(RedisArray, __construct)
ZVAL_NULL(&z_fun);
ZVAL_NULL(&z_dist);
- ZVAL_NULL(&z_algo);
/* extract options */
if(z_opts) {
hOpts = Z_ARRVAL_P(z_opts);
@@ -279,7 +279,7 @@ PHP_METHOD(RedisArray, __construct)
/* extract function name. */
if ((zpData = zend_hash_str_find(hOpts, "algorithm", sizeof("algorithm") - 1)) != NULL && Z_TYPE_P(zpData) == IS_STRING) {
- ZVAL_ZVAL(&z_algo, zpData, 1, 0);
+ algorithm = zval_get_string(zpData);
}
/* extract index option. */
@@ -337,6 +337,11 @@ PHP_METHOD(RedisArray, __construct)
if ((zpData = zend_hash_str_find(hOpts, "consistent", sizeof("consistent") - 1)) != NULL) {
consistent = zval_is_true(zpData);
}
+
+ /* auth */
+ if ((zpData = zend_hash_str_find(hOpts, "auth", sizeof("auth") - 1)) != NULL) {
+ auth = zval_get_string(zpData);
+ }
}
/* extract either name of list of hosts from z0 */
@@ -346,13 +351,14 @@ PHP_METHOD(RedisArray, __construct)
break;
case IS_ARRAY:
- ra = ra_make_array(Z_ARRVAL_P(z0), &z_fun, &z_dist, &z_algo, hPrev, b_index, b_pconnect, l_retry_interval, b_lazy_connect, d_connect_timeout, read_timeout, consistent TSRMLS_CC);
+ ra = ra_make_array(Z_ARRVAL_P(z0), &z_fun, &z_dist, hPrev, b_index, b_pconnect, l_retry_interval, b_lazy_connect, d_connect_timeout, read_timeout, consistent, algorithm, auth TSRMLS_CC);
break;
default:
WRONG_PARAM_COUNT;
}
- zval_dtor(&z_algo);
+ if (algorithm) zend_string_release(algorithm);
+ if (auth) zend_string_release(auth);
zval_dtor(&z_dist);
zval_dtor(&z_fun);
diff --git a/redis_array.h b/redis_array.h
index 55538860..54218412 100644
--- a/redis_array.h
+++ b/redis_array.h
@@ -58,7 +58,7 @@ typedef struct RedisArray_ {
zend_bool pconnect; /* should we use pconnect */
zval z_fun; /* key extractor, callable */
zval z_dist; /* key distributor, callable */
- zval z_algo; /* key hashing algorithm name */
+ zend_string *algorithm; /* key hashing algorithm name */
HashTable *pure_cmds; /* hash table */
double connect_timeout; /* socket connect timeout */
double read_timeout; /* socket read timeout */
diff --git a/redis_array_impl.c b/redis_array_impl.c
index c879d094..153368d5 100644
--- a/redis_array_impl.c
+++ b/redis_array_impl.c
@@ -32,8 +32,8 @@
extern zend_class_entry *redis_ce;
-RedisArray*
-ra_load_hosts(RedisArray *ra, HashTable *hosts, long retry_interval, zend_bool b_lazy_connect TSRMLS_DC)
+static RedisArray *
+ra_load_hosts(RedisArray *ra, HashTable *hosts, zend_string *auth, long retry_interval, zend_bool b_lazy_connect TSRMLS_DC)
{
int i = 0, host_len;
char *host, *p;
@@ -74,10 +74,13 @@ ra_load_hosts(RedisArray *ra, HashTable *hosts, long retry_interval, zend_bool b
/* create socket */
redis->sock = redis_sock_create(host, host_len, port, ra->connect_timeout, ra->read_timeout, ra->pconnect, NULL, retry_interval);
+ /* copy password is specified */
+ if (auth) redis->sock->auth = zend_string_copy(auth);
+
if (!b_lazy_connect)
{
/* connect */
- if (redis_sock_server_open(redis->sock TSRMLS_CC) < 0) {
+ if (redis_sock_server_open(redis->sock TSRMLS_CC) < 0 || (auth && redis_sock_auth(redis->sock TSRMLS_CC) < 0)) {
zval_dtor(&z_cons);
ra->count = ++i;
return NULL;
@@ -161,7 +164,7 @@ ra_find_name(const char *name) {
/* laod array from INI settings */
RedisArray *ra_load_array(const char *name TSRMLS_DC) {
- zval *z_data, z_fun, z_dist, z_algo;
+ zval *z_data, z_fun, z_dist;
zval z_params_hosts;
zval z_params_prev;
zval z_params_funs;
@@ -175,8 +178,10 @@ RedisArray *ra_load_array(const char *name TSRMLS_DC) {
zval z_params_read_timeout;
zval z_params_lazy_connect;
zval z_params_consistent;
+ zval z_params_auth;
RedisArray *ra = NULL;
+ zend_string *algorithm = NULL, *auth = NULL;
zend_bool b_index = 0, b_autorehash = 0, b_pconnect = 0, consistent = 0;
long l_retry_interval = 0;
zend_bool b_lazy_connect = 0;
@@ -232,9 +237,8 @@ RedisArray *ra_load_array(const char *name TSRMLS_DC) {
if ((iptr = INI_STR("redis.arrays.algorithm")) != NULL) {
sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_params_algo TSRMLS_CC);
}
- ZVAL_NULL(&z_algo);
if ((z_data = zend_hash_str_find(Z_ARRVAL(z_params_algo), name, name_len)) != NULL) {
- ZVAL_ZVAL(&z_algo, z_data, 1, 0);
+ algorithm = zval_get_string(z_data);
}
/* find index option */
@@ -335,15 +339,26 @@ RedisArray *ra_load_array(const char *name TSRMLS_DC) {
}
}
+ /* find auth option */
+ array_init(&z_params_auth);
+ if ((iptr = INI_STR("redis.arrays.auth")) != NULL) {
+ sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_params_auth TSRMLS_CC);
+ }
+ if ((z_data = zend_hash_str_find(Z_ARRVAL(z_params_auth), name, name_len)) != NULL) {
+ auth = zval_get_string(z_data);
+ }
/* create RedisArray object */
- ra = ra_make_array(hHosts, &z_fun, &z_dist, &z_algo, hPrev, b_index, b_pconnect, l_retry_interval, b_lazy_connect, d_connect_timeout, read_timeout, consistent TSRMLS_CC);
+ ra = ra_make_array(hHosts, &z_fun, &z_dist, hPrev, b_index, b_pconnect, l_retry_interval, b_lazy_connect, d_connect_timeout, read_timeout, consistent, algorithm, auth TSRMLS_CC);
if (ra) {
ra->auto_rehash = b_autorehash;
if(ra->prev) ra->prev->auto_rehash = b_autorehash;
}
/* cleanup */
+ if (algorithm) zend_string_release(algorithm);
+ if (auth) zend_string_release(auth);
+
zval_dtor(&z_params_hosts);
zval_dtor(&z_params_prev);
zval_dtor(&z_params_funs);
@@ -357,7 +372,7 @@ RedisArray *ra_load_array(const char *name TSRMLS_DC) {
zval_dtor(&z_params_read_timeout);
zval_dtor(&z_params_lazy_connect);
zval_dtor(&z_params_consistent);
- zval_dtor(&z_algo);
+ zval_dtor(&z_params_auth);
zval_dtor(&z_dist);
zval_dtor(&z_fun);
@@ -405,8 +420,8 @@ ra_make_continuum(zend_string **hosts, int nb_hosts)
}
RedisArray *
-ra_make_array(HashTable *hosts, zval *z_fun, zval *z_dist, zval *z_algo, HashTable *hosts_prev, zend_bool b_index, zend_bool b_pconnect, long retry_interval, zend_bool b_lazy_connect, double connect_timeout, double read_timeout, zend_bool consistent TSRMLS_DC) {
-
+ra_make_array(HashTable *hosts, zval *z_fun, zval *z_dist, HashTable *hosts_prev, zend_bool b_index, zend_bool b_pconnect, long retry_interval, zend_bool b_lazy_connect, double connect_timeout, double read_timeout, zend_bool consistent, zend_string *algorithm, zend_string *auth TSRMLS_DC)
+{
int i, count;
RedisArray *ra;
@@ -415,7 +430,7 @@ ra_make_array(HashTable *hosts, zval *z_fun, zval *z_dist, zval *z_algo, HashTab
/* create object */
ra = emalloc(sizeof(RedisArray));
ra->hosts = ecalloc(count, sizeof(*ra->hosts));
- ra->redis = ecalloc(count, sizeof(zval));
+ ra->redis = ecalloc(count, sizeof(*ra->redis));
ra->count = 0;
ra->z_multi_exec = NULL;
ra->index = b_index;
@@ -424,8 +439,9 @@ ra_make_array(HashTable *hosts, zval *z_fun, zval *z_dist, zval *z_algo, HashTab
ra->connect_timeout = connect_timeout;
ra->read_timeout = read_timeout;
ra->continuum = NULL;
+ ra->algorithm = NULL;
- if (ra_load_hosts(ra, hosts, retry_interval, b_lazy_connect TSRMLS_CC) == NULL || !ra->count) {
+ if (ra_load_hosts(ra, hosts, auth, retry_interval, b_lazy_connect TSRMLS_CC) == NULL || !ra->count) {
for (i = 0; i < ra->count; ++i) {
zval_dtor(&ra->redis[i]);
zend_string_release(ra->hosts[i]);
@@ -435,7 +451,7 @@ ra_make_array(HashTable *hosts, zval *z_fun, zval *z_dist, zval *z_algo, HashTab
efree(ra);
return NULL;
}
- ra->prev = hosts_prev ? ra_make_array(hosts_prev, z_fun, z_dist, z_algo, NULL, b_index, b_pconnect, retry_interval, b_lazy_connect, connect_timeout, read_timeout, consistent TSRMLS_CC) : NULL;
+ ra->prev = hosts_prev ? ra_make_array(hosts_prev, z_fun, z_dist, NULL, b_index, b_pconnect, retry_interval, b_lazy_connect, connect_timeout, read_timeout, consistent, algorithm, auth TSRMLS_CC) : NULL;
/* init array data structures */
ra_init_function_table(ra);
@@ -443,7 +459,7 @@ ra_make_array(HashTable *hosts, zval *z_fun, zval *z_dist, zval *z_algo, HashTab
/* Set hash function and distribtor if provided */
ZVAL_ZVAL(&ra->z_fun, z_fun, 1, 0);
ZVAL_ZVAL(&ra->z_dist, z_dist, 1, 0);
- ZVAL_ZVAL(&ra->z_algo, z_algo, 1, 0);
+ if (algorithm) ra->algorithm = zend_string_copy(algorithm);
/* init continuum */
if (consistent) {
@@ -537,7 +553,7 @@ ra_find_node(RedisArray *ra, const char *key, int key_len, int *out_pos TSRMLS_D
const php_hash_ops *ops;
/* hash */
- if (Z_TYPE(ra->z_algo) == IS_STRING && (ops = php_hash_fetch_ops(Z_STRVAL(ra->z_algo), Z_STRLEN(ra->z_algo))) != NULL) {
+ if (ra->algorithm && (ops = php_hash_fetch_ops(ZSTR_VAL(ra->algorithm), ZSTR_LEN(ra->algorithm))) != NULL) {
void *ctx = emalloc(ops->context_size);
unsigned char *digest = emalloc(ops->digest_size);
diff --git a/redis_array_impl.h b/redis_array_impl.h
index 43705ee5..877b22f8 100644
--- a/redis_array_impl.h
+++ b/redis_array_impl.h
@@ -9,9 +9,8 @@
#include "redis_array.h"
-RedisArray *ra_load_hosts(RedisArray *ra, HashTable *hosts, long retry_interval, zend_bool b_lazy_connect TSRMLS_DC);
RedisArray *ra_load_array(const char *name TSRMLS_DC);
-RedisArray *ra_make_array(HashTable *hosts, zval *z_fun, zval *z_dist, zval *z_algo, HashTable *hosts_prev, zend_bool b_index, zend_bool b_pconnect, long retry_interval, zend_bool b_lazy_connect, double connect_timeout, double read_timeout, zend_bool consistent TSRMLS_DC);
+RedisArray *ra_make_array(HashTable *hosts, zval *z_fun, zval *z_dist, HashTable *hosts_prev, zend_bool b_index, zend_bool b_pconnect, long retry_interval, zend_bool b_lazy_connect, double connect_timeout, double read_timeout, zend_bool consistent, zend_string *algorithm, zend_string *auth TSRMLS_DC);
zval *ra_find_node_by_name(RedisArray *ra, const char *host, int host_len TSRMLS_DC);
zval *ra_find_node(RedisArray *ra, const char *key, int key_len, int *out_pos TSRMLS_DC);
void ra_init_function_table(RedisArray *ra);
diff --git a/redis_cluster.c b/redis_cluster.c
index 4de9d6c3..2688c178 100644
--- a/redis_cluster.c
+++ b/redis_cluster.c
@@ -33,6 +33,7 @@
#include <SAPI.h>
zend_class_entry *redis_cluster_ce;
+int le_cluster_slot_cache;
/* Exception handler */
zend_class_entry *redis_cluster_exception_ce;
@@ -350,49 +351,137 @@ void free_cluster_context(zend_object *object) {
zend_object_std_dtor(&cluster->std TSRMLS_CC);
}
-/* 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, char *auth,
- size_t auth_len TSRMLS_DC)
-{
- // Validate timeout
+/* Turn a seed array into a zend_string we can use to look up a slot cache */
+static zend_string *cluster_hash_seeds(HashTable *ht) {
+ smart_str hash = {0};
+ zend_string *zstr;
+ zval *z_seed;
+
+ ZEND_HASH_FOREACH_VAL(ht, z_seed) {
+ zstr = zval_get_string(z_seed);
+ smart_str_appendc(&hash, '[');
+ smart_str_appendl(&hash, ZSTR_VAL(zstr), ZSTR_LEN(zstr));
+ smart_str_appendc(&hash, ']');
+ zend_string_release(zstr);
+ } ZEND_HASH_FOREACH_END();
+
+ /* Not strictly needed but null terminate anyway */
+ smart_str_0(&hash);
+
+ /* smart_str is a zend_string internally */
+ return hash.s;
+}
+
+#define CACHING_ENABLED() (INI_INT("redis.clusters.cache_slots") == 1)
+static redisCachedCluster *cluster_cache_load(HashTable *ht_seeds TSRMLS_DC) {
+ zend_resource *le;
+ zend_string *h;
+
+ /* Short circuit if we're not caching slots or if our seeds don't have any
+ * elements, since it doesn't make sense to cache an empty string */
+ if (!CACHING_ENABLED() || zend_hash_num_elements(ht_seeds) == 0)
+ return NULL;
+
+ /* Look for cached slot information */
+ h = cluster_hash_seeds(ht_seeds);
+ le = zend_hash_str_find_ptr(&EG(persistent_list), ZSTR_VAL(h), ZSTR_LEN(h));
+ zend_string_release(h);
+
+ if (le != NULL) {
+ /* Sanity check on our list type */
+ if (le->type != le_cluster_slot_cache) {
+ php_error_docref(0 TSRMLS_CC, E_WARNING, "Invalid slot cache resource");
+ return NULL;
+ }
+
+ /* Success, return the cached entry */
+ return le->ptr;
+ }
+
+ /* Not found */
+ return NULL;
+}
+
+/* Cache a cluster's slot information in persistent_list if it's enabled */
+static int cluster_cache_store(HashTable *ht_seeds, HashTable *nodes TSRMLS_DC) {
+ redisCachedCluster *cc;
+ zend_string *hash;
+
+ /* Short circuit if caching is disabled or there aren't any seeds */
+ if (!CACHING_ENABLED() || zend_hash_num_elements(ht_seeds) == 0)
+ return !CACHING_ENABLED() ? SUCCESS : FAILURE;
+
+ /* Construct our cache */
+ hash = cluster_hash_seeds(ht_seeds);
+ cc = cluster_cache_create(hash, nodes);
+ zend_string_release(hash);
+
+ /* Set up our resource */
+#if PHP_VERSION_ID < 70300
+ zend_resource le;
+ le.type = le_cluster_slot_cache;
+ le.ptr = cc;
+
+ zend_hash_update_mem(&EG(persistent_list), cc->hash, (void*)&le, sizeof(zend_resource));
+#else
+ zend_register_persistent_resource_ex(cc->hash, cc, le_cluster_slot_cache);
+#endif
+
+ return SUCCESS;
+}
+
+/* Validate redis cluster construction arguments */
+static int
+cluster_validate_args(double timeout, double read_timeout, HashTable *seeds) {
if (timeout < 0L || timeout > INT_MAX) {
CLUSTER_THROW_EXCEPTION("Invalid timeout", 0);
+ return FAILURE;
}
-
- // Validate our read timeout
if (read_timeout < 0L || read_timeout > INT_MAX) {
CLUSTER_THROW_EXCEPTION("Invalid read timeout", 0);
+ return FAILURE;
}
-
/* Make sure there are some seeds */
- if (zend_hash_num_elements(ht_seeds) == 0) {
+ if (zend_hash_num_elements(seeds) == 0) {
CLUSTER_THROW_EXCEPTION("Must pass seeds", 0);
+ return FAILURE;
}
+ return SUCCESS;
+}
+
+/* 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, char *auth,
+ size_t auth_len TSRMLS_DC)
+{
+ redisCachedCluster *cc;
+
+ cluster_validate_args(timeout, read_timeout, ht_seeds);
+
if (auth && auth_len > 0) {
c->auth = zend_string_init(auth, auth_len, 0);
}
- /* Set our timeout and read_timeout which we'll pass through to the
- * socket type operations */
c->timeout = timeout;
c->read_timeout = read_timeout;
-
- /* Set our option to use or not use persistent connections */
c->persistent = persistent;
/* Calculate the number of miliseconds we will wait when bouncing around,
* (e.g. a node goes down), which is not the same as a standard timeout. */
c->waitms = (long)(timeout * 1000);
- // Initialize our RedisSock "seed" objects
- cluster_init_seeds(c, ht_seeds);
-
- // Create and map our key space
- cluster_map_keyspace(c TSRMLS_CC);
+ /* Attempt to load from cache */
+ if ((cc = cluster_cache_load(ht_seeds TSRMLS_CC))) {
+ cluster_init_cache(c, cc);
+ } else if (cluster_init_seeds(c, ht_seeds) == SUCCESS &&
+ cluster_map_keyspace(c TSRMLS_CC) == SUCCESS)
+ {
+ cluster_cache_store(ht_seeds, c->nodes TSRMLS_CC);
+ }
}
+
/* Attempt to load a named cluster configured in php.ini */
void redis_cluster_load(redisCluster *c, char *name, int name_len TSRMLS_DC) {
zval z_seeds, z_timeout, z_read_timeout, z_persistent, z_auth, *z_value;
@@ -911,7 +1000,7 @@ static void cluster_generic_delete(INTERNAL_FUNCTION_PARAMETERS,
char *kw, int kw_len)
{
zval *z_ret = emalloc(sizeof(*z_ret));
-
+
// Initialize a LONG value to zero for our return
ZVAL_LONG(z_ret, 0);
@@ -3098,3 +3187,4 @@ PHP_METHOD(RedisCluster, command) {
}
/* vim: set tabstop=4 softtabstop=4 expandtab shiftwidth=4: */
+