redict/tests/modules/test_lazyfree.c
Viktor Söderqvist acf3495eb8
Sort out the mess around writable replicas and lookupKeyRead/Write (#9572)
Writable replicas now no longer use the values of expired keys. Expired keys are
deleted when lookupKeyWrite() is used, even on a writable replica. Previously,
writable replicas could use the value of an expired key in write commands such
as INCR, SUNIONSTORE, etc..

This commit also sorts out the mess around the functions lookupKeyRead() and
lookupKeyWrite() so they now indicate what we intend to do with the key and
are not affected by the command calling them.

Multi-key commands like SUNIONSTORE, ZUNIONSTORE, COPY and SORT with the
store option now use lookupKeyRead() for the keys they're reading from (which will
not allow reading from logically expired keys).

This commit also fixes a bug where PFCOUNT could return a value of an
expired key.

Test modules commands have their readonly and write flags updated to correctly
reflect their lookups for reading or writing. Modules are not required to
correctly reflect this in their command flags, but this change is made for
consistency since the tests serve as usage examples.

Fixes #6842. Fixes #7475.
2021-11-28 11:26:28 +02:00

197 lines
6.1 KiB
C

/* This module emulates a linked list for lazyfree testing of modules, which
is a simplified version of 'hellotype.c'
*/
#include "redismodule.h"
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <stdint.h>
static RedisModuleType *LazyFreeLinkType;
struct LazyFreeLinkNode {
int64_t value;
struct LazyFreeLinkNode *next;
};
struct LazyFreeLinkObject {
struct LazyFreeLinkNode *head;
size_t len; /* Number of elements added. */
};
struct LazyFreeLinkObject *createLazyFreeLinkObject(void) {
struct LazyFreeLinkObject *o;
o = RedisModule_Alloc(sizeof(*o));
o->head = NULL;
o->len = 0;
return o;
}
void LazyFreeLinkInsert(struct LazyFreeLinkObject *o, int64_t ele) {
struct LazyFreeLinkNode *next = o->head, *newnode, *prev = NULL;
while(next && next->value < ele) {
prev = next;
next = next->next;
}
newnode = RedisModule_Alloc(sizeof(*newnode));
newnode->value = ele;
newnode->next = next;
if (prev) {
prev->next = newnode;
} else {
o->head = newnode;
}
o->len++;
}
void LazyFreeLinkReleaseObject(struct LazyFreeLinkObject *o) {
struct LazyFreeLinkNode *cur, *next;
cur = o->head;
while(cur) {
next = cur->next;
RedisModule_Free(cur);
cur = next;
}
RedisModule_Free(o);
}
/* LAZYFREELINK.INSERT key value */
int LazyFreeLinkInsert_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
RedisModule_AutoMemory(ctx); /* Use automatic memory management. */
if (argc != 3) return RedisModule_WrongArity(ctx);
RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1],
REDISMODULE_READ|REDISMODULE_WRITE);
int type = RedisModule_KeyType(key);
if (type != REDISMODULE_KEYTYPE_EMPTY &&
RedisModule_ModuleTypeGetType(key) != LazyFreeLinkType)
{
return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE);
}
long long value;
if ((RedisModule_StringToLongLong(argv[2],&value) != REDISMODULE_OK)) {
return RedisModule_ReplyWithError(ctx,"ERR invalid value: must be a signed 64 bit integer");
}
struct LazyFreeLinkObject *hto;
if (type == REDISMODULE_KEYTYPE_EMPTY) {
hto = createLazyFreeLinkObject();
RedisModule_ModuleTypeSetValue(key,LazyFreeLinkType,hto);
} else {
hto = RedisModule_ModuleTypeGetValue(key);
}
LazyFreeLinkInsert(hto,value);
RedisModule_SignalKeyAsReady(ctx,argv[1]);
RedisModule_ReplyWithLongLong(ctx,hto->len);
RedisModule_ReplicateVerbatim(ctx);
return REDISMODULE_OK;
}
/* LAZYFREELINK.LEN key */
int LazyFreeLinkLen_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
RedisModule_AutoMemory(ctx); /* Use automatic memory management. */
if (argc != 2) return RedisModule_WrongArity(ctx);
RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1],
REDISMODULE_READ);
int type = RedisModule_KeyType(key);
if (type != REDISMODULE_KEYTYPE_EMPTY &&
RedisModule_ModuleTypeGetType(key) != LazyFreeLinkType)
{
return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE);
}
struct LazyFreeLinkObject *hto = RedisModule_ModuleTypeGetValue(key);
RedisModule_ReplyWithLongLong(ctx,hto ? hto->len : 0);
return REDISMODULE_OK;
}
void *LazyFreeLinkRdbLoad(RedisModuleIO *rdb, int encver) {
if (encver != 0) {
return NULL;
}
uint64_t elements = RedisModule_LoadUnsigned(rdb);
struct LazyFreeLinkObject *hto = createLazyFreeLinkObject();
while(elements--) {
int64_t ele = RedisModule_LoadSigned(rdb);
LazyFreeLinkInsert(hto,ele);
}
return hto;
}
void LazyFreeLinkRdbSave(RedisModuleIO *rdb, void *value) {
struct LazyFreeLinkObject *hto = value;
struct LazyFreeLinkNode *node = hto->head;
RedisModule_SaveUnsigned(rdb,hto->len);
while(node) {
RedisModule_SaveSigned(rdb,node->value);
node = node->next;
}
}
void LazyFreeLinkAofRewrite(RedisModuleIO *aof, RedisModuleString *key, void *value) {
struct LazyFreeLinkObject *hto = value;
struct LazyFreeLinkNode *node = hto->head;
while(node) {
RedisModule_EmitAOF(aof,"LAZYFREELINK.INSERT","sl",key,node->value);
node = node->next;
}
}
void LazyFreeLinkFree(void *value) {
LazyFreeLinkReleaseObject(value);
}
size_t LazyFreeLinkFreeEffort(RedisModuleString *key, const void *value) {
REDISMODULE_NOT_USED(key);
const struct LazyFreeLinkObject *hto = value;
return hto->len;
}
void LazyFreeLinkUnlink(RedisModuleString *key, const void *value) {
REDISMODULE_NOT_USED(key);
REDISMODULE_NOT_USED(value);
/* Here you can know which key and value is about to be freed. */
}
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
if (RedisModule_Init(ctx,"lazyfreetest",1,REDISMODULE_APIVER_1)
== REDISMODULE_ERR) return REDISMODULE_ERR;
/* We only allow our module to be loaded when the redis core version is greater than the version of my module */
if (RedisModule_GetTypeMethodVersion() < REDISMODULE_TYPE_METHOD_VERSION) {
return REDISMODULE_ERR;
}
RedisModuleTypeMethods tm = {
.version = REDISMODULE_TYPE_METHOD_VERSION,
.rdb_load = LazyFreeLinkRdbLoad,
.rdb_save = LazyFreeLinkRdbSave,
.aof_rewrite = LazyFreeLinkAofRewrite,
.free = LazyFreeLinkFree,
.free_effort = LazyFreeLinkFreeEffort,
.unlink = LazyFreeLinkUnlink,
};
LazyFreeLinkType = RedisModule_CreateDataType(ctx,"test_lazy",0,&tm);
if (LazyFreeLinkType == NULL) return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"lazyfreelink.insert",
LazyFreeLinkInsert_RedisCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"lazyfreelink.len",
LazyFreeLinkLen_RedisCommand,"readonly",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
return REDISMODULE_OK;
}