Skip to content

Commit e8ad58b

Browse files
Merge branch 'errorthrow' into develop
2 parents da51f06 + 346e4d4 commit e8ad58b

File tree

2 files changed

+134
-49
lines changed

2 files changed

+134
-49
lines changed

common.h

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

6767
/* SCAN options */
68-
6968
#define REDIS_SCAN_NORETRY 0
7069
#define REDIS_SCAN_RETRY 1
7170

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

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

library.c

+126-48
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

@@ -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)