Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 5ca4141

Browse files
Issue.1765 (#1774)
Various improvements and fixes to cluster slot caching. * Improves slot caching so any unique set of seeds all hash to the same key * Fix a couple of memory leaks. * Fixes a segfault when executing a multiple key command such as `MGET` or `MSET` while the cluster is resharding.
1 parent a0c53e0 commit 5ca4141

4 files changed

Lines changed: 255 additions & 176 deletions

File tree

cluster_library.c

Lines changed: 163 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -865,12 +865,14 @@ cluster_free(redisCluster *c, int free_ctx)
865865
/* Free any error we've got */
866866
if (c->err) zend_string_release(c->err);
867867

868-
/* Invalidate our cache if we were redirected during operation */
869868
if (c->cache_key) {
869+
/* Invalidate persistent cache if the cluster has changed */
870870
if (c->redirections) {
871871
zend_hash_del(&EG(persistent_list), c->cache_key);
872-
c->cache_key = NULL;
873872
}
873+
874+
/* Release our hold on the cache key */
875+
zend_string_release(c->cache_key);
874876
}
875877

876878
/* Free structure itself */
@@ -921,34 +923,6 @@ redisCachedCluster *cluster_cache_create(zend_string *hash, HashTable *nodes) {
921923
return cc;
922924
}
923925

924-
/* Takes our input hash table and returns a straight C array with elements,
925-
* which have been randomized. The return value needs to be freed. */
926-
static zval **cluster_shuffle_seeds(HashTable *seeds, int *len) {
927-
zval **z_seeds, *z_ele;
928-
int *map, i, count, index = 0;
929-
930-
/* How many */
931-
count = zend_hash_num_elements(seeds);
932-
933-
/* Allocate our return value and map */
934-
z_seeds = ecalloc(count, sizeof(zval*));
935-
map = emalloc(sizeof(int)*count);
936-
937-
/* Fill in and shuffle our map */
938-
for (i = 0; i < count; i++) map[i] = i;
939-
fyshuffle(map, count);
940-
941-
/* Iterate over our source array and use our map to create a random list */
942-
ZEND_HASH_FOREACH_VAL(seeds, z_ele) {
943-
z_seeds[map[index++]] = z_ele;
944-
} ZEND_HASH_FOREACH_END();
945-
946-
efree(map);
947-
948-
*len = count;
949-
return z_seeds;
950-
}
951-
952926
static void cluster_free_cached_master(redisCachedMaster *cm) {
953927
size_t i;
954928

@@ -988,7 +962,6 @@ PHP_REDIS_API void cluster_cache_free(redisCachedCluster *rcc) {
988962
cluster_free_cached_master(&rcc->master[i]);
989963
}
990964

991-
/* Free hash key */
992965
zend_string_release(rcc->hash);
993966
pefree(rcc->master, 1);
994967
pefree(rcc, 1);
@@ -1009,17 +982,16 @@ void cluster_init_cache(redisCluster *c, redisCachedCluster *cc) {
1009982
for (i = 0; i < cc->count; i++) map[i] = i;
1010983
fyshuffle(map, cc->count);
1011984

985+
/* Duplicate the hash key so we can invalidate when redirected */
986+
c->cache_key = zend_string_copy(cc->hash);
987+
1012988
/* Iterate over masters */
1013989
for (i = 0; i < cc->count; i++) {
1014-
/* Attach cache key */
1015-
c->cache_key = cc->hash;
1016-
1017990
/* Grab the next master */
1018991
cm = &cc->master[map[i]];
1019992

1020993
/* Hash our host and port */
1021-
keylen = snprintf(key, sizeof(key), "%s:%u", ZSTR_VAL(cm->host.addr),
1022-
cm->host.port);
994+
keylen = snprintf(key, sizeof(key), "%s:%u", ZSTR_VAL(cm->host.addr), cm->host.port);
1023995

1024996
/* Create socket */
1025997
sock = redis_sock_create(ZSTR_VAL(cm->host.addr), ZSTR_LEN(cm->host.addr), cm->host.port,
@@ -1053,56 +1025,45 @@ void cluster_init_cache(redisCluster *c, redisCachedCluster *cc) {
10531025
efree(map);
10541026
}
10551027

1056-
/* Initialize seeds */
1057-
PHP_REDIS_API int
1058-
cluster_init_seeds(redisCluster *cluster, HashTable *ht_seeds) {
1059-
RedisSock *redis_sock;
1060-
char *str, *psep, key[1024];
1061-
int key_len, count, i;
1062-
zval **z_seeds, *z_seed;
1063-
1064-
/* Get our seeds in a randomized array */
1065-
z_seeds = cluster_shuffle_seeds(ht_seeds, &count);
1066-
1067-
// Iterate our seeds array
1068-
for (i = 0; i < count; i++) {
1069-
if ((z_seed = z_seeds[i]) == NULL) continue;
1070-
1071-
ZVAL_DEREF(z_seed);
1028+
/* Initialize seeds. By the time we get here we've already validated our
1029+
* seeds array and know we have a non-empty array of strings all in
1030+
* host:port format. */
1031+
PHP_REDIS_API void
1032+
cluster_init_seeds(redisCluster *cluster, zend_string **seeds, uint32_t nseeds) {
1033+
RedisSock *sock;
1034+
char *seed, *sep, key[1024];
1035+
int key_len, i, *map;
10721036

1073-
/* Has to be a string */
1074-
if (Z_TYPE_P(z_seed) != IS_STRING) continue;
1037+
/* Get a randomized order to hit our seeds */
1038+
map = ecalloc(nseeds, sizeof(*map));
1039+
for (i = 0; i < nseeds; i++) map[i] = i;
1040+
fyshuffle(map, nseeds);
10751041

1076-
// Grab a copy of the string
1077-
str = Z_STRVAL_P(z_seed);
1042+
for (i = 0; i < nseeds; i++) {
1043+
seed = ZSTR_VAL(seeds[map[i]]);
10781044

1079-
/* Make sure we have a colon for host:port. Search right to left in the
1080-
* case of IPv6 */
1081-
if ((psep = strrchr(str, ':')) == NULL)
1082-
continue;
1045+
sep = strrchr(seed, ':');
1046+
ZEND_ASSERT(sep != NULL);
10831047

10841048
// Allocate a structure for this seed
1085-
redis_sock = redis_sock_create(str, psep-str,
1086-
(unsigned short)atoi(psep+1), cluster->timeout,
1049+
sock = redis_sock_create(seed, sep - seed,
1050+
(unsigned short)atoi(sep+1), cluster->timeout,
10871051
cluster->read_timeout, cluster->persistent, NULL, 0);
10881052

10891053
// Set auth information if specified
10901054
if (cluster->flags->auth) {
1091-
redis_sock->auth = zend_string_copy(cluster->flags->auth);
1055+
sock->auth = zend_string_copy(cluster->flags->auth);
10921056
}
10931057

10941058
// Index this seed by host/port
1095-
key_len = snprintf(key, sizeof(key), "%s:%u", ZSTR_VAL(redis_sock->host),
1096-
redis_sock->port);
1059+
key_len = snprintf(key, sizeof(key), "%s:%u", ZSTR_VAL(sock->host),
1060+
sock->port);
10971061

10981062
// Add to our seed HashTable
1099-
zend_hash_str_update_ptr(cluster->seeds, key, key_len, redis_sock);
1063+
zend_hash_str_update_ptr(cluster->seeds, key, key_len, sock);
11001064
}
11011065

1102-
efree(z_seeds);
1103-
1104-
// Success if at least one seed seems valid
1105-
return zend_hash_num_elements(cluster->seeds) > 0 ? SUCCESS : FAILURE;
1066+
efree(map);
11061067
}
11071068

11081069
/* Initial mapping of our cluster keyspace */
@@ -1137,7 +1098,7 @@ PHP_REDIS_API int cluster_map_keyspace(redisCluster *c) {
11371098
// Throw an exception if we couldn't map
11381099
if (!mapped) {
11391100
CLUSTER_THROW_EXCEPTION("Couldn't map cluster keyspace using any provided seed", 0);
1140-
return -1;
1101+
return FAILURE;
11411102
}
11421103

11431104
return SUCCESS;
@@ -1626,11 +1587,9 @@ PHP_REDIS_API short cluster_send_command(redisCluster *c, short slot, const char
16261587
redis_sock_disconnect(c->cmd_sock, 1);
16271588

16281589
if (timedout) {
1629-
CLUSTER_THROW_EXCEPTION(
1630-
"Timed out attempting to find data in the correct node!", 0);
1590+
CLUSTER_THROW_EXCEPTION("Timed out attempting to find data in the correct node!", 0);
16311591
} else {
1632-
CLUSTER_THROW_EXCEPTION(
1633-
"Error processing response from Redis node!", 0);
1592+
CLUSTER_THROW_EXCEPTION("Error processing response from Redis node!", 0);
16341593
}
16351594

16361595
return -1;
@@ -2460,10 +2419,11 @@ PHP_REDIS_API void cluster_msetnx_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluste
24602419
// Set return value if it's our last response
24612420
if (mctx->last) {
24622421
if (CLUSTER_IS_ATOMIC(c)) {
2463-
RETVAL_ZVAL(mctx->z_multi, 0, 1);
2422+
RETVAL_ZVAL(mctx->z_multi, 0, 0);
24642423
} else {
24652424
add_next_index_zval(&c->multi_resp, mctx->z_multi);
24662425
}
2426+
efree(mctx->z_multi);
24672427
}
24682428

24692429
// Free multi context
@@ -2745,42 +2705,145 @@ int mbulk_resp_loop_assoc(RedisSock *redis_sock, zval *z_result,
27452705
return SUCCESS;
27462706
}
27472707

2748-
/* Turn a seed array into a zend_string we can use to look up a slot cache */
2749-
zend_string *cluster_hash_seeds(HashTable *ht) {
2750-
smart_str hash = {0};
2751-
zend_string *zstr;
2708+
/* Free an array of zend_string seeds */
2709+
void free_seed_array(zend_string **seeds, uint32_t nseeds) {
2710+
int i;
2711+
2712+
if (seeds == NULL)
2713+
return;
2714+
2715+
for (i = 0; i < nseeds; i++)
2716+
zend_string_release(seeds[i]);
2717+
2718+
efree(seeds);
2719+
}
2720+
2721+
static zend_string **get_valid_seeds(HashTable *input, uint32_t *nseeds) {
2722+
HashTable *valid;
2723+
uint32_t count, idx = 0;
27522724
zval *z_seed;
2725+
zend_string *zkey, **seeds = NULL;
2726+
2727+
/* Short circuit if we don't have any sees */
2728+
count = zend_hash_num_elements(input);
2729+
if (count == 0)
2730+
return NULL;
27532731

2754-
ZEND_HASH_FOREACH_VAL(ht, z_seed) {
2755-
zstr = zval_get_string(z_seed);
2732+
ALLOC_HASHTABLE(valid);
2733+
zend_hash_init(valid, count, NULL, NULL, 0);
2734+
2735+
ZEND_HASH_FOREACH_VAL(input, z_seed) {
2736+
ZVAL_DEREF(z_seed);
2737+
2738+
if (Z_TYPE_P(z_seed) != IS_STRING) {
2739+
php_error_docref(NULL, E_WARNING, "Skipping non-string entry in seeds array");
2740+
continue;
2741+
} else if (strrchr(Z_STRVAL_P(z_seed), ':') == NULL) {
2742+
php_error_docref(NULL, E_WARNING,
2743+
"Seed '%s' not in host:port format, ignoring", Z_STRVAL_P(z_seed));
2744+
continue;
2745+
}
2746+
2747+
/* Add as a key to avoid duplicates */
2748+
zend_hash_str_update_ptr(valid, Z_STRVAL_P(z_seed), Z_STRLEN_P(z_seed), NULL);
2749+
} ZEND_HASH_FOREACH_END();
2750+
2751+
/* We need at least one valid seed */
2752+
count = zend_hash_num_elements(valid);
2753+
if (count == 0)
2754+
goto cleanup;
2755+
2756+
/* Populate our return array */
2757+
seeds = ecalloc(count, sizeof(*seeds));
2758+
ZEND_HASH_FOREACH_STR_KEY(valid, zkey) {
2759+
seeds[idx++] = zend_string_copy(zkey);
2760+
} ZEND_HASH_FOREACH_END();
2761+
2762+
*nseeds = idx;
2763+
2764+
cleanup:
2765+
zend_hash_destroy(valid);
2766+
FREE_HASHTABLE(valid);
2767+
2768+
return seeds;
2769+
}
2770+
2771+
/* Validate cluster construction arguments and return a sanitized and validated
2772+
* array of seeds */
2773+
zend_string**
2774+
cluster_validate_args(double timeout, double read_timeout, HashTable *seeds,
2775+
uint32_t *nseeds, char **errstr)
2776+
{
2777+
zend_string **retval;
2778+
2779+
if (timeout < 0L || timeout > INT_MAX) {
2780+
if (errstr) *errstr = "Invalid timeout";
2781+
return NULL;
2782+
}
2783+
2784+
if (read_timeout < 0L || read_timeout > INT_MAX) {
2785+
if (errstr) *errstr = "Invalid read timeout";
2786+
return NULL;
2787+
}
2788+
2789+
retval = get_valid_seeds(seeds, nseeds);
2790+
if (retval == NULL && errstr)
2791+
*errstr = "No valid seeds detected";
2792+
2793+
return retval;
2794+
}
2795+
2796+
/* Helper function to compare to host:port seeds */
2797+
static int cluster_cmp_seeds(const void *a, const void *b) {
2798+
zend_string *za = *(zend_string **)a;
2799+
zend_string *zb = *(zend_string **)b;
2800+
return strcmp(ZSTR_VAL(za), ZSTR_VAL(zb));
2801+
}
2802+
2803+
static void cluster_swap_seeds(void *a, void *b) {
2804+
zend_string **za, **zb, *tmp;
2805+
2806+
za = a;
2807+
zb = b;
2808+
2809+
tmp = *za;
2810+
*za = *zb;
2811+
*zb = tmp;
2812+
}
2813+
2814+
/* Turn an array of cluster seeds into a string we can cache. If we get here we know
2815+
* we have at least one entry and that every entry is a string in the form host:port */
2816+
#define SLOT_CACHE_PREFIX "phpredis_slots:"
2817+
zend_string *cluster_hash_seeds(zend_string **seeds, uint32_t count) {
2818+
smart_str hash = {0};
2819+
size_t i;
2820+
2821+
/* Sort our seeds so any any array with identical seeds hashes to the same key
2822+
* regardless of what order the user gives them to us in. */
2823+
zend_sort(seeds, count, sizeof(*seeds), cluster_cmp_seeds, cluster_swap_seeds);
2824+
2825+
/* Global phpredis hash prefix */
2826+
smart_str_appendl(&hash, SLOT_CACHE_PREFIX, sizeof(SLOT_CACHE_PREFIX) - 1);
2827+
2828+
/* Construct our actual hash */
2829+
for (i = 0; i < count; i++) {
27562830
smart_str_appendc(&hash, '[');
2757-
smart_str_appendl(&hash, ZSTR_VAL(zstr), ZSTR_LEN(zstr));
2831+
smart_str_append_ex(&hash, seeds[i], 0);
27582832
smart_str_appendc(&hash, ']');
2759-
zend_string_release(zstr);
2760-
} ZEND_HASH_FOREACH_END();
2833+
}
27612834

2762-
/* Not strictly needed but null terminate anyway */
2835+
/* Null terminate */
27632836
smart_str_0(&hash);
27642837

2765-
/* smart_str is a zend_string internally */
2838+
/* Return the internal zend_string */
27662839
return hash.s;
27672840
}
27682841

2769-
2770-
#define SLOT_CACHING_ENABLED() (INI_INT("redis.clusters.cache_slots") == 1)
2771-
PHP_REDIS_API redisCachedCluster *cluster_cache_load(HashTable *ht_seeds) {
2842+
PHP_REDIS_API redisCachedCluster *cluster_cache_load(zend_string *hash) {
27722843
zend_resource *le;
2773-
zend_string *h;
2774-
2775-
/* Short circuit if we're not caching slots or if our seeds don't have any
2776-
* elements, since it doesn't make sense to cache an empty string */
2777-
if (!SLOT_CACHING_ENABLED() || zend_hash_num_elements(ht_seeds) == 0)
2778-
return NULL;
27792844

27802845
/* Look for cached slot information */
2781-
h = cluster_hash_seeds(ht_seeds);
2782-
le = zend_hash_find_ptr(&EG(persistent_list), h);
2783-
zend_string_release(h);
2846+
le = zend_hash_find_ptr(&EG(persistent_list), hash);
27842847

27852848
if (le != NULL) {
27862849
/* Sanity check on our list type */
@@ -2798,21 +2861,11 @@ PHP_REDIS_API redisCachedCluster *cluster_cache_load(HashTable *ht_seeds) {
27982861
}
27992862

28002863
/* Cache a cluster's slot information in persistent_list if it's enabled */
2801-
PHP_REDIS_API int cluster_cache_store(HashTable *ht_seeds, HashTable *nodes) {
2864+
PHP_REDIS_API int cluster_cache_store(zend_string *hash, HashTable *nodes) {
28022865
redisCachedCluster *cc;
2803-
zend_string *hash;
2804-
2805-
/* Short circuit if caching is disabled or there aren't any seeds */
2806-
if (!SLOT_CACHING_ENABLED()) {
2807-
return SUCCESS;
2808-
} else if (zend_hash_num_elements(ht_seeds) == 0) {
2809-
return FAILURE;
2810-
}
28112866

28122867
/* Construct our cache */
2813-
hash = cluster_hash_seeds(ht_seeds);
28142868
cc = cluster_cache_create(hash, nodes);
2815-
zend_string_release(hash);
28162869

28172870
/* Set up our resource */
28182871
#if PHP_VERSION_ID < 70300

0 commit comments

Comments
 (0)