Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 86391ec

Browse filesBrowse files
Add invalidateSlotCaches() function to invalidate any slot caches.
This commit adds a mechanism to invalidate every `RedisCluster` slot cache across all forked workers with one call to any of the workers. The invalidation itslef works by using a `uint64_t` generation counter which is allocated in shared memory on `MINIT` which is stored in every slot cache when it is created. When a user wants to invalidate any of the slot caches they can call `RedisCluster::invalidateSlotCaches()` which simply does an atomic increment on this global genertion value. When a slot cache is retreived from the persistent list, the internal generation is compared to the global shared memory generation and if they don't match, the cache is not used, which is the same as invalidting it as RedisCluster will write a new slot cache on object destruction. The feature is only compiled if `config.m4` determines that using `mmap` to allocate shared memory is available and works. Additionally, the feature must be explicitly enabled by setting the `redis.clusters.shared_slot_cache_invalidation`.
1 parent 47d3239 commit 86391ec
Copy full SHA for 86391ec

8 files changed

+165
-28
lines changed

‎cluster_library.c

Copy file name to clipboardExpand all lines: cluster_library.c
+53-5Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,18 @@
66
#include "crc16.h"
77
#include <zend_exceptions.h>
88

9+
#ifdef HAVE_CONFIG_H
10+
#include "config.h"
11+
#endif
12+
13+
#ifdef HAVE_REDIS_ATOMICS_MMAP
14+
#include <stdatomic.h>
15+
#include <sys/mman.h>
16+
17+
static _Atomic uint64_t *g_cluster_cache_gen;
18+
static pid_t g_cluster_cache_pid;
19+
#endif
20+
921
extern zend_class_entry *redis_cluster_exception_ce;
1022
int le_cluster_slot_cache;
1123

@@ -883,7 +895,7 @@ cluster_free(redisCluster *c, int free_ctx)
883895
if (free_ctx) efree(c);
884896
}
885897

886-
static zend_long cluster_slot_cache_expiry(void) {
898+
static zend_long cluster_cache_expiry(void) {
887899
zend_long expiry;
888900

889901
expiry = INI_INT("redis.clusters.slot_cache_expiry");
@@ -893,6 +905,38 @@ static zend_long cluster_slot_cache_expiry(void) {
893905
return time(NULL) + expiry;
894906
}
895907

908+
#ifdef HAVE_REDIS_ATOMICS_MMAP
909+
void cluster_cache_gen_init(void) {
910+
g_cluster_cache_pid = getpid();
911+
g_cluster_cache_gen = mmap(NULL, sizeof(uint64_t), PROT_READ | PROT_WRITE,
912+
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
913+
}
914+
915+
void cluster_cache_gen_free(void) {
916+
if (g_cluster_cache_gen && g_cluster_cache_pid == getpid()) {
917+
munmap(g_cluster_cache_gen, sizeof(uint64_t));
918+
g_cluster_cache_gen = NULL;
919+
}
920+
}
921+
922+
int cluster_cache_gen_invalidate(void) {
923+
if (g_cluster_cache_gen == NULL)
924+
return FAILURE;
925+
926+
atomic_fetch_add_explicit(g_cluster_cache_gen, 1, memory_order_relaxed);
927+
928+
return SUCCESS;
929+
}
930+
931+
static uint64_t cluster_cache_gen(void) {
932+
if (g_cluster_cache_gen == NULL)
933+
return 0;
934+
935+
return atomic_load(g_cluster_cache_gen);
936+
}
937+
938+
#endif
939+
896940
/* Create a cluster slot cache structure */
897941
PHP_REDIS_API
898942
redisCachedCluster *cluster_cache_create(zend_string *hash, HashTable *nodes) {
@@ -902,7 +946,8 @@ redisCachedCluster *cluster_cache_create(zend_string *hash, HashTable *nodes) {
902946

903947
cc = pecalloc(1, sizeof(*cc), 1);
904948
cc->hash = zend_string_dup(hash, 1);
905-
cc->expiry = cluster_slot_cache_expiry();
949+
cc->expiry = cluster_cache_expiry();
950+
cc->generation = cluster_cache_gen();
906951

907952
/* Copy nodes */
908953
cc->master = pecalloc(zend_hash_num_elements(nodes), sizeof(*cc->master), 1);
@@ -3119,7 +3164,6 @@ PHP_REDIS_API redisCachedCluster *cluster_cache_load(zend_string *hash) {
31193164

31203165
/* Look for cached slot information */
31213166
le = zend_hash_find_ptr(&EG(persistent_list), hash);
3122-
31233167
if (le == NULL)
31243168
return NULL;
31253169

@@ -3129,9 +3173,13 @@ PHP_REDIS_API redisCachedCluster *cluster_cache_load(zend_string *hash) {
31293173
}
31303174

31313175
cc = le->ptr;
3132-
if (cc->expiry != 0 && cc->expiry <= time(NULL)) {
3176+
/* Short circuit if it should be expired */
3177+
if (cc->expiry != 0 && cc->expiry <= time(NULL))
3178+
return NULL;
3179+
3180+
/* Short circuit if it has been globally invalidated */
3181+
if (cluster_cache_gen() != cc->generation)
31333182
return NULL;
3134-
}
31353183

31363184
return cc;
31373185
}

‎cluster_library.h

Copy file name to clipboardExpand all lines: cluster_library.h
+8-1Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ typedef struct redisCachedMaster {
157157
} redisCachedMaster;
158158

159159
typedef struct redisCachedCluster {
160-
// int rsrc_id; /* Zend resource ID */
160+
uint64_t generation; /* Shared invalidation generation */
161161
zend_string *hash; /* What we're cached by */
162162
redisCachedMaster *master; /* Array of masters */
163163
size_t count; /* Number of masters */
@@ -387,6 +387,13 @@ PHP_REDIS_API redisCachedCluster *cluster_cache_create(zend_string *hash, HashTa
387387
PHP_REDIS_API void cluster_cache_free(redisCachedCluster *rcc);
388388
PHP_REDIS_API void cluster_init_cache(redisCluster *c, redisCachedCluster *rcc);
389389

390+
/* Conditionally compiled shared slot cache invalidation functions */
391+
#ifdef HAVE_REDIS_ATOMICS_MMAP
392+
void cluster_cache_gen_init(void);
393+
void cluster_cache_gen_free(void);
394+
int cluster_cache_gen_invalidate(void);
395+
#endif
396+
390397
/* Functions to facilitate cluster slot caching */
391398

392399
PHP_REDIS_API char **cluster_sock_read_multibulk_reply(RedisSock *redis_sock, int *len);

‎config.m4

Copy file name to clipboardExpand all lines: config.m4
+19Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,25 @@ if test "$PHP_REDIS" != "no"; then
319319
fi
320320
fi
321321

322+
dnl Check if we can use C11 atomics and anonymous shared mmap
323+
AC_LINK_IFELSE(
324+
[AC_LANG_PROGRAM([[
325+
#include <stddef.h>
326+
#include <stdatomic.h>
327+
#include <sys/mman.h>
328+
]], [[
329+
static _Atomic int test = 0;
330+
void *ptr = mmap(NULL, 8, PROT_READ | PROT_WRITE,
331+
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
332+
if (ptr == (void *)-1) return 1;
333+
atomic_fetch_add(&test, 1);
334+
return 0;
335+
]])],
336+
[AC_DEFINE([HAVE_REDIS_ATOMICS_MMAP], [1],
337+
[Define if C11 atomics and MAP_SHARED|MAP_ANONYMOUS mmap are usable])],
338+
[]
339+
)
340+
322341
AC_CHECK_PROG([GIT], [git], [yes], [no])
323342
if test "$GIT" = "yes" && test -d "$srcdir/.git"; then
324343
AC_DEFINE_UNQUOTED(GIT_REVISION, ["$(git log -1 --format=%H)"], [ ])

‎redis.c

Copy file name to clipboardExpand all lines: redis.c
+19-1Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,11 @@ PHP_INI_BEGIN()
9696
/* redis cluster */
9797
PHP_INI_ENTRY("redis.clusters.cache_slots", "0", PHP_INI_ALL, NULL)
9898
PHP_INI_ENTRY("redis.clusters.slot_cache_expiry", "0", PHP_INI_ALL, NULL)
99+
100+
#ifdef HAVE_REDIS_ATOMICS_MMAP
101+
PHP_INI_ENTRY("redis.clusters.shared_slot_cache_invalidation", "0",
102+
PHP_INI_ALL, NULL)
103+
#endif
99104
PHP_INI_ENTRY("redis.clusters.auth", "", PHP_INI_ALL, NULL)
100105
PHP_INI_ENTRY("redis.clusters.persistent", "0", PHP_INI_ALL, NULL)
101106
PHP_INI_ENTRY("redis.clusters.read_timeout", "0", PHP_INI_ALL, NULL)
@@ -145,7 +150,7 @@ zend_module_entry redis_module_entry = {
145150
"redis",
146151
NULL,
147152
PHP_MINIT(redis),
148-
NULL,
153+
PHP_MSHUTDOWN(redis),
149154
NULL,
150155
NULL,
151156
PHP_MINFO(redis),
@@ -380,6 +385,11 @@ PHP_MINIT_FUNCTION(redis)
380385
"Redis cluster slot cache",
381386
module_number);
382387

388+
#ifdef HAVE_REDIS_ATOMICS_MMAP
389+
/* Initialize shared slot cache invalidation */
390+
cluster_cache_gen_init();
391+
#endif
392+
383393
/* RedisException class */
384394
redis_exception_ce = register_class_RedisException(spl_ce_RuntimeException);
385395

@@ -395,6 +405,14 @@ PHP_MINIT_FUNCTION(redis)
395405
return SUCCESS;
396406
}
397407

408+
PHP_MSHUTDOWN_FUNCTION(redis) {
409+
#ifdef HAVE_REDIS_ATOMICS_MMAP
410+
cluster_cache_gen_free();
411+
#endif
412+
413+
return SUCCESS;
414+
}
415+
398416
static const char *
399417
get_available_serializers(void)
400418
{

‎redis_cluster.c

Copy file name to clipboardExpand all lines: redis_cluster.c
+14-1Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1770,12 +1770,25 @@ static void redisClearNodeBytes(redisClusterNode *node) {
17701770

17711771
PHP_METHOD(RedisCluster, flushSlotCache) {
17721772
redisCluster *c = GET_CONTEXT();
1773-
1773+
17741774
ZEND_PARSE_PARAMETERS_NONE();
17751775

17761776
RETURN_BOOL(cluster_cache_clear(c) == SUCCESS);
17771777
}
17781778

1779+
#ifdef HAVE_REDIS_ATOMICS_MMAP
1780+
PHP_METHOD(RedisCluster, invalidateSlotCaches) {
1781+
ZEND_PARSE_PARAMETERS_NONE();
1782+
1783+
if (INI_INT("redis.clusters.shared_slot_cache_invalidation") == 0) {
1784+
php_error_docref(NULL, E_WARNING, "Shared slot cache invalidation disabled");
1785+
RETURN_FALSE;
1786+
}
1787+
1788+
RETURN_BOOL(cluster_cache_gen_invalidate() == SUCCESS);
1789+
}
1790+
#endif
1791+
17791792
PHP_METHOD(RedisCluster, gettransferredbytes) {
17801793
redisCluster *c = GET_CONTEXT();
17811794
zend_long rx = 0, tx = 0;

‎redis_cluster.stub.php

Copy file name to clipboardExpand all lines: redis_cluster.stub.php
+10Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,16 @@ public function _redir(): string|null;
9494
*/
9595
public function flushSlotCache(): bool;
9696

97+
#ifdef HAVE_REDIS_ATOMICS_MMAP
98+
/**
99+
* Invaalidate all slot caches for across all workers. Only available on
100+
* linux like systems with c11 atomics and shared memory allocation
101+
*
102+
* @return bool Whether we could invalidate any cache(es)
103+
*/
104+
public static function invalidateSlotCaches(): bool;
105+
#endif
106+
97107
/**
98108
* @see Redis::acl
99109
*/

‎redis_cluster_arginfo.h

Copy file name to clipboardExpand all lines: redis_cluster_arginfo.h
+21-11Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* This is a generated file, edit the .stub.php file instead.
2-
* Stub hash: db05ec768efb80715ac68fe0a1ab65d37de1d390 */
2+
* Stub hash: b1d3eb09f86ccffaa4cf0cc6885550e7d4b6da04 */
33

44
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_RedisCluster___construct, 0, 0, 1)
55
ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 1)
@@ -44,6 +44,11 @@ ZEND_END_ARG_INFO()
4444
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_RedisCluster_flushSlotCache, 0, 0, _IS_BOOL, 0)
4545
ZEND_END_ARG_INFO()
4646

47+
#if defined(HAVE_REDIS_ATOMICS_MMAP)
48+
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_RedisCluster_invalidateSlotCaches, 0, 0, _IS_BOOL, 0)
49+
ZEND_END_ARG_INFO()
50+
#endif
51+
4752
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_RedisCluster_acl, 0, 2, IS_MIXED, 0)
4853
ZEND_ARG_TYPE_MASK(0, key_or_address, MAY_BE_STRING|MAY_BE_ARRAY, NULL)
4954
ZEND_ARG_TYPE_INFO(0, subcmd, IS_STRING, 0)
@@ -1064,7 +1069,6 @@ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_RedisCluster_zdiff, 0,
10641069
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, options, IS_ARRAY, 1, "null")
10651070
ZEND_END_ARG_INFO()
10661071

1067-
10681072
ZEND_METHOD(RedisCluster, __construct);
10691073
ZEND_METHOD(RedisCluster, _compress);
10701074
ZEND_METHOD(RedisCluster, _uncompress);
@@ -1076,6 +1080,9 @@ ZEND_METHOD(RedisCluster, _prefix);
10761080
ZEND_METHOD(RedisCluster, _masters);
10771081
ZEND_METHOD(RedisCluster, _redir);
10781082
ZEND_METHOD(RedisCluster, flushSlotCache);
1083+
#if defined(HAVE_REDIS_ATOMICS_MMAP)
1084+
ZEND_METHOD(RedisCluster, invalidateSlotCaches);
1085+
#endif
10791086
ZEND_METHOD(RedisCluster, acl);
10801087
ZEND_METHOD(RedisCluster, append);
10811088
ZEND_METHOD(RedisCluster, bgrewriteaof);
@@ -1295,7 +1302,6 @@ ZEND_METHOD(RedisCluster, zdiffstore);
12951302
ZEND_METHOD(RedisCluster, zunion);
12961303
ZEND_METHOD(RedisCluster, zdiff);
12971304

1298-
12991305
static const zend_function_entry class_RedisCluster_methods[] = {
13001306
ZEND_ME(RedisCluster, __construct, arginfo_class_RedisCluster___construct, ZEND_ACC_PUBLIC)
13011307
ZEND_ME(RedisCluster, _compress, arginfo_class_RedisCluster__compress, ZEND_ACC_PUBLIC)
@@ -1308,6 +1314,9 @@ static const zend_function_entry class_RedisCluster_methods[] = {
13081314
ZEND_ME(RedisCluster, _masters, arginfo_class_RedisCluster__masters, ZEND_ACC_PUBLIC)
13091315
ZEND_ME(RedisCluster, _redir, arginfo_class_RedisCluster__redir, ZEND_ACC_PUBLIC)
13101316
ZEND_ME(RedisCluster, flushSlotCache, arginfo_class_RedisCluster_flushSlotCache, ZEND_ACC_PUBLIC)
1317+
#if defined(HAVE_REDIS_ATOMICS_MMAP)
1318+
ZEND_ME(RedisCluster, invalidateSlotCaches, arginfo_class_RedisCluster_invalidateSlotCaches, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
1319+
#endif
13111320
ZEND_ME(RedisCluster, acl, arginfo_class_RedisCluster_acl, ZEND_ACC_PUBLIC)
13121321
ZEND_ME(RedisCluster, append, arginfo_class_RedisCluster_append, ZEND_ACC_PUBLIC)
13131322
ZEND_ME(RedisCluster, bgrewriteaof, arginfo_class_RedisCluster_bgrewriteaof, ZEND_ACC_PUBLIC)
@@ -1529,17 +1538,16 @@ static const zend_function_entry class_RedisCluster_methods[] = {
15291538
ZEND_FE_END
15301539
};
15311540

1532-
1533-
static const zend_function_entry class_RedisClusterException_methods[] = {
1534-
ZEND_FE_END
1535-
};
1536-
15371541
static zend_class_entry *register_class_RedisCluster(void)
15381542
{
15391543
zend_class_entry ce, *class_entry;
15401544

15411545
INIT_CLASS_ENTRY(ce, "RedisCluster", class_RedisCluster_methods);
1546+
#if (PHP_VERSION_ID >= 80400)
1547+
class_entry = zend_register_internal_class_with_flags(&ce, NULL, 0);
1548+
#else
15421549
class_entry = zend_register_internal_class_ex(&ce, NULL);
1550+
#endif
15431551

15441552
zval const_OPT_SLAVE_FAILOVER_value;
15451553
ZVAL_LONG(&const_OPT_SLAVE_FAILOVER_value, REDIS_OPT_FAILOVER);
@@ -1570,13 +1578,11 @@ static zend_class_entry *register_class_RedisCluster(void)
15701578
zend_string *const_FAILOVER_DISTRIBUTE_SLAVES_name = zend_string_init_interned("FAILOVER_DISTRIBUTE_SLAVES", sizeof("FAILOVER_DISTRIBUTE_SLAVES") - 1, 1);
15711579
zend_declare_class_constant_ex(class_entry, const_FAILOVER_DISTRIBUTE_SLAVES_name, &const_FAILOVER_DISTRIBUTE_SLAVES_value, ZEND_ACC_PUBLIC, NULL);
15721580
zend_string_release(const_FAILOVER_DISTRIBUTE_SLAVES_name);
1573-
#if (PHP_VERSION_ID >= 80000)
15741581

15751582

15761583
zend_string *attribute_name_SensitiveParameter_func___construct_arg5_0 = zend_string_init_interned("SensitiveParameter", sizeof("SensitiveParameter") - 1, 1);
15771584
zend_add_parameter_attribute(zend_hash_str_find_ptr(&class_entry->function_table, "__construct", sizeof("__construct") - 1), 5, attribute_name_SensitiveParameter_func___construct_arg5_0, 0);
15781585
zend_string_release(attribute_name_SensitiveParameter_func___construct_arg5_0);
1579-
#endif
15801586

15811587
return class_entry;
15821588
}
@@ -1585,8 +1591,12 @@ static zend_class_entry *register_class_RedisClusterException(zend_class_entry *
15851591
{
15861592
zend_class_entry ce, *class_entry;
15871593

1588-
INIT_CLASS_ENTRY(ce, "RedisClusterException", class_RedisClusterException_methods);
1594+
INIT_CLASS_ENTRY(ce, "RedisClusterException", NULL);
1595+
#if (PHP_VERSION_ID >= 80400)
1596+
class_entry = zend_register_internal_class_with_flags(&ce, class_entry_RuntimeException, 0);
1597+
#else
15891598
class_entry = zend_register_internal_class_ex(&ce, class_entry_RuntimeException);
1599+
#endif
15901600

15911601
return class_entry;
15921602
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.