Skip to content

Commit 24f7585

Browse files
Modify error handling for specific errors, and reathorize when reconnecting.
For certain error types (Redis LOADING the dataset, SYNC in progress/master down, and AUTH failures) it may be prudent to throw an exception rather than simply return false. In addition, this commit adds logic to reauthorize the connection in the event of a reconnect (for whatever reason). Addresses phpredis#515
1 parent c1f862c commit 24f7585

File tree

2 files changed

+135
-50
lines changed

2 files changed

+135
-50
lines changed

common.h

+8-1
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,21 @@ typedef enum _PUBSUB_TYPE {
6464
#define REDIS_SERIALIZER_IGBINARY 2
6565

6666
/* SCAN options */
67-
6867
#define REDIS_SCAN_NORETRY 0
6968
#define REDIS_SCAN_RETRY 1
7069

7170
/* GETBIT/SETBIT offset range limits */
7271
#define BITOP_MIN_OFFSET 0
7372
#define BITOP_MAX_OFFSET 4294967295
7473

74+
/* Specific error messages we want to throw against */
75+
#define REDIS_ERR_LOADING_MSG "LOADING Redis is loading the dataset in memory"
76+
#define REDIS_ERR_LOADING_KW "LOADING"
77+
#define REDIS_ERR_AUTH_MSG "NOAUTH Authentication required."
78+
#define REDIS_ERR_AUTH_KW "NOAUTH"
79+
#define REDIS_ERR_SYNC_MSG "MASTERDOWN Link with MASTER is down and slave-serve-stale-data is set to 'no'"
80+
#define REDIS_ERR_SYNC_KW "MASTERDOWN"
81+
7582
#define IF_MULTI() if(redis_sock->mode == MULTI)
7683
#define IF_MULTI_OR_ATOMIC() if(redis_sock->mode == MULTI || redis_sock->mode == ATOMIC)\
7784

library.c

+127-49
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,89 @@ extern zend_class_entry *redis_ce;
3939
extern zend_class_entry *redis_exception_ce;
4040
extern zend_class_entry *spl_ce_RuntimeException;
4141

42+
/* Helper to reselect the proper DB number when we reconnect */
43+
static int reselect_db(RedisSock *redis_sock TSRMLS_DC) {
44+
char *cmd, *response;
45+
int cmd_len, response_len;
46+
47+
cmd_len = redis_cmd_format_static(&cmd, "SELECT", "d", redis_sock->dbNumber);
48+
49+
if (redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) {
50+
efree(cmd);
51+
return -1;
52+
}
53+
54+
efree(cmd);
55+
56+
if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) {
57+
return -1;
58+
}
59+
60+
if (strncmp(response, "+OK", 3)) {
61+
efree(response);
62+
return -1;
63+
}
64+
65+
efree(response);
66+
return 0;
67+
}
68+
69+
/* Helper to resend AUTH <password> in the case of a reconnect */
70+
static int resend_auth(RedisSock *redis_sock TSRMLS_DC) {
71+
char *cmd, *response;
72+
int cmd_len, response_len;
73+
74+
cmd_len = redis_cmd_format_static(&cmd, "AUTH", "s", redis_sock->auth,
75+
strlen(redis_sock->auth));
76+
77+
if (redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_DC) < 0) {
78+
efree(cmd);
79+
return -1;
80+
}
81+
82+
efree(cmd);
83+
84+
response = redis_sock_read(redis_sock, &response_len TSRMLS_CC);
85+
if (response == NULL) {
86+
return -1;
87+
}
88+
89+
if (strncmp(response, "+OK", 3)) {
90+
efree(response);
91+
return -1;
92+
}
93+
94+
efree(response);
95+
return 0;
96+
}
97+
98+
/* Helper function that will throw an exception for a small number of ERR codes
99+
* returned by Redis. Typically we just return FALSE to the caller in the event
100+
* of an ERROR reply, but for the following error types:
101+
* 1) MASTERDOWN
102+
* 2) AUTH
103+
* 3) LOADING
104+
*/
105+
static void redis_error_throw(char *err, size_t err_len TSRMLS_DC) {
106+
/* Handle stale data error (slave syncing with master) */
107+
if (err_len == sizeof(REDIS_ERR_SYNC_MSG) - 1 &&
108+
!memcmp(err,REDIS_ERR_SYNC_KW,sizeof(REDIS_ERR_SYNC_KW)-1))
109+
{
110+
zend_throw_exception(redis_exception_ce,
111+
"SYNC with master in progress or master down!", 0 TSRMLS_CC);
112+
} else if (err_len == sizeof(REDIS_ERR_LOADING_MSG) - 1 &&
113+
!memcmp(err,REDIS_ERR_LOADING_KW,sizeof(REDIS_ERR_LOADING_KW)-1))
114+
{
115+
zend_throw_exception(redis_exception_ce,
116+
"Redis is LOADING the dataset", 0 TSRMLS_CC);
117+
} else if (err_len == sizeof(REDIS_ERR_AUTH_MSG) -1 &&
118+
!memcmp(err,REDIS_ERR_AUTH_KW,sizeof(REDIS_ERR_AUTH_KW)-1))
119+
{
120+
zend_throw_exception(redis_exception_ce,
121+
"Failed to AUTH connection", 0 TSRMLS_CC);
122+
}
123+
}
124+
42125
PHP_REDIS_API void redis_stream_close(RedisSock *redis_sock TSRMLS_DC) {
43126
if (!redis_sock->persistent) {
44127
php_stream_close(redis_sock->stream);
@@ -58,64 +141,60 @@ PHP_REDIS_API int redis_check_eof(RedisSock *redis_sock TSRMLS_DC)
58141

59142
eof = php_stream_eof(redis_sock->stream);
60143
for (; eof; count++) {
61-
if((MULTI == redis_sock->mode) || redis_sock->watching || count == 10) { /* too many failures */
62-
if(redis_sock->stream) { /* close stream if still here */
144+
/* Only try up to a certain point */
145+
if((MULTI == redis_sock->mode) || redis_sock->watching || count == 10) {
146+
if(redis_sock->stream) { /* close stream if still here */
63147
redis_stream_close(redis_sock TSRMLS_CC);
64148
redis_sock->stream = NULL;
65-
redis_sock->mode = ATOMIC;
149+
redis_sock->mode = ATOMIC;
66150
redis_sock->status = REDIS_SOCK_STATUS_FAILED;
67151
redis_sock->watching = 0;
68-
}
152+
}
153+
69154
zend_throw_exception(redis_exception_ce, "Connection lost", 0 TSRMLS_CC);
70-
return -1;
71-
}
72-
if(redis_sock->stream) { /* close existing stream before reconnecting */
155+
return -1;
156+
}
157+
158+
/* Close existing stream before reconnecting */
159+
if(redis_sock->stream) {
73160
redis_stream_close(redis_sock TSRMLS_CC);
74161
redis_sock->stream = NULL;
75-
redis_sock->mode = ATOMIC;
162+
redis_sock->mode = ATOMIC;
76163
redis_sock->watching = 0;
77-
}
78-
/* Wait for a while before trying to reconnect */
79-
if (redis_sock->retry_interval) {
80-
// Random factor to avoid having several (or many) concurrent connections trying to reconnect at the same time
81-
long retry_interval = (count ? redis_sock->retry_interval : (php_rand(TSRMLS_C) % redis_sock->retry_interval));
82-
usleep(retry_interval);
83-
}
164+
}
165+
166+
/* Wait for a while before trying to reconnect */
167+
if (redis_sock->retry_interval) {
168+
// Random factor to avoid having several (or many) concurrent connections trying to reconnect at the same time
169+
long retry_interval = (count ? redis_sock->retry_interval : (php_rand(TSRMLS_C) redis_sock->retry_interval));
170+
usleep(retry_interval);
171+
}
172+
84173
redis_sock_connect(redis_sock TSRMLS_CC); /* reconnect */
85174
if(redis_sock->stream) { /* check for EOF again. */
86175
eof = php_stream_eof(redis_sock->stream);
87176
}
88177
}
89178

90-
/* Reselect the DB. */
91-
if (count && redis_sock->dbNumber) {
92-
char *cmd, *response;
93-
int cmd_len, response_len;
94-
95-
cmd_len = redis_cmd_format_static(&cmd, "SELECT", "d", redis_sock->dbNumber);
96-
97-
if (redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) {
98-
efree(cmd);
179+
/* We've reconnected if we have a count */
180+
if (count) {
181+
/* If we're using a password, attempt a reauthorization */
182+
if (redis_sock->auth && resend_auth(redis_sock) != 0) {
99183
return -1;
100184
}
101-
efree(cmd);
102185

103-
if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) {
186+
/* If we're using a non zero db, reselect it */
187+
if (redis_sock->dbNumber && reselect_db(redis_sock) != 0) {
104188
return -1;
105189
}
106-
107-
if (strncmp(response, "+OK", 3)) {
108-
efree(response);
109-
return -1;
110-
}
111-
efree(response);
112190
}
113191

192+
/* Success */
114193
return 0;
115194
}
116195

117196

118-
PHP_REDIS_API int
197+
PHP_REDIS_API int
119198
redis_sock_read_scan_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
120199
REDIS_SCAN_TYPE type, long *iter)
121200
{
@@ -259,14 +338,14 @@ PHP_REDIS_API char *redis_sock_read(RedisSock *redis_sock, int *buf_len TSRMLS_D
259338

260339
switch(inbuf[0]) {
261340
case '-':
262-
err_len = strlen(inbuf+1) - 2;
341+
/* Set the last error */
342+
err_len = strlen(inbuf+1) - 2;
263343
redis_sock_set_err(redis_sock, inbuf+1, err_len);
264-
/* stale data */
265-
if(memcmp(inbuf + 1, "-ERR SYNC ", 10) == 0) {
266-
zend_throw_exception(redis_exception_ce, "SYNC with master in progress", 0 TSRMLS_CC);
267-
}
268-
return NULL;
269344

345+
/* Filter our ERROR through the few that should actually throw */
346+
redis_error_throw(inbuf + 1, err_len);
347+
348+
return NULL;
270349
case '$':
271350
*buf_len = atoi(inbuf + 1);
272351
resp = redis_sock_read_bulk_reply(redis_sock, *buf_len TSRMLS_CC);
@@ -432,7 +511,7 @@ redis_cmd_format_static(char **ret, char *keyword, char *format, ...) {
432511
/**
433512
* This command behave somehow like printf, except that strings need 2 arguments:
434513
* Their data and their size (strlen).
435-
* Supported formats are: %d, %i, %s, %l
514+
* Supported formats are:d, %i, %s, %l
436515
*/
437516
int
438517
redis_cmd_format(char **ret, char *format, ...) {
@@ -1090,7 +1169,7 @@ PHP_REDIS_API void redis_ping_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *
10901169
}
10911170

10921171
/* Response for DEBUG object which is a formatted single line reply */
1093-
PHP_REDIS_API void redis_debug_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
1172+
PHP_REDIS_API void redis_debug_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
10941173
zval *z_tab, void *ctx)
10951174
{
10961175
char *resp, *p, *p2, *p3, *p4;
@@ -1116,7 +1195,7 @@ PHP_REDIS_API void redis_debug_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock
11161195
while((p2 = strchr(p, ':'))!=NULL) {
11171196
/* Null terminate at the ':' */
11181197
*p2++ = '\0';
1119-
1198+
11201199
/* Null terminate at the space if we have one */
11211200
if((p3 = strchr(p2, ' '))!=NULL) {
11221201
*p3++ = '\0';
@@ -1138,7 +1217,7 @@ PHP_REDIS_API void redis_debug_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock
11381217
} else {
11391218
add_assoc_string(z_result, p, p2, 1);
11401219
}
1141-
1220+
11421221
p = p3;
11431222
}
11441223

@@ -1493,7 +1572,7 @@ redis_sock_read_multibulk_reply_loop(INTERNAL_FUNCTION_PARAMETERS, RedisSock *re
14931572
if(response != NULL) {
14941573
zval *z = NULL;
14951574
int can_unserialize = unwrap_key;
1496-
if(unserialize_even_only == UNSERIALIZE_ONLY_VALUES && numElems % 2 == 0)
1575+
if(unserialize_even_only == UNSERIALIZE_ONLY_VALUES && numElems 2 == 0)
14971576
can_unserialize = 0;
14981577

14991578
if(can_unserialize && redis_unserialize(redis_sock, response, response_len, &z TSRMLS_CC) == 1) {
@@ -1789,7 +1868,6 @@ redis_sock_gets(RedisSock *redis_sock, char *buf, int buf_size, size_t *line_siz
17891868
*line_size-=2;
17901869
buf[*line_size]='\0';
17911870

1792-
17931871
/* Success! */
17941872
return 0;
17951873
}
@@ -1839,11 +1917,11 @@ redis_read_variant_line(RedisSock *redis_sock, REDIS_REPLY_TYPE reply_type, zval
18391917
return -1;
18401918
}
18411919

1842-
/* If this is an error response, check if it is a SYNC error, and throw in that case */
1920+
/* If this is an error response, filter specific errors that should throw
1921+
* an exception, and set our error field in our RedisSock object. */
18431922
if(reply_type == TYPE_ERR) {
1844-
if(memcmp(inbuf, "ERR SYNC", 9) == 0) {
1845-
zend_throw_exception(redis_exception_ce, "SYNC with master in progress", 0 TSRMLS_CC);
1846-
}
1923+
/* Handle throwable errors */
1924+
redis_error_throw(inbuf, line_size TSRMLS_CC);
18471925

18481926
/* Set our last error */
18491927
redis_sock_set_err(redis_sock, inbuf, line_size);

0 commit comments

Comments
 (0)