summaryrefslogtreecommitdiff
path: root/src/dict.c
diff options
context:
space:
mode:
authorHuang Zhw <huang_zhw@126.com>2022-11-30 17:56:36 +0800
committerGitHub <noreply@github.com>2022-11-30 11:56:36 +0200
commitc81813148b71cd4be686402ef69f628f67dbb8c4 (patch)
tree54a5f6bc0701c97db66cb1cebe85fecfa3181429 /src/dict.c
parent7dfd7b9197bbe216912049eebecbda3f1684925e (diff)
downloadredis-c81813148b71cd4be686402ef69f628f67dbb8c4.tar.gz
Add a special notification unlink available only for modules (#9406)
Add a new module event `RedisModule_Event_Key`, this event is fired when a key is removed from the keyspace. The event includes an open key that can be used for reading the key before it is removed. Modules can also extract the key-name, and use RM_Open or RM_Call to access key from within that event, but shouldn't modify anything from within this event. The following sub events are available: - `REDISMODULE_SUBEVENT_KEY_DELETED` - `REDISMODULE_SUBEVENT_KEY_EXPIRED` - `REDISMODULE_SUBEVENT_KEY_EVICTED` - `REDISMODULE_SUBEVENT_KEY_OVERWRITE` The data pointer can be casted to a RedisModuleKeyInfo structure with the following fields: ``` RedisModuleKey *key; // Opened Key ``` ### internals * We also add two dict functions: `dictTwoPhaseUnlinkFind` finds an element from the table, also get the plink of the entry. The entry is returned if the element is found. The user should later call `dictTwoPhaseUnlinkFree` with it in order to unlink and release it. Otherwise if the key is not found, NULL is returned. These two functions should be used in pair. `dictTwoPhaseUnlinkFind` pauses rehash and `dictTwoPhaseUnlinkFree` resumes rehash. * We change `dbOverwrite` to `dbReplaceValue` which just replaces the value of the key and doesn't fire any events. The "overwrite" part (which emits events) is just when called from `setKey`, the other places that called dbOverwrite were ones that just update the value in-place (INCR*, SPOP, and dbUnshareStringValue). This should not have any real impact since `moduleNotifyKeyUnlink` and `signalDeletedKeyAsReady` wouldn't have mattered in these cases anyway (i.e. module keys and stream keys didn't have direct calls to dbOverwrite) * since we allow doing RM_OpenKey from withing these callbacks, we temporarily disable lazy expiry. * We also temporarily disable lazy expiry when we are in unlink/unlink2 callback and keyspace notification callback. * Move special definitions to the top of redismodule.h This is needed to resolve compilation errors with RedisModuleKeyInfoV1 that carries a RedisModuleKey member. Co-authored-by: Oran Agra <oran@redislabs.com>
Diffstat (limited to 'src/dict.c')
-rw-r--r--src/dict.c50
1 files changed, 50 insertions, 0 deletions
diff --git a/src/dict.c b/src/dict.c
index 9ed461d62..7fecb2daa 100644
--- a/src/dict.c
+++ b/src/dict.c
@@ -541,6 +541,56 @@ void *dictFetchValue(dict *d, const void *key) {
return he ? dictGetVal(he) : NULL;
}
+/* Find an element from the table, also get the plink of the entry. The entry
+ * is returned if the element is found, and the user should later call
+ * `dictTwoPhaseUnlinkFree` with it in order to unlink and release it. Otherwise if
+ * the key is not found, NULL is returned. These two functions should be used in pair.
+ * `dictTwoPhaseUnlinkFind` pauses rehash and `dictTwoPhaseUnlinkFree` resumes rehash.
+ *
+ * We can use like this:
+ *
+ * dictEntry *de = dictTwoPhaseUnlinkFind(db->dict,key->ptr,&plink, &table);
+ * // Do something, but we can't modify the dict
+ * dictTwoPhaseUnlinkFree(db->dict,de,plink,table); // We don't need to lookup again
+ *
+ * If we want to find an entry before delete this entry, this an optimization to avoid
+ * dictFind followed by dictDelete. i.e. the first API is a find, and it gives some info
+ * to the second one to avoid repeating the lookup
+ */
+dictEntry *dictTwoPhaseUnlinkFind(dict *d, const void *key, dictEntry ***plink, int *table_index) {
+ uint64_t h, idx, table;
+
+ if (dictSize(d) == 0) return NULL; /* dict is empty */
+ if (dictIsRehashing(d)) _dictRehashStep(d);
+ h = dictHashKey(d, key);
+
+ for (table = 0; table <= 1; table++) {
+ idx = h & DICTHT_SIZE_MASK(d->ht_size_exp[table]);
+ dictEntry **ref = &d->ht_table[table][idx];
+ while(*ref) {
+ if (key==(*ref)->key || dictCompareKeys(d, key, (*ref)->key)) {
+ *table_index = table;
+ *plink = ref;
+ dictPauseRehashing(d);
+ return *ref;
+ }
+ ref = &(*ref)->next;
+ }
+ if (!dictIsRehashing(d)) return NULL;
+ }
+ return NULL;
+}
+
+void dictTwoPhaseUnlinkFree(dict *d, dictEntry *he, dictEntry **plink, int table_index) {
+ if (he == NULL) return;
+ d->ht_used[table_index]--;
+ *plink = he->next;
+ dictFreeKey(d, he);
+ dictFreeVal(d, he);
+ zfree(he);
+ dictResumeRehashing(d);
+}
+
/* A fingerprint is a 64 bit number that represents the state of the dictionary
* at a given time, it's just a few dict properties xored together.
* When an unsafe iterator is initialized, we get the dict fingerprint, and check