Skip to content

Commit 5d54091

Browse files
committed
Add support for exponential backoff on retry
1 parent c3ab4a2 commit 5d54091

File tree

10 files changed

+264
-46
lines changed

10 files changed

+264
-46
lines changed

backoff.c

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
#include "common.h"
2+
3+
#include <ext/standard/php_rand.h>
4+
#include <ext/standard/php_mt_rand.h>
5+
6+
#include "backoff.h"
7+
8+
static zend_ulong random_range(zend_ulong min, zend_ulong max) {
9+
if (max < min) {
10+
zend_ulong new_min = max;
11+
max = min;
12+
min = new_min;
13+
}
14+
15+
return php_mt_rand_range(min, max);
16+
}
17+
18+
static zend_ulong redis_default_backoff(struct RedisBackoff *self, unsigned int retry_index) {
19+
zend_ulong backoff = retry_index ? self->base : random_range(0, self->base);
20+
return MIN(self->cap, backoff);
21+
}
22+
23+
static zend_ulong redis_constant_backoff(struct RedisBackoff *self, unsigned int retry_index) {
24+
zend_ulong backoff = self->base;
25+
return MIN(self->cap, backoff);
26+
}
27+
28+
static zend_ulong redis_uniform_backoff(struct RedisBackoff *self, unsigned int retry_index) {
29+
zend_ulong backoff = random_range(0, self->base);
30+
return MIN(self->cap, backoff);
31+
}
32+
33+
static zend_ulong redis_exponential_backoff(struct RedisBackoff *self, unsigned int retry_index) {
34+
zend_ulong pow = MIN(retry_index, 10);
35+
zend_ulong backoff = self->base * (1 << pow);
36+
return MIN(self->cap, backoff);
37+
}
38+
39+
static zend_ulong redis_full_jitter_backoff(struct RedisBackoff *self, unsigned int retry_index) {
40+
zend_ulong pow = MIN(retry_index, 10);
41+
zend_ulong backoff = self->base * (1 << pow);
42+
zend_ulong cap = MIN(self->cap, backoff);
43+
return random_range(0, self->cap);
44+
}
45+
46+
static zend_ulong redis_equal_jitter_backoff(struct RedisBackoff *self, unsigned int retry_index) {
47+
zend_ulong pow = MIN(retry_index, 10);
48+
zend_ulong backoff = self->base * (1 << pow);
49+
zend_ulong temp = MIN(self->cap, backoff);
50+
return temp / 2 + random_range(0, temp) / 2;
51+
}
52+
53+
static zend_ulong redis_decorrelated_jitter_backoff(struct RedisBackoff *self, unsigned int retry_index) {
54+
self->previous_backoff = random_range(self->base, self->previous_backoff * 3);
55+
return MIN(self->cap, self->previous_backoff);
56+
}
57+
58+
typedef zend_ulong (*redis_backoff_algorithm)(struct RedisBackoff *self, unsigned int retry_index);
59+
60+
static redis_backoff_algorithm redis_backoff_algorithms[REDIS_BACKOFF_ALGORITHMS] = {
61+
redis_default_backoff,
62+
redis_decorrelated_jitter_backoff,
63+
redis_full_jitter_backoff,
64+
redis_equal_jitter_backoff,
65+
redis_exponential_backoff,
66+
redis_uniform_backoff,
67+
redis_constant_backoff,
68+
};
69+
70+
void redis_initialize_backoff(struct RedisBackoff *self, unsigned long retry_interval) {
71+
self->algorithm = 0; // default backoff
72+
self->base = retry_interval;
73+
self->cap = retry_interval;
74+
self->previous_backoff = 0;
75+
}
76+
77+
void redis_backoff_reset(struct RedisBackoff *self) {
78+
self->previous_backoff = 0;
79+
}
80+
81+
zend_ulong redis_backoff_compute(struct RedisBackoff *self, unsigned int retry_index) {
82+
return redis_backoff_algorithms[self->algorithm](self, retry_index);
83+
}

backoff.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#ifndef REDIS_BACKOFF_H
2+
#define REDIS_BACKOFF_H
3+
4+
/* {{{ struct RedisBackoff */
5+
struct RedisBackoff {
6+
unsigned int algorithm; /* index of algorithm function, returns backoff in microseconds*/
7+
zend_ulong base; /* base backoff in microseconds */
8+
zend_ulong cap; /* max backoff in microseconds */
9+
zend_ulong previous_backoff; /* previous backoff in microseconds */
10+
};
11+
/* }}} */
12+
13+
void redis_initialize_backoff(struct RedisBackoff *self, unsigned long retry_interval);
14+
void redis_backoff_reset(struct RedisBackoff *self);
15+
zend_ulong redis_backoff_compute(struct RedisBackoff *self, unsigned int retry_index);
16+
17+
#endif

common.h

Lines changed: 53 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
#define NULL ((void *) 0)
2222
#endif
2323

24+
#include "backoff.h"
25+
2426
typedef enum {
2527
REDIS_SOCK_STATUS_FAILED = -1,
2628
REDIS_SOCK_STATUS_DISCONNECTED,
@@ -83,6 +85,10 @@ typedef enum _PUBSUB_TYPE {
8385
#define REDIS_OPT_REPLY_LITERAL 8
8486
#define REDIS_OPT_COMPRESSION_LEVEL 9
8587
#define REDIS_OPT_NULL_MBULK_AS_NULL 10
88+
#define REDIS_OPT_MAX_RETRIES 11
89+
#define REDIS_OPT_BACKOFF_ALGORITHM 12
90+
#define REDIS_OPT_BACKOFF_BASE 13
91+
#define REDIS_OPT_BACKOFF_CAP 14
8692

8793
/* cluster options */
8894
#define REDIS_FAILOVER_NONE 0
@@ -109,6 +115,16 @@ typedef enum {
109115
#define REDIS_SCAN_PREFIX 2
110116
#define REDIS_SCAN_NOPREFIX 3
111117

118+
/* BACKOFF_ALGORITHM options */
119+
#define REDIS_BACKOFF_ALGORITHMS 7
120+
#define REDIS_BACKOFF_ALGORITHM_DEFAULT 0
121+
#define REDIS_BACKOFF_ALGORITHM_DECORRELATED_JITTER 1
122+
#define REDIS_BACKOFF_ALGORITHM_FULL_JITTER 2
123+
#define REDIS_BACKOFF_ALGORITHM_EQUAL_JITTER 3
124+
#define REDIS_BACKOFF_ALGORITHM_EXPONENTIAL 4
125+
#define REDIS_BACKOFF_ALGORITHM_UNIFORM 5
126+
#define REDIS_BACKOFF_ALGORITHM_CONSTANT 6
127+
112128
/* GETBIT/SETBIT offset range limits */
113129
#define BITOP_MIN_OFFSET 0
114130
#define BITOP_MAX_OFFSET 4294967295U
@@ -258,41 +274,43 @@ typedef enum {
258274

259275
/* {{{ struct RedisSock */
260276
typedef struct {
261-
php_stream *stream;
262-
php_stream_context *stream_ctx;
263-
zend_string *host;
264-
int port;
265-
zend_string *user;
266-
zend_string *pass;
267-
double timeout;
268-
double read_timeout;
269-
long retry_interval;
270-
redis_sock_status status;
271-
int persistent;
272-
int watching;
273-
zend_string *persistent_id;
274-
275-
redis_serializer serializer;
276-
int compression;
277-
int compression_level;
278-
long dbNumber;
279-
280-
zend_string *prefix;
281-
282-
short mode;
283-
struct fold_item *head;
284-
struct fold_item *current;
285-
286-
zend_string *pipeline_cmd;
287-
288-
zend_string *err;
289-
290-
int scan;
291-
292-
int readonly;
293-
int reply_literal;
294-
int null_mbulk_as_null;
295-
int tcp_keepalive;
277+
php_stream *stream;
278+
php_stream_context *stream_ctx;
279+
zend_string *host;
280+
int port;
281+
zend_string *user;
282+
zend_string *pass;
283+
double timeout;
284+
double read_timeout;
285+
long retry_interval;
286+
int max_retries;
287+
struct RedisBackoff backoff;
288+
redis_sock_status status;
289+
int persistent;
290+
int watching;
291+
zend_string *persistent_id;
292+
293+
redis_serializer serializer;
294+
int compression;
295+
int compression_level;
296+
long dbNumber;
297+
298+
zend_string *prefix;
299+
300+
short mode;
301+
struct fold_item *head;
302+
struct fold_item *current;
303+
304+
zend_string *pipeline_cmd;
305+
306+
zend_string *err;
307+
308+
int scan;
309+
310+
int readonly;
311+
int reply_literal;
312+
int null_mbulk_as_null;
313+
int tcp_keepalive;
296314
} RedisSock;
297315
/* }}} */
298316

config.m4

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,5 +323,5 @@ if test "$PHP_REDIS" != "no"; then
323323
fi
324324

325325
PHP_SUBST(REDIS_SHARED_LIBADD)
326-
PHP_NEW_EXTENSION(redis, redis.c redis_commands.c library.c redis_session.c redis_array.c redis_array_impl.c redis_cluster.c cluster_library.c redis_sentinel.c sentinel_library.c $lzf_sources, $ext_shared)
326+
PHP_NEW_EXTENSION(redis, redis.c redis_commands.c library.c redis_session.c redis_array.c redis_array_impl.c redis_cluster.c cluster_library.c redis_sentinel.c sentinel_library.c backoff.c $lzf_sources, $ext_shared)
327327
fi

config.w32

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ ARG_ENABLE("redis-session", "whether to enable sessions", "yes");
55
ARG_ENABLE("redis-igbinary", "whether to enable igbinary serializer support", "no");
66

77
if (PHP_REDIS != "no") {
8-
var sources = "redis.c redis_commands.c library.c redis_session.c redis_array.c redis_array_impl.c redis_cluster.c cluster_library.c redis_sentinel.c sentinel_library.c";
8+
var sources = "redis.c redis_commands.c library.c redis_session.c redis_array.c redis_array_impl.c redis_cluster.c cluster_library.c redis_sentinel.c sentinel_library.c backoff.c";
99
if (PHP_REDIS_SESSION != "no") {
1010
ADD_EXTENSION_DEP("redis", "session");
1111
ADD_FLAG("CFLAGS_REDIS", ' /D PHP_SESSION=1 ');

library.c

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ redis_error_throw(RedisSock *redis_sock)
301301
PHP_REDIS_API int
302302
redis_check_eof(RedisSock *redis_sock, int no_throw)
303303
{
304-
int count;
304+
unsigned int retry_index;
305305
char *errmsg;
306306

307307
if (!redis_sock || !redis_sock->stream || redis_sock->status == REDIS_SOCK_STATUS_FAILED) {
@@ -333,18 +333,17 @@ redis_check_eof(RedisSock *redis_sock, int no_throw)
333333
errmsg = "Connection lost and socket is in MULTI/watching mode";
334334
} else {
335335
errmsg = "Connection lost";
336-
/* TODO: configurable max retry count */
337-
for (count = 0; count < 10; ++count) {
336+
redis_backoff_reset(&redis_sock->backoff);
337+
for (retry_index = 0; retry_index < redis_sock->max_retries; ++retry_index) {
338338
/* close existing stream before reconnecting */
339339
if (redis_sock->stream) {
340340
redis_sock_disconnect(redis_sock, 1);
341341
}
342-
// Wait for a while before trying to reconnect
343-
if (redis_sock->retry_interval) {
344-
// Random factor to avoid having several (or many) concurrent connections trying to reconnect at the same time
345-
long retry_interval = (count ? redis_sock->retry_interval : (php_rand() % redis_sock->retry_interval));
346-
usleep(retry_interval);
347-
}
342+
/* Sleep based on our backoff algorithm */
343+
zend_ulong delay = redis_backoff_compute(&redis_sock->backoff, retry_index);
344+
if (delay != 0)
345+
usleep(delay);
346+
348347
/* reconnect */
349348
if (redis_sock_connect(redis_sock) == 0) {
350349
/* check for EOF again. */
@@ -2150,6 +2149,8 @@ redis_sock_create(char *host, int host_len, int port,
21502149
redis_sock->host = zend_string_init(host, host_len, 0);
21512150
redis_sock->status = REDIS_SOCK_STATUS_DISCONNECTED;
21522151
redis_sock->retry_interval = retry_interval * 1000;
2152+
redis_sock->max_retries = 10;
2153+
redis_initialize_backoff(&redis_sock->backoff, retry_interval);
21532154
redis_sock->persistent = persistent;
21542155

21552156
if (persistent && persistent_id != NULL) {

package.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
8888
<file role='doc' name='arrays.markdown'/>
8989
<file role='doc' name='cluster.markdown'/>
9090
<file role='doc' name='sentinel.markdown'/>
91+
<file role='src' name='backoff.c'/>
92+
<file role='src' name='backoff.h'/>
9193
<file role='src' name='cluster_library.c'/>
9294
<file role='src' name='cluster_library.h'/>
9395
<file role='src' name='common.h'/>

redis.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,22 @@ static void add_class_constants(zend_class_entry *ce, int is_cluster) {
775775
zend_declare_class_constant_stringl(ce, "LEFT", 4, "left", 4);
776776
zend_declare_class_constant_stringl(ce, "RIGHT", 5, "right", 5);
777777
}
778+
779+
/* retry/backoff options*/
780+
zend_declare_class_constant_long(ce, ZEND_STRL("OPT_MAX_RETRIES"), REDIS_OPT_MAX_RETRIES);
781+
782+
zend_declare_class_constant_long(ce, ZEND_STRL("OPT_BACKOFF_ALGORITHM"), REDIS_OPT_BACKOFF_ALGORITHM);
783+
zend_declare_class_constant_long(ce, ZEND_STRL("BACKOFF_ALGORITHM_DEFAULT"), REDIS_BACKOFF_ALGORITHM_DEFAULT);
784+
zend_declare_class_constant_long(ce, ZEND_STRL("BACKOFF_ALGORITHM_CONSTANT"), REDIS_BACKOFF_ALGORITHM_CONSTANT);
785+
zend_declare_class_constant_long(ce, ZEND_STRL("BACKOFF_ALGORITHM_UNIFORM"), REDIS_BACKOFF_ALGORITHM_UNIFORM);
786+
zend_declare_class_constant_long(ce, ZEND_STRL("BACKOFF_ALGORITHM_EXPONENTIAL"), REDIS_BACKOFF_ALGORITHM_EXPONENTIAL);
787+
zend_declare_class_constant_long(ce, ZEND_STRL("BACKOFF_ALGORITHM_FULL_JITTER"), REDIS_BACKOFF_ALGORITHM_FULL_JITTER);
788+
zend_declare_class_constant_long(ce, ZEND_STRL("BACKOFF_ALGORITHM_EQUAL_JITTER"), REDIS_BACKOFF_ALGORITHM_EQUAL_JITTER);
789+
zend_declare_class_constant_long(ce, ZEND_STRL("BACKOFF_ALGORITHM_DECORRELATED_JITTER"), REDIS_BACKOFF_ALGORITHM_DECORRELATED_JITTER);
790+
791+
zend_declare_class_constant_long(ce, ZEND_STRL("OPT_BACKOFF_BASE"), REDIS_OPT_BACKOFF_BASE);
792+
793+
zend_declare_class_constant_long(ce, ZEND_STRL("OPT_BACKOFF_CAP"), REDIS_OPT_BACKOFF_CAP);
778794
}
779795

780796
static ZEND_RSRC_DTOR_FUNC(redis_connections_pool_dtor)

redis_commands.c

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4308,6 +4308,14 @@ void redis_getoption_handler(INTERNAL_FUNCTION_PARAMETERS,
43084308
RETURN_LONG(redis_sock->null_mbulk_as_null);
43094309
case REDIS_OPT_FAILOVER:
43104310
RETURN_LONG(c->failover);
4311+
case REDIS_OPT_MAX_RETRIES:
4312+
RETURN_LONG(redis_sock->max_retries);
4313+
case REDIS_OPT_BACKOFF_ALGORITHM:
4314+
RETURN_LONG(redis_sock->backoff.algorithm);
4315+
case REDIS_OPT_BACKOFF_BASE:
4316+
RETURN_LONG(redis_sock->backoff.base / 1000);
4317+
case REDIS_OPT_BACKOFF_CAP:
4318+
RETURN_LONG(redis_sock->backoff.cap / 1000);
43114319
default:
43124320
RETURN_FALSE;
43134321
}
@@ -4441,6 +4449,35 @@ void redis_setoption_handler(INTERNAL_FUNCTION_PARAMETERS,
44414449
RETURN_TRUE;
44424450
}
44434451
break;
4452+
case REDIS_OPT_MAX_RETRIES:
4453+
val_long = zval_get_long(val);
4454+
if(val_long >= 0) {
4455+
redis_sock->max_retries = val_long;
4456+
RETURN_TRUE;
4457+
}
4458+
break;
4459+
case REDIS_OPT_BACKOFF_ALGORITHM:
4460+
val_long = zval_get_long(val);
4461+
if(val_long >= 0 &&
4462+
val_long < REDIS_BACKOFF_ALGORITHMS) {
4463+
redis_sock->backoff.algorithm = val_long;
4464+
RETURN_TRUE;
4465+
}
4466+
break;
4467+
case REDIS_OPT_BACKOFF_BASE:
4468+
val_long = zval_get_long(val);
4469+
if(val_long >= 0) {
4470+
redis_sock->backoff.base = val_long * 1000;
4471+
RETURN_TRUE;
4472+
}
4473+
break;
4474+
case REDIS_OPT_BACKOFF_CAP:
4475+
val_long = zval_get_long(val);
4476+
if(val_long >= 0) {
4477+
redis_sock->backoff.cap = val_long * 1000;
4478+
RETURN_TRUE;
4479+
}
4480+
break;
44444481
EMPTY_SWITCH_DEFAULT_CASE()
44454482
}
44464483
RETURN_FALSE;

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy