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

Skip to content

Commit b144743

Browse files
Rework the HMGET command to skip invalid keys
This relates to a previous hotfix for issue phpredis#379 where phpredis would time out if you sent an array of empty values. The reason it was timing out is that the argument count being sent wasn't reflecting any skipped items in the array (meaning redis was waiting for the rest of the command). I realized that the previous fix would still fail if you were to send some valid values, with invalid (null, empty string, etc) ones mixed in. Presently, we're just skipping invalid items in the array but there might be a case to issue a php_error_docref type warning when we encounter them, so the user can know that it happened. In addition, HMGET now uses a smart_str to build the command, which means the time it takes to build the key will scale in a linear fashion
1 parent 59a2886 commit b144743

File tree

2 files changed

+65
-63
lines changed

2 files changed

+65
-63
lines changed

redis.c

Lines changed: 62 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -4918,93 +4918,92 @@ PHP_METHOD(Redis, hIncrBy)
49184918

49194919
}
49204920

4921-
4921+
/* {{{ array Redis::hMget(string hash, array keys) */
49224922
PHP_METHOD(Redis, hMget) {
49234923
zval *object;
49244924
RedisSock *redis_sock;
4925-
char *key = NULL, *cmd;
4926-
int key_len, cmd_len, key_free;
4927-
zval *z_array;
4928-
zval **z_keys;
4929-
int nb_fields, i;
4930-
char *old_cmd = NULL;
4931-
4932-
zval **data;
4933-
HashTable *arr_hash;
4934-
HashPosition pointer;
4925+
char *key = NULL;
4926+
zval *z_array, **z_keys, **data;
4927+
int field_count, i, valid, key_len, key_free;
4928+
HashTable *ht_array;
4929+
HashPosition ptr;
4930+
smart_str cmd = {0};
49354931

4936-
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osa",
4937-
&object, redis_ce,
4938-
&key, &key_len, &z_array) == FAILURE) {
4932+
// Make sure we can grab our arguments properly
4933+
if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osa",
4934+
&object, redis_ce, &key, &key_len, &z_array)
4935+
== FAILURE)
4936+
{
49394937
RETURN_FALSE;
49404938
}
49414939

4942-
if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) {
4940+
// We'll need our socket
4941+
if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) {
49434942
RETURN_FALSE;
49444943
}
4945-
nb_fields = zend_hash_num_elements(Z_ARRVAL_P(z_array));
49464944

4947-
if( nb_fields == 0) {
4945+
// Grab member count and abort if we don't have any
4946+
if((field_count = zend_hash_num_elements(Z_ARRVAL_P(z_array))) == 0) {
49484947
RETURN_FALSE;
49494948
}
49504949

4951-
z_keys = ecalloc(nb_fields, sizeof(zval *));
4950+
// Prefix our key if we need to
4951+
key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC);
49524952

4953-
key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC);
4953+
// Allocate enough memory for the number of keys being requested
4954+
z_keys = ecalloc(field_count, sizeof(zval *));
49544955

4955-
cmd_len = redis_cmd_format(&cmd,
4956-
"*%d" _NL
4957-
"$5" _NL
4958-
"HMGET" _NL
4956+
// Grab our HashTable
4957+
ht_array = Z_ARRVAL_P(z_array);
49594958

4960-
"$%d" _NL /* key */
4961-
"%s" _NL
4962-
, nb_fields + 2
4963-
, key_len, key, key_len);
4964-
if(key_free) efree(key);
4959+
// Iterate through our keys, grabbing members that are valid
4960+
for(valid=0, zend_hash_internal_pointer_reset_ex(ht_array, &ptr);
4961+
zend_hash_get_current_data_ex(ht_array, (void**)&data, &ptr)==SUCCESS;
4962+
zend_hash_move_forward_ex(ht_array, &ptr))
4963+
{
4964+
// Make sure the data is a long or string, and if it's a string that
4965+
// it isn't empty. There is no reason to send empty length members.
4966+
if((Z_TYPE_PP(data) == IS_STRING && Z_STRLEN_PP(data)>0) ||
4967+
Z_TYPE_PP(data) == IS_LONG)
4968+
{
4969+
// This is a key we can ask for, copy it and set it in our array
4970+
MAKE_STD_ZVAL(z_keys[valid]);
4971+
*z_keys[valid] = **data;
4972+
zval_copy_ctor(z_keys[valid]);
4973+
convert_to_string(z_keys[valid]);
4974+
4975+
// Increment the number of valid keys we've encountered
4976+
valid++;
4977+
}
4978+
}
49654979

4966-
arr_hash = Z_ARRVAL_P(z_array);
4980+
// If we don't have any valid keys, we can abort here
4981+
if(valid == 0) {
4982+
if(key_free) efree(key);
4983+
efree(z_keys);
4984+
RETURN_FALSE;
4985+
}
49674986

4968-
for (i = 0, zend_hash_internal_pointer_reset_ex(arr_hash, &pointer);
4969-
zend_hash_get_current_data_ex(arr_hash, (void**) &data,
4970-
&pointer) == SUCCESS;
4971-
zend_hash_move_forward_ex(arr_hash, &pointer)) {
4987+
// Build command header. One extra argument for the hash key itself
4988+
redis_cmd_init_sstr(&cmd, valid+1, "HMGET", sizeof("HMGET")-1);
49724989

4973-
if (Z_TYPE_PP(data) == IS_LONG || Z_TYPE_PP(data) == IS_STRING) {
4990+
// Add the hash key
4991+
redis_cmd_append_sstr(&cmd, key, key_len);
49744992

4975-
old_cmd = cmd;
4976-
if (Z_TYPE_PP(data) == IS_LONG) {
4977-
cmd_len = redis_cmd_format(&cmd, "%s" "$%d" _NL "%d" _NL
4978-
, cmd, cmd_len
4979-
, integer_length(Z_LVAL_PP(data)), (int)Z_LVAL_PP(data));
4980-
} else if (Z_TYPE_PP(data) == IS_STRING) {
4981-
cmd_len = redis_cmd_format(&cmd, "%s" "$%d" _NL "%s" _NL
4982-
, cmd, cmd_len
4983-
, Z_STRLEN_PP(data), Z_STRVAL_PP(data), Z_STRLEN_PP(data));
4984-
}
4985-
efree(old_cmd);
4986-
/* save context */
4987-
MAKE_STD_ZVAL(z_keys[i]);
4988-
*z_keys[i] = **data;
4989-
zval_copy_ctor(z_keys[i]);
4990-
convert_to_string(z_keys[i]);
4993+
// Free key memory if it was prefixed
4994+
if(key_free) efree(key);
49914995

4992-
i++;
4993-
}
4996+
// Iterate our keys, appending them as arguments
4997+
for(i=0;i<valid;i++) {
4998+
redis_cmd_append_sstr(&cmd, Z_STRVAL_P(z_keys[i]), Z_STRLEN_P(z_keys[i]));
49944999
}
49955000

4996-
// This is a failure if none of the keys were valid
4997-
if(i == 0) {
4998-
efree(cmd);
4999-
efree(z_keys);
5000-
RETURN_FALSE;
5001+
// Kick off our request
5002+
REDIS_PROCESS_REQUEST(redis_sock, cmd.c, cmd.len);
5003+
IF_ATOMIC() {
5004+
redis_sock_read_multibulk_reply_assoc(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, z_keys);
50015005
}
5002-
5003-
REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len);
5004-
IF_ATOMIC() {
5005-
redis_sock_read_multibulk_reply_assoc(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, z_keys);
5006-
}
5007-
REDIS_PROCESS_RESPONSE_CLOSURE(redis_sock_read_multibulk_reply_assoc, z_keys);
5006+
REDIS_PROCESS_RESPONSE_CLOSURE(redis_sock_read_multibulk_reply_assoc, z_keys);
50085007
}
50095008

50105009
PHP_METHOD(Redis, hMset)

tests/TestRedis.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2285,6 +2285,9 @@ public function testHashes() {
22852285
// Test with an array populated with things we can't use as keys
22862286
$this->assertTrue($this->redis->hmget('h', Array(false,NULL,false)) === FALSE);
22872287

2288+
// Test with some invalid keys mixed in (which should just be ignored)
2289+
$this->assertTrue(array('x'=>'123','y'=>'456','z'=>'abc') === $this->redis->hMget('h',Array('x',null,'y','','z',false)));
2290+
22882291
// hmget/hmset with numeric fields
22892292
$this->redis->del('h');
22902293
$this->assertTrue(TRUE === $this->redis->hMset('h', array(123 => 'x', 'y' => 456)));

0 commit comments

Comments
 (0)