Skip to content

Commit 25a1593

Browse files
Merge branch 'develop' of github.com:nicolasff/phpredis into develop
2 parents 22ebd71 + b2a57c1 commit 25a1593

12 files changed

+344
-25
lines changed

README.markdown

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2888,6 +2888,34 @@ $redis->script('exists', $script1, [$script2, $script3, ...]);
28882888
* SCRIPT KILL will return true if a script was able to be killed and false if not
28892889
* SCRIPT EXISTS will return an array with TRUE or FALSE for each passed script
28902890

2891+
### client
2892+
-----
2893+
_**Description**_: Issue the CLIENT command with various arguments.
2894+
2895+
The Redis CLIENT command can be used in four ways.
2896+
* CLIENT LIST
2897+
* CLIENT GETNAME
2898+
* CLIENT SETNAME [name]
2899+
* CLIENT KILL [ip:port]
2900+
2901+
##### *Usage*
2902+
~~~
2903+
$redis->client('list'); // Get a list of clients
2904+
$redis->client('getname'); // Get the name of the current connection
2905+
$redis->client('setname', 'somename'); // Set the name of the current connection
2906+
$redis->client('kill', <ip:port>); // Kill the process at ip:port
2907+
~~~
2908+
2909+
##### *Return value*
2910+
This will vary depending on which client command was executed.
2911+
2912+
* CLIENT LIST will return an array of arrays with client information.
2913+
* CLIENT GETNAME will return the client name or false if none has been set
2914+
* CLIENT SETNAME will return true if it can be set and false if not
2915+
* CLIENT KILL will return true if the client can be killed, and false if not
2916+
2917+
Note: phpredis will attempt to reconnect so you can actually kill your own connection
2918+
but may not notice losing it!
28912919
### getLastError
28922920
-----
28932921
_**Description**_: The last error message (if any)

common.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ typedef struct {
159159
short port;
160160
double timeout;
161161
double read_timeout;
162+
long retry_interval;
162163
int failed;
163164
int status;
164165
int persistent;

library.c

Lines changed: 133 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,9 @@ PHPAPI int redis_check_eof(RedisSock *redis_sock TSRMLS_DC)
3838
int eof;
3939
int count = 0;
4040

41-
if (!redis_sock->stream)
41+
if (!redis_sock->stream) {
4242
return -1;
43+
}
4344

4445
eof = php_stream_eof(redis_sock->stream);
4546
for (; eof; count++) {
@@ -60,6 +61,12 @@ PHPAPI int redis_check_eof(RedisSock *redis_sock TSRMLS_DC)
6061
redis_sock->mode = ATOMIC;
6162
redis_sock->watching = 0;
6263
}
64+
// Wait for a while before trying to reconnect
65+
if (redis_sock->retry_interval) {
66+
// Random factor to avoid having several (or many) concurrent connections trying to reconnect at the same time
67+
long retry_interval = (count ? redis_sock->retry_interval : (random() % redis_sock->retry_interval));
68+
usleep(retry_interval);
69+
}
6370
redis_sock_connect(redis_sock TSRMLS_CC); /* reconnect */
6471
if(redis_sock->stream) { /* check for EOF again. */
6572
eof = php_stream_eof(redis_sock->stream);
@@ -608,6 +615,128 @@ PHPAPI void redis_info_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_s
608615
}
609616
}
610617

618+
/*
619+
* Specialized handling of the CLIENT LIST output so it comes out in a simple way for PHP userland code
620+
* to handle.
621+
*/
622+
PHPAPI void redis_client_list_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab) {
623+
char *resp;
624+
int resp_len;
625+
zval *z_result, *z_sub_result;
626+
627+
// Make sure we can read a response from Redis
628+
if((resp = redis_sock_read(redis_sock, &resp_len TSRMLS_CC)) == NULL) {
629+
RETURN_FALSE;
630+
}
631+
632+
// Allocate memory for our response
633+
MAKE_STD_ZVAL(z_result);
634+
array_init(z_result);
635+
636+
// Allocate memory for one user (there should be at least one, namely us!)
637+
ALLOC_INIT_ZVAL(z_sub_result);
638+
array_init(z_sub_result);
639+
640+
// Pointers for parsing
641+
char *p = resp, *lpos = resp, *kpos = NULL, *vpos = NULL, *p2, *key, *value;
642+
643+
// Key length, done flag
644+
int klen, done = 0, is_numeric;
645+
646+
// While we've got more to parse
647+
while(!done) {
648+
// What character are we on
649+
switch(*p) {
650+
/* We're done */
651+
case '\0':
652+
done = 1;
653+
break;
654+
/* \n, ' ' mean we can pull a k/v pair */
655+
case '\n':
656+
case ' ':
657+
// Grab our value
658+
vpos = lpos;
659+
660+
// There is some communication error or Redis bug if we don't
661+
// have a key and value, but check anyway.
662+
if(kpos && vpos) {
663+
// Allocate, copy in our key
664+
key = emalloc(klen + 1);
665+
strncpy(key, kpos, klen);
666+
key[klen] = 0;
667+
668+
// Allocate, copy in our value
669+
value = emalloc(p-lpos+1);
670+
strncpy(value,lpos,p-lpos+1);
671+
value[p-lpos]=0;
672+
673+
// Treat numbers as numbers, strings as strings
674+
is_numeric = 1;
675+
for(p2 = value; *p; ++p) {
676+
if(*p < '0' || *p > '9') {
677+
is_numeric = 0;
678+
break;
679+
}
680+
}
681+
682+
// Add as a long or string, depending
683+
if(is_numeric == 1) {
684+
add_assoc_long(z_sub_result, key, atol(value));
685+
efree(value);
686+
} else {
687+
add_assoc_string(z_sub_result, key, value, 0);
688+
}
689+
690+
// If we hit a '\n', then we can add this user to our list
691+
if(*p == '\n') {
692+
// Add our user
693+
add_next_index_zval(z_result, z_sub_result);
694+
695+
// If we have another user, make another one
696+
if(*(p+1) != '\0') {
697+
ALLOC_INIT_ZVAL(z_sub_result);
698+
array_init(z_sub_result);
699+
}
700+
}
701+
702+
// Free our key
703+
efree(key);
704+
} else {
705+
// Something is wrong
706+
efree(resp);
707+
RETURN_FALSE;
708+
}
709+
710+
// Move forward
711+
lpos = p + 1;
712+
713+
break;
714+
/* We can pull the key and null terminate at our sep */
715+
case '=':
716+
// Key, key length
717+
kpos = lpos;
718+
klen = p - lpos;
719+
720+
// Move forward
721+
lpos = p + 1;
722+
723+
break;
724+
}
725+
726+
// Increment
727+
p++;
728+
}
729+
730+
// Free our respoonse
731+
efree(resp);
732+
733+
IF_MULTI_OR_PIPELINE() {
734+
add_next_index_zval(z_tab, z_result);
735+
} else {
736+
RETVAL_ZVAL(z_result, 0, 1);
737+
}
738+
}
739+
611740
PHPAPI void redis_boolean_response_impl(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx, SuccessCallback success_callback) {
612741

613742
char *response;
@@ -840,7 +969,8 @@ PHPAPI void redis_ping_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_s
840969
* redis_sock_create
841970
*/
842971
PHPAPI RedisSock* redis_sock_create(char *host, int host_len, unsigned short port,
843-
double timeout, int persistent, char *persistent_id)
972+
double timeout, int persistent, char *persistent_id,
973+
long retry_interval)
844974
{
845975
RedisSock *redis_sock;
846976

@@ -850,7 +980,7 @@ PHPAPI RedisSock* redis_sock_create(char *host, int host_len, unsigned short por
850980
redis_sock->status = REDIS_SOCK_STATUS_DISCONNECTED;
851981
redis_sock->watching = 0;
852982
redis_sock->dbNumber = 0;
853-
983+
redis_sock->retry_interval = retry_interval * 1000;
854984
redis_sock->persistent = persistent;
855985

856986
if(persistent_id) {

library.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ PHPAPI void redis_string_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis
2020
PHPAPI void redis_ping_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
2121
PHPAPI void redis_info_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
2222
PHPAPI void redis_type_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
23-
PHPAPI RedisSock* redis_sock_create(char *host, int host_len, unsigned short port, double timeout, int persistent, char *persistent_id);
23+
PHPAPI RedisSock* redis_sock_create(char *host, int host_len, unsigned short port, double timeout, int persistent, char *persistent_id, long retry_interval);
2424
PHPAPI int redis_sock_connect(RedisSock *redis_sock TSRMLS_DC);
2525
PHPAPI int redis_sock_server_open(RedisSock *redis_sock, int force_connect TSRMLS_DC);
2626
PHPAPI int redis_sock_disconnect(RedisSock *redis_sock TSRMLS_DC);
@@ -59,6 +59,7 @@ PHPAPI int redis_read_variant_bulk(RedisSock *redis_sock, int size, zval **z_ret
5959
PHPAPI int redis_read_multibulk_recursive(RedisSock *redis_sock, int elements, zval **z_ret TSRMLS_DC);
6060
PHPAPI int redis_read_variant_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab);
6161

62+
PHPAPI void redis_client_list_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab);
6263

6364
#if ZEND_MODULE_API_NO >= 20100000
6465
#define REDIS_DOUBLE_TO_STRING(dbl_str, dbl_len, dbl) \

php_redis.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,8 @@ PHP_METHOD(Redis, setOption);
181181

182182
PHP_METHOD(Redis, config);
183183

184+
PHP_METHOD(Redis, client);
185+
184186
#ifdef PHP_WIN32
185187
#define PHP_REDIS_API __declspec(dllexport)
186188
#else

redis.c

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ static zend_function_entry redis_functions[] = {
227227
PHP_ME(Redis, _prefix, NULL, ZEND_ACC_PUBLIC)
228228
PHP_ME(Redis, _unserialize, NULL, ZEND_ACC_PUBLIC)
229229

230+
PHP_ME(Redis, client, NULL, ZEND_ACC_PUBLIC)
230231

231232
/* options */
232233
PHP_ME(Redis, getOption, NULL, ZEND_ACC_PUBLIC)
@@ -524,7 +525,7 @@ PHP_METHOD(Redis,__destruct) {
524525
}
525526
}
526527

527-
/* {{{ proto boolean Redis::connect(string host, int port [, double timeout])
528+
/* {{{ proto boolean Redis::connect(string host, int port [, double timeout [, long retry_interval]])
528529
*/
529530
PHP_METHOD(Redis, connect)
530531
{
@@ -560,6 +561,7 @@ PHPAPI int redis_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent) {
560561
int host_len, id;
561562
char *host = NULL;
562563
long port = -1;
564+
long retry_interval = 0;
563565

564566
char *persistent_id = NULL;
565567
int persistent_id_len = -1;
@@ -572,9 +574,10 @@ PHPAPI int redis_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent) {
572574
persistent = 0;
573575
#endif
574576

575-
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|lds",
577+
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|ldsl",
576578
&object, redis_ce, &host, &host_len, &port,
577-
&timeout, &persistent_id, &persistent_id_len) == FAILURE) {
579+
&timeout, &persistent_id, &persistent_id_len,
580+
&retry_interval) == FAILURE) {
578581
return FAILURE;
579582
}
580583

@@ -583,6 +586,11 @@ PHPAPI int redis_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent) {
583586
return FAILURE;
584587
}
585588

589+
if (retry_interval < 0L || retry_interval > INT_MAX) {
590+
zend_throw_exception(redis_exception_ce, "Invalid retry interval", 0 TSRMLS_CC);
591+
return FAILURE;
592+
}
593+
586594
if(port == -1 && host_len && host[0] != '/') { /* not unix socket, set to default value */
587595
port = 6379;
588596
}
@@ -599,7 +607,7 @@ PHPAPI int redis_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent) {
599607
zend_clear_exception(TSRMLS_C); /* clear exception triggered by non-existent socket during connect(). */
600608
}
601609

602-
redis_sock = redis_sock_create(host, host_len, port, timeout, persistent, persistent_id);
610+
redis_sock = redis_sock_create(host, host_len, port, timeout, persistent, persistent_id, retry_interval);
603611

604612
if (redis_sock_server_open(redis_sock, 1 TSRMLS_CC) < 0) {
605613
redis_free_socket(redis_sock);
@@ -6304,5 +6312,56 @@ PHP_METHOD(Redis, time) {
63046312
REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply_raw);
63056313
}
63066314

6307-
/* vim: set tabstop=4 softtabstop=4 noexpandtab shiftwidth=4: */
6315+
/*
6316+
* $redis->client('list');
6317+
* $redis->client('kill', <ip:port>);
6318+
* $redis->client('setname', <name>);
6319+
* $redis->client('getname');
6320+
*/
6321+
PHP_METHOD(Redis, client) {
6322+
zval *object;
6323+
RedisSock *redis_sock;
6324+
char *cmd, *opt=NULL, *arg=NULL;
6325+
int cmd_len, opt_len, arg_len;
6326+
6327+
// Parse our method parameters
6328+
if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|s",
6329+
&object, redis_ce, &opt, &opt_len, &arg, &arg_len) == FAILURE)
6330+
{
6331+
RETURN_FALSE;
6332+
}
6333+
6334+
// Grab our socket
6335+
if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) {
6336+
RETURN_FALSE;
6337+
}
6338+
6339+
// Build our CLIENT command
6340+
if(ZEND_NUM_ARGS() == 2) {
6341+
cmd_len = redis_cmd_format_static(&cmd, "CLIENT", "ss", opt, opt_len,
6342+
arg, arg_len);
6343+
} else {
6344+
cmd_len = redis_cmd_format_static(&cmd, "CLIENT", "s", opt, opt_len);
6345+
}
6346+
6347+
// Handle CLIENT LIST specifically
6348+
int is_list = !strncasecmp(opt, "list", 4);
6349+
6350+
// Execute our queue command
6351+
REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len);
6352+
6353+
// We handle CLIENT LIST with a custom response function
6354+
if(!strncasecmp(opt, "list", 4)) {
6355+
IF_ATOMIC() {
6356+
redis_client_list_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU,redis_sock,NULL);
6357+
}
6358+
REDIS_PROCESS_RESPONSE(redis_client_list_reply);
6359+
} else {
6360+
IF_ATOMIC() {
6361+
redis_read_variant_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU,redis_sock,NULL);
6362+
}
6363+
REDIS_PROCESS_RESPONSE(redis_read_variant_reply);
6364+
}
6365+
}
63086366

6367+
/* vim: set tabstop=4 softtabstops=4 noexpandtab shiftwidth=4: */

0 commit comments

Comments
 (0)