@@ -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-
952926static 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