Merge branch 'unstable' into rm_get_server_info
This commit is contained in:
commit
64c2508ee3
39
redis.conf
39
redis.conf
@ -813,11 +813,11 @@ replica-priority 100
|
|||||||
# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
|
# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
|
||||||
# is reached. You can select among five behaviors:
|
# is reached. You can select among five behaviors:
|
||||||
#
|
#
|
||||||
# volatile-lru -> Evict using approximated LRU among the keys with an expire set.
|
# volatile-lru -> Evict using approximated LRU, only keys with an expire set.
|
||||||
# allkeys-lru -> Evict any key using approximated LRU.
|
# allkeys-lru -> Evict any key using approximated LRU.
|
||||||
# volatile-lfu -> Evict using approximated LFU among the keys with an expire set.
|
# volatile-lfu -> Evict using approximated LFU, only keys with an expire set.
|
||||||
# allkeys-lfu -> Evict any key using approximated LFU.
|
# allkeys-lfu -> Evict any key using approximated LFU.
|
||||||
# volatile-random -> Remove a random key among the ones with an expire set.
|
# volatile-random -> Remove a random key having an expire set.
|
||||||
# allkeys-random -> Remove a random key, any key.
|
# allkeys-random -> Remove a random key, any key.
|
||||||
# volatile-ttl -> Remove the key with the nearest expire time (minor TTL)
|
# volatile-ttl -> Remove the key with the nearest expire time (minor TTL)
|
||||||
# noeviction -> Don't evict anything, just return an error on write operations.
|
# noeviction -> Don't evict anything, just return an error on write operations.
|
||||||
@ -872,6 +872,23 @@ replica-priority 100
|
|||||||
#
|
#
|
||||||
# replica-ignore-maxmemory yes
|
# replica-ignore-maxmemory yes
|
||||||
|
|
||||||
|
# Redis reclaims expired keys in two ways: upon access when those keys are
|
||||||
|
# found to be expired, and also in background, in what is called the
|
||||||
|
# "active expire key". The key space is slowly and interactively scanned
|
||||||
|
# looking for expired keys to reclaim, so that it is possible to free memory
|
||||||
|
# of keys that are expired and will never be accessed again in a short time.
|
||||||
|
#
|
||||||
|
# The default effort of the expire cycle will try to avoid having more than
|
||||||
|
# ten percent of expired keys still in memory, and will try to avoid consuming
|
||||||
|
# more than 25% of total memory and to add latency to the system. However
|
||||||
|
# it is possible to increase the expire "effort" that is normally set to
|
||||||
|
# "1", to a greater value, up to the value "10". At its maximum value the
|
||||||
|
# system will use more CPU, longer cycles (and technically may introduce
|
||||||
|
# more latency), and will tollerate less already expired keys still present
|
||||||
|
# in the system. It's a tradeoff betweeen memory, CPU and latecy.
|
||||||
|
#
|
||||||
|
# active-expire-effort 1
|
||||||
|
|
||||||
############################# LAZY FREEING ####################################
|
############################# LAZY FREEING ####################################
|
||||||
|
|
||||||
# Redis has two primitives to delete keys. One is called DEL and is a blocking
|
# Redis has two primitives to delete keys. One is called DEL and is a blocking
|
||||||
@ -1606,10 +1623,6 @@ rdb-save-incremental-fsync yes
|
|||||||
|
|
||||||
########################### ACTIVE DEFRAGMENTATION #######################
|
########################### ACTIVE DEFRAGMENTATION #######################
|
||||||
#
|
#
|
||||||
# WARNING THIS FEATURE IS EXPERIMENTAL. However it was stress tested
|
|
||||||
# even in production and manually tested by multiple engineers for some
|
|
||||||
# time.
|
|
||||||
#
|
|
||||||
# What is active defragmentation?
|
# What is active defragmentation?
|
||||||
# -------------------------------
|
# -------------------------------
|
||||||
#
|
#
|
||||||
@ -1649,7 +1662,7 @@ rdb-save-incremental-fsync yes
|
|||||||
# a good idea to leave the defaults untouched.
|
# a good idea to leave the defaults untouched.
|
||||||
|
|
||||||
# Enabled active defragmentation
|
# Enabled active defragmentation
|
||||||
# activedefrag yes
|
# activedefrag no
|
||||||
|
|
||||||
# Minimum amount of fragmentation waste to start active defrag
|
# Minimum amount of fragmentation waste to start active defrag
|
||||||
# active-defrag-ignore-bytes 100mb
|
# active-defrag-ignore-bytes 100mb
|
||||||
@ -1660,11 +1673,13 @@ rdb-save-incremental-fsync yes
|
|||||||
# Maximum percentage of fragmentation at which we use maximum effort
|
# Maximum percentage of fragmentation at which we use maximum effort
|
||||||
# active-defrag-threshold-upper 100
|
# active-defrag-threshold-upper 100
|
||||||
|
|
||||||
# Minimal effort for defrag in CPU percentage
|
# Minimal effort for defrag in CPU percentage, to be used when the lower
|
||||||
# active-defrag-cycle-min 5
|
# threshold is reached
|
||||||
|
# active-defrag-cycle-min 1
|
||||||
|
|
||||||
# Maximal effort for defrag in CPU percentage
|
# Maximal effort for defrag in CPU percentage, to be used when the upper
|
||||||
# active-defrag-cycle-max 75
|
# threshold is reached
|
||||||
|
# active-defrag-cycle-max 25
|
||||||
|
|
||||||
# Maximum number of set/hash/zset/list fields that will be processed from
|
# Maximum number of set/hash/zset/list fields that will be processed from
|
||||||
# the main dictionary scan
|
# the main dictionary scan
|
||||||
|
@ -21,4 +21,7 @@ $TCLSH tests/test_helper.tcl \
|
|||||||
--single unit/moduleapi/propagate \
|
--single unit/moduleapi/propagate \
|
||||||
--single unit/moduleapi/hooks \
|
--single unit/moduleapi/hooks \
|
||||||
--single unit/moduleapi/misc \
|
--single unit/moduleapi/misc \
|
||||||
|
--single unit/moduleapi/blockonkeys \
|
||||||
|
--single unit/moduleapi/scan \
|
||||||
|
--single unit/moduleapi/datatype \
|
||||||
"${@}"
|
"${@}"
|
||||||
|
@ -66,7 +66,7 @@ typedef struct list {
|
|||||||
#define listSetMatchMethod(l,m) ((l)->match = (m))
|
#define listSetMatchMethod(l,m) ((l)->match = (m))
|
||||||
|
|
||||||
#define listGetDupMethod(l) ((l)->dup)
|
#define listGetDupMethod(l) ((l)->dup)
|
||||||
#define listGetFree(l) ((l)->free)
|
#define listGetFreeMethod(l) ((l)->free)
|
||||||
#define listGetMatchMethod(l) ((l)->match)
|
#define listGetMatchMethod(l) ((l)->match)
|
||||||
|
|
||||||
/* Prototypes */
|
/* Prototypes */
|
||||||
|
16
src/aof.c
16
src/aof.c
@ -731,7 +731,7 @@ int loadAppendOnlyFile(char *filename) {
|
|||||||
server.aof_state = AOF_OFF;
|
server.aof_state = AOF_OFF;
|
||||||
|
|
||||||
fakeClient = createAOFClient();
|
fakeClient = createAOFClient();
|
||||||
startLoadingFile(fp, filename);
|
startLoadingFile(fp, filename, RDBFLAGS_AOF_PREAMBLE);
|
||||||
|
|
||||||
/* Check if this AOF file has an RDB preamble. In that case we need to
|
/* Check if this AOF file has an RDB preamble. In that case we need to
|
||||||
* load the RDB file and later continue loading the AOF tail. */
|
* load the RDB file and later continue loading the AOF tail. */
|
||||||
@ -746,7 +746,7 @@ int loadAppendOnlyFile(char *filename) {
|
|||||||
serverLog(LL_NOTICE,"Reading RDB preamble from AOF file...");
|
serverLog(LL_NOTICE,"Reading RDB preamble from AOF file...");
|
||||||
if (fseek(fp,0,SEEK_SET) == -1) goto readerr;
|
if (fseek(fp,0,SEEK_SET) == -1) goto readerr;
|
||||||
rioInitWithFile(&rdb,fp);
|
rioInitWithFile(&rdb,fp);
|
||||||
if (rdbLoadRio(&rdb,NULL,1) != C_OK) {
|
if (rdbLoadRio(&rdb,RDBFLAGS_AOF_PREAMBLE,NULL) != C_OK) {
|
||||||
serverLog(LL_WARNING,"Error reading the RDB preamble of the AOF file, AOF loading aborted");
|
serverLog(LL_WARNING,"Error reading the RDB preamble of the AOF file, AOF loading aborted");
|
||||||
goto readerr;
|
goto readerr;
|
||||||
} else {
|
} else {
|
||||||
@ -767,6 +767,7 @@ int loadAppendOnlyFile(char *filename) {
|
|||||||
if (!(loops++ % 1000)) {
|
if (!(loops++ % 1000)) {
|
||||||
loadingProgress(ftello(fp));
|
loadingProgress(ftello(fp));
|
||||||
processEventsWhileBlocked();
|
processEventsWhileBlocked();
|
||||||
|
processModuleLoadingProgressEvent(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fgets(buf,sizeof(buf),fp) == NULL) {
|
if (fgets(buf,sizeof(buf),fp) == NULL) {
|
||||||
@ -859,7 +860,7 @@ loaded_ok: /* DB loaded, cleanup and return C_OK to the caller. */
|
|||||||
fclose(fp);
|
fclose(fp);
|
||||||
freeFakeClient(fakeClient);
|
freeFakeClient(fakeClient);
|
||||||
server.aof_state = old_aof_state;
|
server.aof_state = old_aof_state;
|
||||||
stopLoading();
|
stopLoading(1);
|
||||||
aofUpdateCurrentSize();
|
aofUpdateCurrentSize();
|
||||||
server.aof_rewrite_base_size = server.aof_current_size;
|
server.aof_rewrite_base_size = server.aof_current_size;
|
||||||
server.aof_fsync_offset = server.aof_current_size;
|
server.aof_fsync_offset = server.aof_current_size;
|
||||||
@ -1400,9 +1401,11 @@ int rewriteAppendOnlyFile(char *filename) {
|
|||||||
if (server.aof_rewrite_incremental_fsync)
|
if (server.aof_rewrite_incremental_fsync)
|
||||||
rioSetAutoSync(&aof,REDIS_AUTOSYNC_BYTES);
|
rioSetAutoSync(&aof,REDIS_AUTOSYNC_BYTES);
|
||||||
|
|
||||||
|
startSaving(RDBFLAGS_AOF_PREAMBLE);
|
||||||
|
|
||||||
if (server.aof_use_rdb_preamble) {
|
if (server.aof_use_rdb_preamble) {
|
||||||
int error;
|
int error;
|
||||||
if (rdbSaveRio(&aof,&error,RDB_SAVE_AOF_PREAMBLE,NULL) == C_ERR) {
|
if (rdbSaveRio(&aof,&error,RDBFLAGS_AOF_PREAMBLE,NULL) == C_ERR) {
|
||||||
errno = error;
|
errno = error;
|
||||||
goto werr;
|
goto werr;
|
||||||
}
|
}
|
||||||
@ -1465,15 +1468,18 @@ int rewriteAppendOnlyFile(char *filename) {
|
|||||||
if (rename(tmpfile,filename) == -1) {
|
if (rename(tmpfile,filename) == -1) {
|
||||||
serverLog(LL_WARNING,"Error moving temp append only file on the final destination: %s", strerror(errno));
|
serverLog(LL_WARNING,"Error moving temp append only file on the final destination: %s", strerror(errno));
|
||||||
unlink(tmpfile);
|
unlink(tmpfile);
|
||||||
|
stopSaving(0);
|
||||||
return C_ERR;
|
return C_ERR;
|
||||||
}
|
}
|
||||||
serverLog(LL_NOTICE,"SYNC append only file rewrite performed");
|
serverLog(LL_NOTICE,"SYNC append only file rewrite performed");
|
||||||
|
stopSaving(1);
|
||||||
return C_OK;
|
return C_OK;
|
||||||
|
|
||||||
werr:
|
werr:
|
||||||
serverLog(LL_WARNING,"Write error writing append only file on disk: %s", strerror(errno));
|
serverLog(LL_WARNING,"Write error writing append only file on disk: %s", strerror(errno));
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
unlink(tmpfile);
|
unlink(tmpfile);
|
||||||
|
stopSaving(0);
|
||||||
return C_ERR;
|
return C_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1760,7 +1766,7 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
|||||||
server.aof_selected_db = -1; /* Make sure SELECT is re-issued */
|
server.aof_selected_db = -1; /* Make sure SELECT is re-issued */
|
||||||
aofUpdateCurrentSize();
|
aofUpdateCurrentSize();
|
||||||
server.aof_rewrite_base_size = server.aof_current_size;
|
server.aof_rewrite_base_size = server.aof_current_size;
|
||||||
server.aof_current_size = server.aof_current_size;
|
server.aof_fsync_offset = server.aof_current_size;
|
||||||
|
|
||||||
/* Clear regular AOF buffer since its contents was just written to
|
/* Clear regular AOF buffer since its contents was just written to
|
||||||
* the new AOF from the background rewrite buffer. */
|
* the new AOF from the background rewrite buffer. */
|
||||||
|
@ -514,6 +514,16 @@ void handleClientsBlockedOnKeys(void) {
|
|||||||
* we can safely call signalKeyAsReady() against this key. */
|
* we can safely call signalKeyAsReady() against this key. */
|
||||||
dictDelete(rl->db->ready_keys,rl->key);
|
dictDelete(rl->db->ready_keys,rl->key);
|
||||||
|
|
||||||
|
/* Even if we are not inside call(), increment the call depth
|
||||||
|
* in order to make sure that keys are expired against a fixed
|
||||||
|
* reference time, and not against the wallclock time. This
|
||||||
|
* way we can lookup an object multiple times (BRPOPLPUSH does
|
||||||
|
* that) without the risk of it being freed in the second
|
||||||
|
* lookup, invalidating the first one.
|
||||||
|
* See https://github.com/antirez/redis/pull/6554. */
|
||||||
|
server.fixed_time_expire++;
|
||||||
|
updateCachedTime(0);
|
||||||
|
|
||||||
/* Serve clients blocked on list key. */
|
/* Serve clients blocked on list key. */
|
||||||
robj *o = lookupKeyWrite(rl->db,rl->key);
|
robj *o = lookupKeyWrite(rl->db,rl->key);
|
||||||
|
|
||||||
@ -529,6 +539,7 @@ void handleClientsBlockedOnKeys(void) {
|
|||||||
* module is trying to accomplish right now. */
|
* module is trying to accomplish right now. */
|
||||||
serveClientsBlockedOnKeyByModule(rl);
|
serveClientsBlockedOnKeyByModule(rl);
|
||||||
}
|
}
|
||||||
|
server.fixed_time_expire--;
|
||||||
|
|
||||||
/* Free this item. */
|
/* Free this item. */
|
||||||
decrRefCount(rl->key);
|
decrRefCount(rl->key);
|
||||||
|
@ -4966,7 +4966,7 @@ void restoreCommand(client *c) {
|
|||||||
if (!absttl) ttl+=mstime();
|
if (!absttl) ttl+=mstime();
|
||||||
setExpire(c,c->db,c->argv[1],ttl);
|
setExpire(c,c->db,c->argv[1],ttl);
|
||||||
}
|
}
|
||||||
objectSetLRUOrLFU(obj,lfu_freq,lru_idle,lru_clock);
|
objectSetLRUOrLFU(obj,lfu_freq,lru_idle,lru_clock,1000);
|
||||||
signalModifiedKey(c->db,c->argv[1]);
|
signalModifiedKey(c->db,c->argv[1]);
|
||||||
addReply(c,shared.ok);
|
addReply(c,shared.ok);
|
||||||
server.dirty++;
|
server.dirty++;
|
||||||
|
12
src/config.c
12
src/config.c
@ -580,6 +580,14 @@ void loadServerConfigFromString(char *config) {
|
|||||||
err = "active-defrag-max-scan-fields must be positive";
|
err = "active-defrag-max-scan-fields must be positive";
|
||||||
goto loaderr;
|
goto loaderr;
|
||||||
}
|
}
|
||||||
|
} else if (!strcasecmp(argv[0],"active-expire-effort") && argc == 2) {
|
||||||
|
server.active_expire_effort = atoi(argv[1]);
|
||||||
|
if (server.active_expire_effort < 1 ||
|
||||||
|
server.active_expire_effort > 10)
|
||||||
|
{
|
||||||
|
err = "active-expire-effort must be between 1 and 10";
|
||||||
|
goto loaderr;
|
||||||
|
}
|
||||||
} else if (!strcasecmp(argv[0],"hash-max-ziplist-entries") && argc == 2) {
|
} else if (!strcasecmp(argv[0],"hash-max-ziplist-entries") && argc == 2) {
|
||||||
server.hash_max_ziplist_entries = memtoll(argv[1], NULL);
|
server.hash_max_ziplist_entries = memtoll(argv[1], NULL);
|
||||||
} else if (!strcasecmp(argv[0],"hash-max-ziplist-value") && argc == 2) {
|
} else if (!strcasecmp(argv[0],"hash-max-ziplist-value") && argc == 2) {
|
||||||
@ -1165,6 +1173,8 @@ void configSetCommand(client *c) {
|
|||||||
"active-defrag-cycle-max",server.active_defrag_cycle_max,1,99) {
|
"active-defrag-cycle-max",server.active_defrag_cycle_max,1,99) {
|
||||||
} config_set_numerical_field(
|
} config_set_numerical_field(
|
||||||
"active-defrag-max-scan-fields",server.active_defrag_max_scan_fields,1,LONG_MAX) {
|
"active-defrag-max-scan-fields",server.active_defrag_max_scan_fields,1,LONG_MAX) {
|
||||||
|
} config_set_numerical_field(
|
||||||
|
"active-expire-effort",server.active_expire_effort,1,10) {
|
||||||
} config_set_numerical_field(
|
} config_set_numerical_field(
|
||||||
"auto-aof-rewrite-percentage",server.aof_rewrite_perc,0,INT_MAX){
|
"auto-aof-rewrite-percentage",server.aof_rewrite_perc,0,INT_MAX){
|
||||||
} config_set_numerical_field(
|
} config_set_numerical_field(
|
||||||
@ -1478,6 +1488,7 @@ void configGetCommand(client *c) {
|
|||||||
config_get_numerical_field("active-defrag-cycle-min",server.active_defrag_cycle_min);
|
config_get_numerical_field("active-defrag-cycle-min",server.active_defrag_cycle_min);
|
||||||
config_get_numerical_field("active-defrag-cycle-max",server.active_defrag_cycle_max);
|
config_get_numerical_field("active-defrag-cycle-max",server.active_defrag_cycle_max);
|
||||||
config_get_numerical_field("active-defrag-max-scan-fields",server.active_defrag_max_scan_fields);
|
config_get_numerical_field("active-defrag-max-scan-fields",server.active_defrag_max_scan_fields);
|
||||||
|
config_get_numerical_field("active-expire-effort",server.active_expire_effort);
|
||||||
config_get_numerical_field("auto-aof-rewrite-percentage",
|
config_get_numerical_field("auto-aof-rewrite-percentage",
|
||||||
server.aof_rewrite_perc);
|
server.aof_rewrite_perc);
|
||||||
config_get_numerical_field("auto-aof-rewrite-min-size",
|
config_get_numerical_field("auto-aof-rewrite-min-size",
|
||||||
@ -2327,6 +2338,7 @@ int rewriteConfig(char *path) {
|
|||||||
rewriteConfigNumericalOption(state,"active-defrag-cycle-min",server.active_defrag_cycle_min,CONFIG_DEFAULT_DEFRAG_CYCLE_MIN);
|
rewriteConfigNumericalOption(state,"active-defrag-cycle-min",server.active_defrag_cycle_min,CONFIG_DEFAULT_DEFRAG_CYCLE_MIN);
|
||||||
rewriteConfigNumericalOption(state,"active-defrag-cycle-max",server.active_defrag_cycle_max,CONFIG_DEFAULT_DEFRAG_CYCLE_MAX);
|
rewriteConfigNumericalOption(state,"active-defrag-cycle-max",server.active_defrag_cycle_max,CONFIG_DEFAULT_DEFRAG_CYCLE_MAX);
|
||||||
rewriteConfigNumericalOption(state,"active-defrag-max-scan-fields",server.active_defrag_max_scan_fields,CONFIG_DEFAULT_DEFRAG_MAX_SCAN_FIELDS);
|
rewriteConfigNumericalOption(state,"active-defrag-max-scan-fields",server.active_defrag_max_scan_fields,CONFIG_DEFAULT_DEFRAG_MAX_SCAN_FIELDS);
|
||||||
|
rewriteConfigNumericalOption(state,"active-expire-effort",server.active_expire_effort,CONFIG_DEFAULT_ACTIVE_EXPIRE_EFFORT);
|
||||||
rewriteConfigYesNoOption(state,"appendonly",server.aof_enabled,0);
|
rewriteConfigYesNoOption(state,"appendonly",server.aof_enabled,0);
|
||||||
rewriteConfigStringOption(state,"appendfilename",server.aof_filename,CONFIG_DEFAULT_AOF_FILENAME);
|
rewriteConfigStringOption(state,"appendfilename",server.aof_filename,CONFIG_DEFAULT_AOF_FILENAME);
|
||||||
rewriteConfigEnumOption(state,"appendfsync",server.aof_fsync,aof_fsync_enum,CONFIG_DEFAULT_AOF_FSYNC);
|
rewriteConfigEnumOption(state,"appendfsync",server.aof_fsync,aof_fsync_enum,CONFIG_DEFAULT_AOF_FSYNC);
|
||||||
|
75
src/db.c
75
src/db.c
@ -151,9 +151,13 @@ robj *lookupKeyRead(redisDb *db, robj *key) {
|
|||||||
*
|
*
|
||||||
* Returns the linked value object if the key exists or NULL if the key
|
* Returns the linked value object if the key exists or NULL if the key
|
||||||
* does not exist in the specified DB. */
|
* does not exist in the specified DB. */
|
||||||
robj *lookupKeyWrite(redisDb *db, robj *key) {
|
robj *lookupKeyWriteWithFlags(redisDb *db, robj *key, int flags) {
|
||||||
expireIfNeeded(db,key);
|
expireIfNeeded(db,key);
|
||||||
return lookupKey(db,key,LOOKUP_NONE);
|
return lookupKey(db,key,flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
robj *lookupKeyWrite(redisDb *db, robj *key) {
|
||||||
|
return lookupKeyWriteWithFlags(db, key, LOOKUP_NONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
robj *lookupKeyReadOrReply(client *c, robj *key, robj *reply) {
|
robj *lookupKeyReadOrReply(client *c, robj *key, robj *reply) {
|
||||||
@ -461,6 +465,29 @@ int getFlushCommandFlags(client *c, int *flags) {
|
|||||||
return C_OK;
|
return C_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Flushes the whole server data set. */
|
||||||
|
void flushAllDataAndResetRDB(int flags) {
|
||||||
|
server.dirty += emptyDb(-1,flags,NULL);
|
||||||
|
if (server.rdb_child_pid != -1) killRDBChild();
|
||||||
|
if (server.saveparamslen > 0) {
|
||||||
|
/* Normally rdbSave() will reset dirty, but we don't want this here
|
||||||
|
* as otherwise FLUSHALL will not be replicated nor put into the AOF. */
|
||||||
|
int saved_dirty = server.dirty;
|
||||||
|
rdbSaveInfo rsi, *rsiptr;
|
||||||
|
rsiptr = rdbPopulateSaveInfo(&rsi);
|
||||||
|
rdbSave(server.rdb_filename,rsiptr);
|
||||||
|
server.dirty = saved_dirty;
|
||||||
|
}
|
||||||
|
server.dirty++;
|
||||||
|
#if defined(USE_JEMALLOC)
|
||||||
|
/* jemalloc 5 doesn't release pages back to the OS when there's no traffic.
|
||||||
|
* for large databases, flushdb blocks for long anyway, so a bit more won't
|
||||||
|
* harm and this way the flush and purge will be synchroneus. */
|
||||||
|
if (!(flags & EMPTYDB_ASYNC))
|
||||||
|
jemalloc_purge();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
/* FLUSHDB [ASYNC]
|
/* FLUSHDB [ASYNC]
|
||||||
*
|
*
|
||||||
* Flushes the currently SELECTed Redis DB. */
|
* Flushes the currently SELECTed Redis DB. */
|
||||||
@ -484,28 +511,9 @@ void flushdbCommand(client *c) {
|
|||||||
* Flushes the whole server data set. */
|
* Flushes the whole server data set. */
|
||||||
void flushallCommand(client *c) {
|
void flushallCommand(client *c) {
|
||||||
int flags;
|
int flags;
|
||||||
|
|
||||||
if (getFlushCommandFlags(c,&flags) == C_ERR) return;
|
if (getFlushCommandFlags(c,&flags) == C_ERR) return;
|
||||||
server.dirty += emptyDb(-1,flags,NULL);
|
flushAllDataAndResetRDB(flags);
|
||||||
addReply(c,shared.ok);
|
addReply(c,shared.ok);
|
||||||
if (server.rdb_child_pid != -1) killRDBChild();
|
|
||||||
if (server.saveparamslen > 0) {
|
|
||||||
/* Normally rdbSave() will reset dirty, but we don't want this here
|
|
||||||
* as otherwise FLUSHALL will not be replicated nor put into the AOF. */
|
|
||||||
int saved_dirty = server.dirty;
|
|
||||||
rdbSaveInfo rsi, *rsiptr;
|
|
||||||
rsiptr = rdbPopulateSaveInfo(&rsi);
|
|
||||||
rdbSave(server.rdb_filename,rsiptr);
|
|
||||||
server.dirty = saved_dirty;
|
|
||||||
}
|
|
||||||
server.dirty++;
|
|
||||||
#if defined(USE_JEMALLOC)
|
|
||||||
/* jemalloc 5 doesn't release pages back to the OS when there's no traffic.
|
|
||||||
* for large databases, flushdb blocks for long anyway, so a bit more won't
|
|
||||||
* harm and this way the flush and purge will be synchroneus. */
|
|
||||||
if (!(flags & EMPTYDB_ASYNC))
|
|
||||||
jemalloc_purge();
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This command implements DEL and LAZYDEL. */
|
/* This command implements DEL and LAZYDEL. */
|
||||||
@ -1069,10 +1077,12 @@ int dbSwapDatabases(long id1, long id2) {
|
|||||||
db1->dict = db2->dict;
|
db1->dict = db2->dict;
|
||||||
db1->expires = db2->expires;
|
db1->expires = db2->expires;
|
||||||
db1->avg_ttl = db2->avg_ttl;
|
db1->avg_ttl = db2->avg_ttl;
|
||||||
|
db1->expires_cursor = db2->expires_cursor;
|
||||||
|
|
||||||
db2->dict = aux.dict;
|
db2->dict = aux.dict;
|
||||||
db2->expires = aux.expires;
|
db2->expires = aux.expires;
|
||||||
db2->avg_ttl = aux.avg_ttl;
|
db2->avg_ttl = aux.avg_ttl;
|
||||||
|
db2->expires_cursor = aux.expires_cursor;
|
||||||
|
|
||||||
/* Now we need to handle clients blocked on lists: as an effect
|
/* Now we need to handle clients blocked on lists: as an effect
|
||||||
* of swapping the two DBs, a client that was waiting for list
|
* of swapping the two DBs, a client that was waiting for list
|
||||||
@ -1188,6 +1198,7 @@ void propagateExpire(redisDb *db, robj *key, int lazy) {
|
|||||||
/* Check if the key is expired. */
|
/* Check if the key is expired. */
|
||||||
int keyIsExpired(redisDb *db, robj *key) {
|
int keyIsExpired(redisDb *db, robj *key) {
|
||||||
mstime_t when = getExpire(db,key);
|
mstime_t when = getExpire(db,key);
|
||||||
|
mstime_t now;
|
||||||
|
|
||||||
if (when < 0) return 0; /* No expire for this key */
|
if (when < 0) return 0; /* No expire for this key */
|
||||||
|
|
||||||
@ -1199,8 +1210,26 @@ int keyIsExpired(redisDb *db, robj *key) {
|
|||||||
* only the first time it is accessed and not in the middle of the
|
* only the first time it is accessed and not in the middle of the
|
||||||
* script execution, making propagation to slaves / AOF consistent.
|
* script execution, making propagation to slaves / AOF consistent.
|
||||||
* See issue #1525 on Github for more information. */
|
* See issue #1525 on Github for more information. */
|
||||||
mstime_t now = server.lua_caller ? server.lua_time_start : mstime();
|
if (server.lua_caller) {
|
||||||
|
now = server.lua_time_start;
|
||||||
|
}
|
||||||
|
/* If we are in the middle of a command execution, we still want to use
|
||||||
|
* a reference time that does not change: in that case we just use the
|
||||||
|
* cached time, that we update before each call in the call() function.
|
||||||
|
* This way we avoid that commands such as RPOPLPUSH or similar, that
|
||||||
|
* may re-open the same key multiple times, can invalidate an already
|
||||||
|
* open object in a next call, if the next call will see the key expired,
|
||||||
|
* while the first did not. */
|
||||||
|
else if (server.fixed_time_expire > 0) {
|
||||||
|
now = server.mstime;
|
||||||
|
}
|
||||||
|
/* For the other cases, we want to use the most fresh time we have. */
|
||||||
|
else {
|
||||||
|
now = mstime();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The key expired if the current (virtual or real) time is greater
|
||||||
|
* than the expire time of the key. */
|
||||||
return now > when;
|
return now > when;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -417,7 +417,7 @@ NULL
|
|||||||
}
|
}
|
||||||
emptyDb(-1,EMPTYDB_NO_FLAGS,NULL);
|
emptyDb(-1,EMPTYDB_NO_FLAGS,NULL);
|
||||||
protectClient(c);
|
protectClient(c);
|
||||||
int ret = rdbLoad(server.rdb_filename,NULL);
|
int ret = rdbLoad(server.rdb_filename,NULL,RDBFLAGS_NONE);
|
||||||
unprotectClient(c);
|
unprotectClient(c);
|
||||||
if (ret != C_OK) {
|
if (ret != C_OK) {
|
||||||
addReplyError(c,"Error trying to load the RDB dump");
|
addReplyError(c,"Error trying to load the RDB dump");
|
||||||
|
44
src/defrag.c
44
src/defrag.c
@ -919,10 +919,12 @@ int defragLaterItem(dictEntry *de, unsigned long *cursor, long long endtime) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* static variables serving defragLaterStep to continue scanning a key from were we stopped last time. */
|
||||||
|
static sds defrag_later_current_key = NULL;
|
||||||
|
static unsigned long defrag_later_cursor = 0;
|
||||||
|
|
||||||
/* returns 0 if no more work needs to be been done, and 1 if time is up and more work is needed. */
|
/* returns 0 if no more work needs to be been done, and 1 if time is up and more work is needed. */
|
||||||
int defragLaterStep(redisDb *db, long long endtime) {
|
int defragLaterStep(redisDb *db, long long endtime) {
|
||||||
static sds current_key = NULL;
|
|
||||||
static unsigned long cursor = 0;
|
|
||||||
unsigned int iterations = 0;
|
unsigned int iterations = 0;
|
||||||
unsigned long long prev_defragged = server.stat_active_defrag_hits;
|
unsigned long long prev_defragged = server.stat_active_defrag_hits;
|
||||||
unsigned long long prev_scanned = server.stat_active_defrag_scanned;
|
unsigned long long prev_scanned = server.stat_active_defrag_scanned;
|
||||||
@ -930,16 +932,15 @@ int defragLaterStep(redisDb *db, long long endtime) {
|
|||||||
|
|
||||||
do {
|
do {
|
||||||
/* if we're not continuing a scan from the last call or loop, start a new one */
|
/* if we're not continuing a scan from the last call or loop, start a new one */
|
||||||
if (!cursor) {
|
if (!defrag_later_cursor) {
|
||||||
listNode *head = listFirst(db->defrag_later);
|
listNode *head = listFirst(db->defrag_later);
|
||||||
|
|
||||||
/* Move on to next key */
|
/* Move on to next key */
|
||||||
if (current_key) {
|
if (defrag_later_current_key) {
|
||||||
serverAssert(current_key == head->value);
|
serverAssert(defrag_later_current_key == head->value);
|
||||||
sdsfree(head->value);
|
|
||||||
listDelNode(db->defrag_later, head);
|
listDelNode(db->defrag_later, head);
|
||||||
cursor = 0;
|
defrag_later_cursor = 0;
|
||||||
current_key = NULL;
|
defrag_later_current_key = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* stop if we reached the last one. */
|
/* stop if we reached the last one. */
|
||||||
@ -948,21 +949,21 @@ int defragLaterStep(redisDb *db, long long endtime) {
|
|||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
/* start a new key */
|
/* start a new key */
|
||||||
current_key = head->value;
|
defrag_later_current_key = head->value;
|
||||||
cursor = 0;
|
defrag_later_cursor = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* each time we enter this function we need to fetch the key from the dict again (if it still exists) */
|
/* each time we enter this function we need to fetch the key from the dict again (if it still exists) */
|
||||||
dictEntry *de = dictFind(db->dict, current_key);
|
dictEntry *de = dictFind(db->dict, defrag_later_current_key);
|
||||||
key_defragged = server.stat_active_defrag_hits;
|
key_defragged = server.stat_active_defrag_hits;
|
||||||
do {
|
do {
|
||||||
int quit = 0;
|
int quit = 0;
|
||||||
if (defragLaterItem(de, &cursor, endtime))
|
if (defragLaterItem(de, &defrag_later_cursor, endtime))
|
||||||
quit = 1; /* time is up, we didn't finish all the work */
|
quit = 1; /* time is up, we didn't finish all the work */
|
||||||
|
|
||||||
/* Don't start a new BIG key in this loop, this is because the
|
/* Don't start a new BIG key in this loop, this is because the
|
||||||
* next key can be a list, and scanLaterList must be done in once cycle */
|
* next key can be a list, and scanLaterList must be done in once cycle */
|
||||||
if (!cursor)
|
if (!defrag_later_cursor)
|
||||||
quit = 1;
|
quit = 1;
|
||||||
|
|
||||||
/* Once in 16 scan iterations, 512 pointer reallocations, or 64 fields
|
/* Once in 16 scan iterations, 512 pointer reallocations, or 64 fields
|
||||||
@ -982,7 +983,7 @@ int defragLaterStep(redisDb *db, long long endtime) {
|
|||||||
prev_defragged = server.stat_active_defrag_hits;
|
prev_defragged = server.stat_active_defrag_hits;
|
||||||
prev_scanned = server.stat_active_defrag_scanned;
|
prev_scanned = server.stat_active_defrag_scanned;
|
||||||
}
|
}
|
||||||
} while(cursor);
|
} while(defrag_later_cursor);
|
||||||
if(key_defragged != server.stat_active_defrag_hits)
|
if(key_defragged != server.stat_active_defrag_hits)
|
||||||
server.stat_active_defrag_key_hits++;
|
server.stat_active_defrag_key_hits++;
|
||||||
else
|
else
|
||||||
@ -1039,6 +1040,21 @@ void activeDefragCycle(void) {
|
|||||||
mstime_t latency;
|
mstime_t latency;
|
||||||
int quit = 0;
|
int quit = 0;
|
||||||
|
|
||||||
|
if (!server.active_defrag_enabled) {
|
||||||
|
if (server.active_defrag_running) {
|
||||||
|
/* if active defrag was disabled mid-run, start from fresh next time. */
|
||||||
|
server.active_defrag_running = 0;
|
||||||
|
if (db)
|
||||||
|
listEmpty(db->defrag_later);
|
||||||
|
defrag_later_current_key = NULL;
|
||||||
|
defrag_later_cursor = 0;
|
||||||
|
current_db = -1;
|
||||||
|
cursor = 0;
|
||||||
|
db = NULL;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (hasActiveChildProcess())
|
if (hasActiveChildProcess())
|
||||||
return; /* Defragging memory while there's a fork will just do damage. */
|
return; /* Defragging memory while there's a fork will just do damage. */
|
||||||
|
|
||||||
|
138
src/expire.c
138
src/expire.c
@ -78,24 +78,63 @@ int activeExpireCycleTryExpire(redisDb *db, dictEntry *de, long long now) {
|
|||||||
* it will get more aggressive to avoid that too much memory is used by
|
* it will get more aggressive to avoid that too much memory is used by
|
||||||
* keys that can be removed from the keyspace.
|
* keys that can be removed from the keyspace.
|
||||||
*
|
*
|
||||||
* No more than CRON_DBS_PER_CALL databases are tested at every
|
* Every expire cycle tests multiple databases: the next call will start
|
||||||
* iteration.
|
* again from the next db, with the exception of exists for time limit: in that
|
||||||
|
* case we restart again from the last database we were processing. Anyway
|
||||||
|
* no more than CRON_DBS_PER_CALL databases are tested at every iteration.
|
||||||
*
|
*
|
||||||
* This kind of call is used when Redis detects that timelimit_exit is
|
* The function can perform more or less work, depending on the "type"
|
||||||
* true, so there is more work to do, and we do it more incrementally from
|
* argument. It can execute a "fast cycle" or a "slow cycle". The slow
|
||||||
* the beforeSleep() function of the event loop.
|
* cycle is the main way we collect expired cycles: this happens with
|
||||||
|
* the "server.hz" frequency (usually 10 hertz).
|
||||||
*
|
*
|
||||||
* Expire cycle type:
|
* However the slow cycle can exit for timeout, since it used too much time.
|
||||||
|
* For this reason the function is also invoked to perform a fast cycle
|
||||||
|
* at every event loop cycle, in the beforeSleep() function. The fast cycle
|
||||||
|
* will try to perform less work, but will do it much more often.
|
||||||
|
*
|
||||||
|
* The following are the details of the two expire cycles and their stop
|
||||||
|
* conditions:
|
||||||
*
|
*
|
||||||
* If type is ACTIVE_EXPIRE_CYCLE_FAST the function will try to run a
|
* If type is ACTIVE_EXPIRE_CYCLE_FAST the function will try to run a
|
||||||
* "fast" expire cycle that takes no longer than EXPIRE_FAST_CYCLE_DURATION
|
* "fast" expire cycle that takes no longer than EXPIRE_FAST_CYCLE_DURATION
|
||||||
* microseconds, and is not repeated again before the same amount of time.
|
* microseconds, and is not repeated again before the same amount of time.
|
||||||
|
* The cycle will also refuse to run at all if the latest slow cycle did not
|
||||||
|
* terminate because of a time limit condition.
|
||||||
*
|
*
|
||||||
* If type is ACTIVE_EXPIRE_CYCLE_SLOW, that normal expire cycle is
|
* If type is ACTIVE_EXPIRE_CYCLE_SLOW, that normal expire cycle is
|
||||||
* executed, where the time limit is a percentage of the REDIS_HZ period
|
* executed, where the time limit is a percentage of the REDIS_HZ period
|
||||||
* as specified by the ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC define. */
|
* as specified by the ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC define. In the
|
||||||
|
* fast cycle, the check of every database is interrupted once the number
|
||||||
|
* of already expired keys in the database is estimated to be lower than
|
||||||
|
* a given percentage, in order to avoid doing too much work to gain too
|
||||||
|
* little memory.
|
||||||
|
*
|
||||||
|
* The configured expire "effort" will modify the baseline parameters in
|
||||||
|
* order to do more work in both the fast and slow expire cycles.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP 20 /* Keys for each DB loop. */
|
||||||
|
#define ACTIVE_EXPIRE_CYCLE_FAST_DURATION 1000 /* Microseconds. */
|
||||||
|
#define ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC 25 /* Max % of CPU to use. */
|
||||||
|
#define ACTIVE_EXPIRE_CYCLE_ACCEPTABLE_STALE 10 /* % of stale keys after which
|
||||||
|
we do extra efforts. */
|
||||||
|
|
||||||
void activeExpireCycle(int type) {
|
void activeExpireCycle(int type) {
|
||||||
|
/* Adjust the running parameters according to the configured expire
|
||||||
|
* effort. The default effort is 1, and the maximum configurable effort
|
||||||
|
* is 10. */
|
||||||
|
unsigned long
|
||||||
|
effort = server.active_expire_effort-1, /* Rescale from 0 to 9. */
|
||||||
|
config_keys_per_loop = ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP +
|
||||||
|
ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP/4*effort,
|
||||||
|
config_cycle_fast_duration = ACTIVE_EXPIRE_CYCLE_FAST_DURATION +
|
||||||
|
ACTIVE_EXPIRE_CYCLE_FAST_DURATION/4*effort,
|
||||||
|
config_cycle_slow_time_perc = ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC +
|
||||||
|
2*effort,
|
||||||
|
config_cycle_acceptable_stale = ACTIVE_EXPIRE_CYCLE_ACCEPTABLE_STALE-
|
||||||
|
effort;
|
||||||
|
|
||||||
/* This function has some global state in order to continue the work
|
/* This function has some global state in order to continue the work
|
||||||
* incrementally across calls. */
|
* incrementally across calls. */
|
||||||
static unsigned int current_db = 0; /* Last DB tested. */
|
static unsigned int current_db = 0; /* Last DB tested. */
|
||||||
@ -113,10 +152,16 @@ void activeExpireCycle(int type) {
|
|||||||
|
|
||||||
if (type == ACTIVE_EXPIRE_CYCLE_FAST) {
|
if (type == ACTIVE_EXPIRE_CYCLE_FAST) {
|
||||||
/* Don't start a fast cycle if the previous cycle did not exit
|
/* Don't start a fast cycle if the previous cycle did not exit
|
||||||
* for time limit. Also don't repeat a fast cycle for the same period
|
* for time limit, unless the percentage of estimated stale keys is
|
||||||
|
* too high. Also never repeat a fast cycle for the same period
|
||||||
* as the fast cycle total duration itself. */
|
* as the fast cycle total duration itself. */
|
||||||
if (!timelimit_exit) return;
|
if (!timelimit_exit &&
|
||||||
if (start < last_fast_cycle + ACTIVE_EXPIRE_CYCLE_FAST_DURATION*2) return;
|
server.stat_expired_stale_perc < config_cycle_acceptable_stale)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (start < last_fast_cycle + (long long)config_cycle_fast_duration*2)
|
||||||
|
return;
|
||||||
|
|
||||||
last_fast_cycle = start;
|
last_fast_cycle = start;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,16 +175,16 @@ void activeExpireCycle(int type) {
|
|||||||
if (dbs_per_call > server.dbnum || timelimit_exit)
|
if (dbs_per_call > server.dbnum || timelimit_exit)
|
||||||
dbs_per_call = server.dbnum;
|
dbs_per_call = server.dbnum;
|
||||||
|
|
||||||
/* We can use at max ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC percentage of CPU time
|
/* We can use at max 'config_cycle_slow_time_perc' percentage of CPU
|
||||||
* per iteration. Since this function gets called with a frequency of
|
* time per iteration. Since this function gets called with a frequency of
|
||||||
* server.hz times per second, the following is the max amount of
|
* server.hz times per second, the following is the max amount of
|
||||||
* microseconds we can spend in this function. */
|
* microseconds we can spend in this function. */
|
||||||
timelimit = 1000000*ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC/server.hz/100;
|
timelimit = config_cycle_slow_time_perc*1000000/server.hz/100;
|
||||||
timelimit_exit = 0;
|
timelimit_exit = 0;
|
||||||
if (timelimit <= 0) timelimit = 1;
|
if (timelimit <= 0) timelimit = 1;
|
||||||
|
|
||||||
if (type == ACTIVE_EXPIRE_CYCLE_FAST)
|
if (type == ACTIVE_EXPIRE_CYCLE_FAST)
|
||||||
timelimit = ACTIVE_EXPIRE_CYCLE_FAST_DURATION; /* in microseconds. */
|
timelimit = config_cycle_fast_duration; /* in microseconds. */
|
||||||
|
|
||||||
/* Accumulate some global stats as we expire keys, to have some idea
|
/* Accumulate some global stats as we expire keys, to have some idea
|
||||||
* about the number of keys that are already logically expired, but still
|
* about the number of keys that are already logically expired, but still
|
||||||
@ -148,7 +193,9 @@ void activeExpireCycle(int type) {
|
|||||||
long total_expired = 0;
|
long total_expired = 0;
|
||||||
|
|
||||||
for (j = 0; j < dbs_per_call && timelimit_exit == 0; j++) {
|
for (j = 0; j < dbs_per_call && timelimit_exit == 0; j++) {
|
||||||
int expired;
|
/* Expired and checked in a single loop. */
|
||||||
|
unsigned long expired, sampled;
|
||||||
|
|
||||||
redisDb *db = server.db+(current_db % server.dbnum);
|
redisDb *db = server.db+(current_db % server.dbnum);
|
||||||
|
|
||||||
/* Increment the DB now so we are sure if we run out of time
|
/* Increment the DB now so we are sure if we run out of time
|
||||||
@ -172,8 +219,8 @@ void activeExpireCycle(int type) {
|
|||||||
slots = dictSlots(db->expires);
|
slots = dictSlots(db->expires);
|
||||||
now = mstime();
|
now = mstime();
|
||||||
|
|
||||||
/* When there are less than 1% filled slots getting random
|
/* When there are less than 1% filled slots, sampling the key
|
||||||
* keys is expensive, so stop here waiting for better times...
|
* space is expensive, so stop here waiting for better times...
|
||||||
* The dictionary will be resized asap. */
|
* The dictionary will be resized asap. */
|
||||||
if (num && slots > DICT_HT_INITIAL_SIZE &&
|
if (num && slots > DICT_HT_INITIAL_SIZE &&
|
||||||
(num*100/slots < 1)) break;
|
(num*100/slots < 1)) break;
|
||||||
@ -181,27 +228,58 @@ void activeExpireCycle(int type) {
|
|||||||
/* The main collection cycle. Sample random keys among keys
|
/* The main collection cycle. Sample random keys among keys
|
||||||
* with an expire set, checking for expired ones. */
|
* with an expire set, checking for expired ones. */
|
||||||
expired = 0;
|
expired = 0;
|
||||||
|
sampled = 0;
|
||||||
ttl_sum = 0;
|
ttl_sum = 0;
|
||||||
ttl_samples = 0;
|
ttl_samples = 0;
|
||||||
|
|
||||||
if (num > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP)
|
if (num > config_keys_per_loop)
|
||||||
num = ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP;
|
num = config_keys_per_loop;
|
||||||
|
|
||||||
while (num--) {
|
/* Here we access the low level representation of the hash table
|
||||||
dictEntry *de;
|
* for speed concerns: this makes this code coupled with dict.c,
|
||||||
|
* but it hardly changed in ten years.
|
||||||
|
*
|
||||||
|
* Note that certain places of the hash table may be empty,
|
||||||
|
* so we want also a stop condition about the number of
|
||||||
|
* buckets that we scanned. However scanning for free buckets
|
||||||
|
* is very fast: we are in the cache line scanning a sequential
|
||||||
|
* array of NULL pointers, so we can scan a lot more buckets
|
||||||
|
* than keys in the same time. */
|
||||||
|
long max_buckets = num*20;
|
||||||
|
long checked_buckets = 0;
|
||||||
|
|
||||||
|
while (sampled < num && checked_buckets < max_buckets) {
|
||||||
|
for (int table = 0; table < 2; table++) {
|
||||||
|
if (table == 1 && !dictIsRehashing(db->expires)) break;
|
||||||
|
|
||||||
|
unsigned long idx = db->expires_cursor;
|
||||||
|
idx &= db->expires->ht[table].sizemask;
|
||||||
|
dictEntry *de = db->expires->ht[table].table[idx];
|
||||||
long long ttl;
|
long long ttl;
|
||||||
|
|
||||||
if ((de = dictGetRandomKey(db->expires)) == NULL) break;
|
/* Scan the current bucket of the current table. */
|
||||||
ttl = dictGetSignedIntegerVal(de)-now;
|
checked_buckets++;
|
||||||
if (activeExpireCycleTryExpire(db,de,now)) expired++;
|
while(de) {
|
||||||
|
/* Get the next entry now since this entry may get
|
||||||
|
* deleted. */
|
||||||
|
dictEntry *e = de;
|
||||||
|
de = de->next;
|
||||||
|
|
||||||
|
ttl = dictGetSignedIntegerVal(e)-now;
|
||||||
|
if (activeExpireCycleTryExpire(db,e,now)) expired++;
|
||||||
if (ttl > 0) {
|
if (ttl > 0) {
|
||||||
/* We want the average TTL of keys yet not expired. */
|
/* We want the average TTL of keys yet
|
||||||
|
* not expired. */
|
||||||
ttl_sum += ttl;
|
ttl_sum += ttl;
|
||||||
ttl_samples++;
|
ttl_samples++;
|
||||||
}
|
}
|
||||||
total_sampled++;
|
sampled++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
db->expires_cursor++;
|
||||||
}
|
}
|
||||||
total_expired += expired;
|
total_expired += expired;
|
||||||
|
total_sampled += sampled;
|
||||||
|
|
||||||
/* Update the average TTL stats for this database. */
|
/* Update the average TTL stats for this database. */
|
||||||
if (ttl_samples) {
|
if (ttl_samples) {
|
||||||
@ -225,12 +303,14 @@ void activeExpireCycle(int type) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* We don't repeat the cycle if there are less than 25% of keys
|
/* We don't repeat the cycle for the current database if there are
|
||||||
* found expired in the current DB. */
|
* an acceptable amount of stale keys (logically expired but yet
|
||||||
} while (expired > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP/4);
|
* not reclained). */
|
||||||
|
} while ((expired*100/sampled) > config_cycle_acceptable_stale);
|
||||||
}
|
}
|
||||||
|
|
||||||
elapsed = ustime()-start;
|
elapsed = ustime()-start;
|
||||||
|
server.stat_expire_cycle_time_used += elapsed;
|
||||||
latencyAddSampleIfNeeded("expire-cycle",elapsed/1000);
|
latencyAddSampleIfNeeded("expire-cycle",elapsed/1000);
|
||||||
|
|
||||||
/* Update our estimate of keys existing but yet to be expired.
|
/* Update our estimate of keys existing but yet to be expired.
|
||||||
|
@ -3,7 +3,7 @@ GIT_SHA1=`(git show-ref --head --hash=8 2> /dev/null || echo 00000000) | head -n
|
|||||||
GIT_DIRTY=`git diff --no-ext-diff 2> /dev/null | wc -l`
|
GIT_DIRTY=`git diff --no-ext-diff 2> /dev/null | wc -l`
|
||||||
BUILD_ID=`uname -n`"-"`date +%s`
|
BUILD_ID=`uname -n`"-"`date +%s`
|
||||||
if [ -n "$SOURCE_DATE_EPOCH" ]; then
|
if [ -n "$SOURCE_DATE_EPOCH" ]; then
|
||||||
BUILD_ID=$(date -u -d "@$SOURCE_DATE_EPOCH" +%s 2>/dev/null || date -u -r "$SOURCE_DATE_EPOCH" +%s 2>/dev/null || date -u %s)
|
BUILD_ID=$(date -u -d "@$SOURCE_DATE_EPOCH" +%s 2>/dev/null || date -u -r "$SOURCE_DATE_EPOCH" +%s 2>/dev/null || date -u +%s)
|
||||||
fi
|
fi
|
||||||
test -f release.h || touch release.h
|
test -f release.h || touch release.h
|
||||||
(cat release.h | grep SHA1 | grep $GIT_SHA1) && \
|
(cat release.h | grep SHA1 | grep $GIT_SHA1) && \
|
||||||
|
721
src/module.c
721
src/module.c
File diff suppressed because it is too large
Load Diff
@ -530,7 +530,7 @@ void addReplyHumanLongDouble(client *c, long double d) {
|
|||||||
decrRefCount(o);
|
decrRefCount(o);
|
||||||
} else {
|
} else {
|
||||||
char buf[MAX_LONG_DOUBLE_CHARS];
|
char buf[MAX_LONG_DOUBLE_CHARS];
|
||||||
int len = ld2string(buf,sizeof(buf),d,1);
|
int len = ld2string(buf,sizeof(buf),d,LD_STR_HUMAN);
|
||||||
addReplyProto(c,",",1);
|
addReplyProto(c,",",1);
|
||||||
addReplyProto(c,buf,len);
|
addReplyProto(c,buf,len);
|
||||||
addReplyProto(c,"\r\n",2);
|
addReplyProto(c,"\r\n",2);
|
||||||
@ -1118,6 +1118,11 @@ void freeClient(client *c) {
|
|||||||
if (c->flags & CLIENT_SLAVE && listLength(server.slaves) == 0)
|
if (c->flags & CLIENT_SLAVE && listLength(server.slaves) == 0)
|
||||||
server.repl_no_slaves_since = server.unixtime;
|
server.repl_no_slaves_since = server.unixtime;
|
||||||
refreshGoodSlavesCount();
|
refreshGoodSlavesCount();
|
||||||
|
/* Fire the replica change modules event. */
|
||||||
|
if (c->replstate == SLAVE_STATE_ONLINE)
|
||||||
|
moduleFireServerEvent(REDISMODULE_EVENT_REPLICA_CHANGE,
|
||||||
|
REDISMODULE_SUBEVENT_REPLICA_CHANGE_OFFLINE,
|
||||||
|
NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Master/slave cleanup Case 2:
|
/* Master/slave cleanup Case 2:
|
||||||
|
11
src/object.c
11
src/object.c
@ -178,7 +178,7 @@ robj *createStringObjectFromLongLongForValue(long long value) {
|
|||||||
* The 'humanfriendly' option is used for INCRBYFLOAT and HINCRBYFLOAT. */
|
* The 'humanfriendly' option is used for INCRBYFLOAT and HINCRBYFLOAT. */
|
||||||
robj *createStringObjectFromLongDouble(long double value, int humanfriendly) {
|
robj *createStringObjectFromLongDouble(long double value, int humanfriendly) {
|
||||||
char buf[MAX_LONG_DOUBLE_CHARS];
|
char buf[MAX_LONG_DOUBLE_CHARS];
|
||||||
int len = ld2string(buf,sizeof(buf),value,humanfriendly);
|
int len = ld2string(buf,sizeof(buf),value,humanfriendly? LD_STR_HUMAN: LD_STR_AUTO);
|
||||||
return createStringObject(buf,len);
|
return createStringObject(buf,len);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1201,19 +1201,20 @@ sds getMemoryDoctorReport(void) {
|
|||||||
* The lru_idle and lru_clock args are only relevant if policy
|
* The lru_idle and lru_clock args are only relevant if policy
|
||||||
* is MAXMEMORY_FLAG_LRU.
|
* is MAXMEMORY_FLAG_LRU.
|
||||||
* Either or both of them may be <0, in that case, nothing is set. */
|
* Either or both of them may be <0, in that case, nothing is set. */
|
||||||
void objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
|
int objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
|
||||||
long long lru_clock) {
|
long long lru_clock, int lru_multiplier) {
|
||||||
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
||||||
if (lfu_freq >= 0) {
|
if (lfu_freq >= 0) {
|
||||||
serverAssert(lfu_freq <= 255);
|
serverAssert(lfu_freq <= 255);
|
||||||
val->lru = (LFUGetTimeInMinutes()<<8) | lfu_freq;
|
val->lru = (LFUGetTimeInMinutes()<<8) | lfu_freq;
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
} else if (lru_idle >= 0) {
|
} else if (lru_idle >= 0) {
|
||||||
/* Provided LRU idle time is in seconds. Scale
|
/* Provided LRU idle time is in seconds. Scale
|
||||||
* according to the LRU clock resolution this Redis
|
* according to the LRU clock resolution this Redis
|
||||||
* instance was compiled with (normally 1000 ms, so the
|
* instance was compiled with (normally 1000 ms, so the
|
||||||
* below statement will expand to lru_idle*1000/1000. */
|
* below statement will expand to lru_idle*1000/1000. */
|
||||||
lru_idle = lru_idle*1000/LRU_CLOCK_RESOLUTION;
|
lru_idle = lru_idle*lru_multiplier/LRU_CLOCK_RESOLUTION;
|
||||||
long lru_abs = lru_clock - lru_idle; /* Absolute access time. */
|
long lru_abs = lru_clock - lru_idle; /* Absolute access time. */
|
||||||
/* If the LRU field underflows (since LRU it is a wrapping
|
/* If the LRU field underflows (since LRU it is a wrapping
|
||||||
* clock), the best we can do is to provide a large enough LRU
|
* clock), the best we can do is to provide a large enough LRU
|
||||||
@ -1223,7 +1224,9 @@ void objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
|
|||||||
if (lru_abs < 0)
|
if (lru_abs < 0)
|
||||||
lru_abs = (lru_clock+(LRU_CLOCK_MAX/2)) % LRU_CLOCK_MAX;
|
lru_abs = (lru_clock+(LRU_CLOCK_MAX/2)) % LRU_CLOCK_MAX;
|
||||||
val->lru = lru_abs;
|
val->lru = lru_abs;
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ======================= The OBJECT and MEMORY commands =================== */
|
/* ======================= The OBJECT and MEMORY commands =================== */
|
||||||
|
@ -1673,6 +1673,7 @@ int raxSeek(raxIterator *it, const char *op, unsigned char *ele, size_t len) {
|
|||||||
* node, but will be our match, representing the key "f".
|
* node, but will be our match, representing the key "f".
|
||||||
*
|
*
|
||||||
* So in that case, we don't seek backward. */
|
* So in that case, we don't seek backward. */
|
||||||
|
it->data = raxGetData(it->node);
|
||||||
} else {
|
} else {
|
||||||
if (gt && !raxIteratorNextStep(it,0)) return 0;
|
if (gt && !raxIteratorNextStep(it,0)) return 0;
|
||||||
if (lt && !raxIteratorPrevStep(it,0)) return 0;
|
if (lt && !raxIteratorPrevStep(it,0)) return 0;
|
||||||
@ -1791,7 +1792,7 @@ int raxCompare(raxIterator *iter, const char *op, unsigned char *key, size_t key
|
|||||||
if (eq && key_len == iter->key_len) return 1;
|
if (eq && key_len == iter->key_len) return 1;
|
||||||
else if (lt) return iter->key_len < key_len;
|
else if (lt) return iter->key_len < key_len;
|
||||||
else if (gt) return iter->key_len > key_len;
|
else if (gt) return iter->key_len > key_len;
|
||||||
return 0;
|
else return 0; /* Avoid warning, just 'eq' is handled before. */
|
||||||
} else if (cmp > 0) {
|
} else if (cmp > 0) {
|
||||||
return gt ? 1 : 0;
|
return gt ? 1 : 0;
|
||||||
} else /* (cmp < 0) */ {
|
} else /* (cmp < 0) */ {
|
||||||
|
84
src/rdb.c
84
src/rdb.c
@ -1080,9 +1080,9 @@ ssize_t rdbSaveAuxFieldStrInt(rio *rdb, char *key, long long val) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Save a few default AUX fields with information about the RDB generated. */
|
/* Save a few default AUX fields with information about the RDB generated. */
|
||||||
int rdbSaveInfoAuxFields(rio *rdb, int flags, rdbSaveInfo *rsi) {
|
int rdbSaveInfoAuxFields(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||||
int redis_bits = (sizeof(void*) == 8) ? 64 : 32;
|
int redis_bits = (sizeof(void*) == 8) ? 64 : 32;
|
||||||
int aof_preamble = (flags & RDB_SAVE_AOF_PREAMBLE) != 0;
|
int aof_preamble = (rdbflags & RDBFLAGS_AOF_PREAMBLE) != 0;
|
||||||
|
|
||||||
/* Add a few fields about the state when the RDB was created. */
|
/* Add a few fields about the state when the RDB was created. */
|
||||||
if (rdbSaveAuxFieldStrStr(rdb,"redis-ver",REDIS_VERSION) == -1) return -1;
|
if (rdbSaveAuxFieldStrStr(rdb,"redis-ver",REDIS_VERSION) == -1) return -1;
|
||||||
@ -1150,7 +1150,7 @@ ssize_t rdbSaveSingleModuleAux(rio *rdb, int when, moduleType *mt) {
|
|||||||
* When the function returns C_ERR and if 'error' is not NULL, the
|
* When the function returns C_ERR and if 'error' is not NULL, the
|
||||||
* integer pointed by 'error' is set to the value of errno just after the I/O
|
* integer pointed by 'error' is set to the value of errno just after the I/O
|
||||||
* error. */
|
* error. */
|
||||||
int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) {
|
int rdbSaveRio(rio *rdb, int *error, int rdbflags, rdbSaveInfo *rsi) {
|
||||||
dictIterator *di = NULL;
|
dictIterator *di = NULL;
|
||||||
dictEntry *de;
|
dictEntry *de;
|
||||||
char magic[10];
|
char magic[10];
|
||||||
@ -1162,7 +1162,7 @@ int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) {
|
|||||||
rdb->update_cksum = rioGenericUpdateChecksum;
|
rdb->update_cksum = rioGenericUpdateChecksum;
|
||||||
snprintf(magic,sizeof(magic),"REDIS%04d",RDB_VERSION);
|
snprintf(magic,sizeof(magic),"REDIS%04d",RDB_VERSION);
|
||||||
if (rdbWriteRaw(rdb,magic,9) == -1) goto werr;
|
if (rdbWriteRaw(rdb,magic,9) == -1) goto werr;
|
||||||
if (rdbSaveInfoAuxFields(rdb,flags,rsi) == -1) goto werr;
|
if (rdbSaveInfoAuxFields(rdb,rdbflags,rsi) == -1) goto werr;
|
||||||
if (rdbSaveModulesAux(rdb, REDISMODULE_AUX_BEFORE_RDB) == -1) goto werr;
|
if (rdbSaveModulesAux(rdb, REDISMODULE_AUX_BEFORE_RDB) == -1) goto werr;
|
||||||
|
|
||||||
for (j = 0; j < server.dbnum; j++) {
|
for (j = 0; j < server.dbnum; j++) {
|
||||||
@ -1199,7 +1199,7 @@ int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) {
|
|||||||
/* When this RDB is produced as part of an AOF rewrite, move
|
/* When this RDB is produced as part of an AOF rewrite, move
|
||||||
* accumulated diff from parent to child while rewriting in
|
* accumulated diff from parent to child while rewriting in
|
||||||
* order to have a smaller final write. */
|
* order to have a smaller final write. */
|
||||||
if (flags & RDB_SAVE_AOF_PREAMBLE &&
|
if (rdbflags & RDBFLAGS_AOF_PREAMBLE &&
|
||||||
rdb->processed_bytes > processed+AOF_READ_DIFF_INTERVAL_BYTES)
|
rdb->processed_bytes > processed+AOF_READ_DIFF_INTERVAL_BYTES)
|
||||||
{
|
{
|
||||||
processed = rdb->processed_bytes;
|
processed = rdb->processed_bytes;
|
||||||
@ -1254,18 +1254,21 @@ werr:
|
|||||||
int rdbSaveRioWithEOFMark(rio *rdb, int *error, rdbSaveInfo *rsi) {
|
int rdbSaveRioWithEOFMark(rio *rdb, int *error, rdbSaveInfo *rsi) {
|
||||||
char eofmark[RDB_EOF_MARK_SIZE];
|
char eofmark[RDB_EOF_MARK_SIZE];
|
||||||
|
|
||||||
|
startSaving(RDBFLAGS_REPLICATION);
|
||||||
getRandomHexChars(eofmark,RDB_EOF_MARK_SIZE);
|
getRandomHexChars(eofmark,RDB_EOF_MARK_SIZE);
|
||||||
if (error) *error = 0;
|
if (error) *error = 0;
|
||||||
if (rioWrite(rdb,"$EOF:",5) == 0) goto werr;
|
if (rioWrite(rdb,"$EOF:",5) == 0) goto werr;
|
||||||
if (rioWrite(rdb,eofmark,RDB_EOF_MARK_SIZE) == 0) goto werr;
|
if (rioWrite(rdb,eofmark,RDB_EOF_MARK_SIZE) == 0) goto werr;
|
||||||
if (rioWrite(rdb,"\r\n",2) == 0) goto werr;
|
if (rioWrite(rdb,"\r\n",2) == 0) goto werr;
|
||||||
if (rdbSaveRio(rdb,error,RDB_SAVE_NONE,rsi) == C_ERR) goto werr;
|
if (rdbSaveRio(rdb,error,RDBFLAGS_NONE,rsi) == C_ERR) goto werr;
|
||||||
if (rioWrite(rdb,eofmark,RDB_EOF_MARK_SIZE) == 0) goto werr;
|
if (rioWrite(rdb,eofmark,RDB_EOF_MARK_SIZE) == 0) goto werr;
|
||||||
|
stopSaving(1);
|
||||||
return C_OK;
|
return C_OK;
|
||||||
|
|
||||||
werr: /* Write error. */
|
werr: /* Write error. */
|
||||||
/* Set 'error' only if not already set by rdbSaveRio() call. */
|
/* Set 'error' only if not already set by rdbSaveRio() call. */
|
||||||
if (error && *error == 0) *error = errno;
|
if (error && *error == 0) *error = errno;
|
||||||
|
stopSaving(0);
|
||||||
return C_ERR;
|
return C_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1291,11 +1294,12 @@ int rdbSave(char *filename, rdbSaveInfo *rsi) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rioInitWithFile(&rdb,fp);
|
rioInitWithFile(&rdb,fp);
|
||||||
|
startSaving(RDBFLAGS_NONE);
|
||||||
|
|
||||||
if (server.rdb_save_incremental_fsync)
|
if (server.rdb_save_incremental_fsync)
|
||||||
rioSetAutoSync(&rdb,REDIS_AUTOSYNC_BYTES);
|
rioSetAutoSync(&rdb,REDIS_AUTOSYNC_BYTES);
|
||||||
|
|
||||||
if (rdbSaveRio(&rdb,&error,RDB_SAVE_NONE,rsi) == C_ERR) {
|
if (rdbSaveRio(&rdb,&error,RDBFLAGS_NONE,rsi) == C_ERR) {
|
||||||
errno = error;
|
errno = error;
|
||||||
goto werr;
|
goto werr;
|
||||||
}
|
}
|
||||||
@ -1317,6 +1321,7 @@ int rdbSave(char *filename, rdbSaveInfo *rsi) {
|
|||||||
cwdp ? cwdp : "unknown",
|
cwdp ? cwdp : "unknown",
|
||||||
strerror(errno));
|
strerror(errno));
|
||||||
unlink(tmpfile);
|
unlink(tmpfile);
|
||||||
|
stopSaving(0);
|
||||||
return C_ERR;
|
return C_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1324,12 +1329,14 @@ int rdbSave(char *filename, rdbSaveInfo *rsi) {
|
|||||||
server.dirty = 0;
|
server.dirty = 0;
|
||||||
server.lastsave = time(NULL);
|
server.lastsave = time(NULL);
|
||||||
server.lastbgsave_status = C_OK;
|
server.lastbgsave_status = C_OK;
|
||||||
|
stopSaving(1);
|
||||||
return C_OK;
|
return C_OK;
|
||||||
|
|
||||||
werr:
|
werr:
|
||||||
serverLog(LL_WARNING,"Write error saving DB on disk: %s", strerror(errno));
|
serverLog(LL_WARNING,"Write error saving DB on disk: %s", strerror(errno));
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
unlink(tmpfile);
|
unlink(tmpfile);
|
||||||
|
stopSaving(0);
|
||||||
return C_ERR;
|
return C_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1918,23 +1925,33 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
|
|||||||
|
|
||||||
/* Mark that we are loading in the global state and setup the fields
|
/* Mark that we are loading in the global state and setup the fields
|
||||||
* needed to provide loading stats. */
|
* needed to provide loading stats. */
|
||||||
void startLoading(size_t size) {
|
void startLoading(size_t size, int rdbflags) {
|
||||||
/* Load the DB */
|
/* Load the DB */
|
||||||
server.loading = 1;
|
server.loading = 1;
|
||||||
server.loading_start_time = time(NULL);
|
server.loading_start_time = time(NULL);
|
||||||
server.loading_loaded_bytes = 0;
|
server.loading_loaded_bytes = 0;
|
||||||
server.loading_total_bytes = size;
|
server.loading_total_bytes = size;
|
||||||
|
|
||||||
|
/* Fire the loading modules start event. */
|
||||||
|
int subevent;
|
||||||
|
if (rdbflags & RDBFLAGS_AOF_PREAMBLE)
|
||||||
|
subevent = REDISMODULE_SUBEVENT_LOADING_AOF_START;
|
||||||
|
else if(rdbflags & RDBFLAGS_REPLICATION)
|
||||||
|
subevent = REDISMODULE_SUBEVENT_LOADING_REPL_START;
|
||||||
|
else
|
||||||
|
subevent = REDISMODULE_SUBEVENT_LOADING_RDB_START;
|
||||||
|
moduleFireServerEvent(REDISMODULE_EVENT_LOADING,subevent,NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Mark that we are loading in the global state and setup the fields
|
/* Mark that we are loading in the global state and setup the fields
|
||||||
* needed to provide loading stats.
|
* needed to provide loading stats.
|
||||||
* 'filename' is optional and used for rdb-check on error */
|
* 'filename' is optional and used for rdb-check on error */
|
||||||
void startLoadingFile(FILE *fp, char* filename) {
|
void startLoadingFile(FILE *fp, char* filename, int rdbflags) {
|
||||||
struct stat sb;
|
struct stat sb;
|
||||||
if (fstat(fileno(fp), &sb) == -1)
|
if (fstat(fileno(fp), &sb) == -1)
|
||||||
sb.st_size = 0;
|
sb.st_size = 0;
|
||||||
rdbFileBeingLoaded = filename;
|
rdbFileBeingLoaded = filename;
|
||||||
startLoading(sb.st_size);
|
startLoading(sb.st_size, rdbflags);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Refresh the loading progress info */
|
/* Refresh the loading progress info */
|
||||||
@ -1945,9 +1962,37 @@ void loadingProgress(off_t pos) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Loading finished */
|
/* Loading finished */
|
||||||
void stopLoading(void) {
|
void stopLoading(int success) {
|
||||||
server.loading = 0;
|
server.loading = 0;
|
||||||
rdbFileBeingLoaded = NULL;
|
rdbFileBeingLoaded = NULL;
|
||||||
|
|
||||||
|
/* Fire the loading modules end event. */
|
||||||
|
moduleFireServerEvent(REDISMODULE_EVENT_LOADING,
|
||||||
|
success?
|
||||||
|
REDISMODULE_SUBEVENT_LOADING_ENDED:
|
||||||
|
REDISMODULE_SUBEVENT_LOADING_FAILED,
|
||||||
|
NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void startSaving(int rdbflags) {
|
||||||
|
/* Fire the persistence modules end event. */
|
||||||
|
int subevent;
|
||||||
|
if (rdbflags & RDBFLAGS_AOF_PREAMBLE)
|
||||||
|
subevent = REDISMODULE_SUBEVENT_PERSISTENCE_AOF_START;
|
||||||
|
else if (getpid()!=server.pid)
|
||||||
|
subevent = REDISMODULE_SUBEVENT_PERSISTENCE_RDB_START;
|
||||||
|
else
|
||||||
|
subevent = REDISMODULE_SUBEVENT_PERSISTENCE_SYNC_RDB_START;
|
||||||
|
moduleFireServerEvent(REDISMODULE_EVENT_PERSISTENCE,subevent,NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void stopSaving(int success) {
|
||||||
|
/* Fire the persistence modules end event. */
|
||||||
|
moduleFireServerEvent(REDISMODULE_EVENT_PERSISTENCE,
|
||||||
|
success?
|
||||||
|
REDISMODULE_SUBEVENT_PERSISTENCE_ENDED:
|
||||||
|
REDISMODULE_SUBEVENT_PERSISTENCE_FAILED,
|
||||||
|
NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Track loading progress in order to serve client's from time to time
|
/* Track loading progress in order to serve client's from time to time
|
||||||
@ -1961,17 +2006,18 @@ void rdbLoadProgressCallback(rio *r, const void *buf, size_t len) {
|
|||||||
/* The DB can take some non trivial amount of time to load. Update
|
/* The DB can take some non trivial amount of time to load. Update
|
||||||
* our cached time since it is used to create and update the last
|
* our cached time since it is used to create and update the last
|
||||||
* interaction time with clients and for other important things. */
|
* interaction time with clients and for other important things. */
|
||||||
updateCachedTime();
|
updateCachedTime(0);
|
||||||
if (server.masterhost && server.repl_state == REPL_STATE_TRANSFER)
|
if (server.masterhost && server.repl_state == REPL_STATE_TRANSFER)
|
||||||
replicationSendNewlineToMaster();
|
replicationSendNewlineToMaster();
|
||||||
loadingProgress(r->processed_bytes);
|
loadingProgress(r->processed_bytes);
|
||||||
processEventsWhileBlocked();
|
processEventsWhileBlocked();
|
||||||
|
processModuleLoadingProgressEvent(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Load an RDB file from the rio stream 'rdb'. On success C_OK is returned,
|
/* Load an RDB file from the rio stream 'rdb'. On success C_OK is returned,
|
||||||
* otherwise C_ERR is returned and 'errno' is set accordingly. */
|
* otherwise C_ERR is returned and 'errno' is set accordingly. */
|
||||||
int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
|
int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||||
uint64_t dbid;
|
uint64_t dbid;
|
||||||
int type, rdbver;
|
int type, rdbver;
|
||||||
redisDb *db = server.db+0;
|
redisDb *db = server.db+0;
|
||||||
@ -2182,7 +2228,7 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
|
|||||||
* received from the master. In the latter case, the master is
|
* received from the master. In the latter case, the master is
|
||||||
* responsible for key expiry. If we would expire keys here, the
|
* responsible for key expiry. If we would expire keys here, the
|
||||||
* snapshot taken by the master may not be reflected on the slave. */
|
* snapshot taken by the master may not be reflected on the slave. */
|
||||||
if (server.masterhost == NULL && !loading_aof && expiretime != -1 && expiretime < now) {
|
if (server.masterhost == NULL && !(rdbflags&RDBFLAGS_AOF_PREAMBLE) && expiretime != -1 && expiretime < now) {
|
||||||
decrRefCount(key);
|
decrRefCount(key);
|
||||||
decrRefCount(val);
|
decrRefCount(val);
|
||||||
} else {
|
} else {
|
||||||
@ -2193,7 +2239,7 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
|
|||||||
if (expiretime != -1) setExpire(NULL,db,key,expiretime);
|
if (expiretime != -1) setExpire(NULL,db,key,expiretime);
|
||||||
|
|
||||||
/* Set usage information (for eviction). */
|
/* Set usage information (for eviction). */
|
||||||
objectSetLRUOrLFU(val,lfu_freq,lru_idle,lru_clock);
|
objectSetLRUOrLFU(val,lfu_freq,lru_idle,lru_clock,1000);
|
||||||
|
|
||||||
/* Decrement the key refcount since dbAdd() will take its
|
/* Decrement the key refcount since dbAdd() will take its
|
||||||
* own reference. */
|
* own reference. */
|
||||||
@ -2243,17 +2289,17 @@ eoferr:
|
|||||||
*
|
*
|
||||||
* If you pass an 'rsi' structure initialied with RDB_SAVE_OPTION_INIT, the
|
* If you pass an 'rsi' structure initialied with RDB_SAVE_OPTION_INIT, the
|
||||||
* loading code will fiil the information fields in the structure. */
|
* loading code will fiil the information fields in the structure. */
|
||||||
int rdbLoad(char *filename, rdbSaveInfo *rsi) {
|
int rdbLoad(char *filename, rdbSaveInfo *rsi, int rdbflags) {
|
||||||
FILE *fp;
|
FILE *fp;
|
||||||
rio rdb;
|
rio rdb;
|
||||||
int retval;
|
int retval;
|
||||||
|
|
||||||
if ((fp = fopen(filename,"r")) == NULL) return C_ERR;
|
if ((fp = fopen(filename,"r")) == NULL) return C_ERR;
|
||||||
startLoadingFile(fp, filename);
|
startLoadingFile(fp, filename,rdbflags);
|
||||||
rioInitWithFile(&rdb,fp);
|
rioInitWithFile(&rdb,fp);
|
||||||
retval = rdbLoadRio(&rdb,rsi,0);
|
retval = rdbLoadRio(&rdb,rdbflags,rsi);
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
stopLoading();
|
stopLoading(retval==C_OK);
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
11
src/rdb.h
11
src/rdb.h
@ -121,8 +121,10 @@
|
|||||||
#define RDB_LOAD_PLAIN (1<<1)
|
#define RDB_LOAD_PLAIN (1<<1)
|
||||||
#define RDB_LOAD_SDS (1<<2)
|
#define RDB_LOAD_SDS (1<<2)
|
||||||
|
|
||||||
#define RDB_SAVE_NONE 0
|
/* flags on the purpose of rdb save or load */
|
||||||
#define RDB_SAVE_AOF_PREAMBLE (1<<0)
|
#define RDBFLAGS_NONE 0
|
||||||
|
#define RDBFLAGS_AOF_PREAMBLE (1<<0)
|
||||||
|
#define RDBFLAGS_REPLICATION (1<<1)
|
||||||
|
|
||||||
int rdbSaveType(rio *rdb, unsigned char type);
|
int rdbSaveType(rio *rdb, unsigned char type);
|
||||||
int rdbLoadType(rio *rdb);
|
int rdbLoadType(rio *rdb);
|
||||||
@ -135,7 +137,7 @@ uint64_t rdbLoadLen(rio *rdb, int *isencoded);
|
|||||||
int rdbLoadLenByRef(rio *rdb, int *isencoded, uint64_t *lenptr);
|
int rdbLoadLenByRef(rio *rdb, int *isencoded, uint64_t *lenptr);
|
||||||
int rdbSaveObjectType(rio *rdb, robj *o);
|
int rdbSaveObjectType(rio *rdb, robj *o);
|
||||||
int rdbLoadObjectType(rio *rdb);
|
int rdbLoadObjectType(rio *rdb);
|
||||||
int rdbLoad(char *filename, rdbSaveInfo *rsi);
|
int rdbLoad(char *filename, rdbSaveInfo *rsi, int rdbflags);
|
||||||
int rdbSaveBackground(char *filename, rdbSaveInfo *rsi);
|
int rdbSaveBackground(char *filename, rdbSaveInfo *rsi);
|
||||||
int rdbSaveToSlavesSockets(rdbSaveInfo *rsi);
|
int rdbSaveToSlavesSockets(rdbSaveInfo *rsi);
|
||||||
void rdbRemoveTempFile(pid_t childpid);
|
void rdbRemoveTempFile(pid_t childpid);
|
||||||
@ -154,7 +156,8 @@ int rdbSaveBinaryDoubleValue(rio *rdb, double val);
|
|||||||
int rdbLoadBinaryDoubleValue(rio *rdb, double *val);
|
int rdbLoadBinaryDoubleValue(rio *rdb, double *val);
|
||||||
int rdbSaveBinaryFloatValue(rio *rdb, float val);
|
int rdbSaveBinaryFloatValue(rio *rdb, float val);
|
||||||
int rdbLoadBinaryFloatValue(rio *rdb, float *val);
|
int rdbLoadBinaryFloatValue(rio *rdb, float *val);
|
||||||
int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof);
|
int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi);
|
||||||
|
int rdbSaveRio(rio *rdb, int *error, int rdbflags, rdbSaveInfo *rsi);
|
||||||
rdbSaveInfo *rdbPopulateSaveInfo(rdbSaveInfo *rsi);
|
rdbSaveInfo *rdbPopulateSaveInfo(rdbSaveInfo *rsi);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -202,7 +202,7 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
expiretime = -1;
|
expiretime = -1;
|
||||||
startLoadingFile(fp, rdbfilename);
|
startLoadingFile(fp, rdbfilename, RDBFLAGS_NONE);
|
||||||
while(1) {
|
while(1) {
|
||||||
robj *key, *val;
|
robj *key, *val;
|
||||||
|
|
||||||
@ -316,7 +316,7 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (closefile) fclose(fp);
|
if (closefile) fclose(fp);
|
||||||
stopLoading();
|
stopLoading(1);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
eoferr: /* unexpected end of file is handled here with a fatal exit */
|
eoferr: /* unexpected end of file is handled here with a fatal exit */
|
||||||
@ -327,7 +327,7 @@ eoferr: /* unexpected end of file is handled here with a fatal exit */
|
|||||||
}
|
}
|
||||||
err:
|
err:
|
||||||
if (closefile) fclose(fp);
|
if (closefile) fclose(fp);
|
||||||
stopLoading();
|
stopLoading(0);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,10 @@
|
|||||||
#define REDISMODULE_READ (1<<0)
|
#define REDISMODULE_READ (1<<0)
|
||||||
#define REDISMODULE_WRITE (1<<1)
|
#define REDISMODULE_WRITE (1<<1)
|
||||||
|
|
||||||
|
/* RedisModule_OpenKey extra flags for the 'mode' argument.
|
||||||
|
* Avoid touching the LRU/LFU of the key when opened. */
|
||||||
|
#define REDISMODULE_OPEN_KEY_NOTOUCH (1<<16)
|
||||||
|
|
||||||
#define REDISMODULE_LIST_HEAD 0
|
#define REDISMODULE_LIST_HEAD 0
|
||||||
#define REDISMODULE_LIST_TAIL 1
|
#define REDISMODULE_LIST_TAIL 1
|
||||||
|
|
||||||
@ -29,6 +33,7 @@
|
|||||||
#define REDISMODULE_KEYTYPE_SET 4
|
#define REDISMODULE_KEYTYPE_SET 4
|
||||||
#define REDISMODULE_KEYTYPE_ZSET 5
|
#define REDISMODULE_KEYTYPE_ZSET 5
|
||||||
#define REDISMODULE_KEYTYPE_MODULE 6
|
#define REDISMODULE_KEYTYPE_MODULE 6
|
||||||
|
#define REDISMODULE_KEYTYPE_STREAM 7
|
||||||
|
|
||||||
/* Reply types. */
|
/* Reply types. */
|
||||||
#define REDISMODULE_REPLY_UNKNOWN -1
|
#define REDISMODULE_REPLY_UNKNOWN -1
|
||||||
@ -181,6 +186,8 @@ typedef uint64_t RedisModuleTimerID;
|
|||||||
#define REDISMODULE_EVENT_REPLICA_CHANGE 6
|
#define REDISMODULE_EVENT_REPLICA_CHANGE 6
|
||||||
#define REDISMODULE_EVENT_MASTER_LINK_CHANGE 7
|
#define REDISMODULE_EVENT_MASTER_LINK_CHANGE 7
|
||||||
#define REDISMODULE_EVENT_CRON_LOOP 8
|
#define REDISMODULE_EVENT_CRON_LOOP 8
|
||||||
|
#define REDISMODULE_EVENT_MODULE_CHANGE 9
|
||||||
|
#define REDISMODULE_EVENT_LOADING_PROGRESS 10
|
||||||
|
|
||||||
typedef struct RedisModuleEvent {
|
typedef struct RedisModuleEvent {
|
||||||
uint64_t id; /* REDISMODULE_EVENT_... defines. */
|
uint64_t id; /* REDISMODULE_EVENT_... defines. */
|
||||||
@ -226,18 +233,28 @@ static const RedisModuleEvent
|
|||||||
RedisModuleEvent_MasterLinkChange = {
|
RedisModuleEvent_MasterLinkChange = {
|
||||||
REDISMODULE_EVENT_MASTER_LINK_CHANGE,
|
REDISMODULE_EVENT_MASTER_LINK_CHANGE,
|
||||||
1
|
1
|
||||||
|
},
|
||||||
|
RedisModuleEvent_ModuleChange = {
|
||||||
|
REDISMODULE_EVENT_MODULE_CHANGE,
|
||||||
|
1
|
||||||
|
},
|
||||||
|
RedisModuleEvent_LoadingProgress = {
|
||||||
|
REDISMODULE_EVENT_LOADING_PROGRESS,
|
||||||
|
1
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Those are values that are used for the 'subevent' callback argument. */
|
/* Those are values that are used for the 'subevent' callback argument. */
|
||||||
#define REDISMODULE_SUBEVENT_PERSISTENCE_RDB_START 0
|
#define REDISMODULE_SUBEVENT_PERSISTENCE_RDB_START 0
|
||||||
#define REDISMODULE_SUBEVENT_PERSISTENCE_RDB_END 1
|
#define REDISMODULE_SUBEVENT_PERSISTENCE_AOF_START 1
|
||||||
#define REDISMODULE_SUBEVENT_PERSISTENCE_AOF_START 2
|
#define REDISMODULE_SUBEVENT_PERSISTENCE_SYNC_RDB_START 2
|
||||||
#define REDISMODULE_SUBEVENT_PERSISTENCE_AOF_END 3
|
#define REDISMODULE_SUBEVENT_PERSISTENCE_ENDED 3
|
||||||
|
#define REDISMODULE_SUBEVENT_PERSISTENCE_FAILED 4
|
||||||
|
|
||||||
#define REDISMODULE_SUBEVENT_LOADING_RDB_START 0
|
#define REDISMODULE_SUBEVENT_LOADING_RDB_START 0
|
||||||
#define REDISMODULE_SUBEVENT_LOADING_RDB_END 1
|
#define REDISMODULE_SUBEVENT_LOADING_AOF_START 1
|
||||||
#define REDISMODULE_SUBEVENT_LOADING_AOF_START 2
|
#define REDISMODULE_SUBEVENT_LOADING_REPL_START 2
|
||||||
#define REDISMODULE_SUBEVENT_LOADING_AOF_END 3
|
#define REDISMODULE_SUBEVENT_LOADING_ENDED 3
|
||||||
|
#define REDISMODULE_SUBEVENT_LOADING_FAILED 4
|
||||||
|
|
||||||
#define REDISMODULE_SUBEVENT_CLIENT_CHANGE_CONNECTED 0
|
#define REDISMODULE_SUBEVENT_CLIENT_CHANGE_CONNECTED 0
|
||||||
#define REDISMODULE_SUBEVENT_CLIENT_CHANGE_DISCONNECTED 1
|
#define REDISMODULE_SUBEVENT_CLIENT_CHANGE_DISCONNECTED 1
|
||||||
@ -245,12 +262,21 @@ static const RedisModuleEvent
|
|||||||
#define REDISMODULE_SUBEVENT_MASTER_LINK_UP 0
|
#define REDISMODULE_SUBEVENT_MASTER_LINK_UP 0
|
||||||
#define REDISMODULE_SUBEVENT_MASTER_LINK_DOWN 1
|
#define REDISMODULE_SUBEVENT_MASTER_LINK_DOWN 1
|
||||||
|
|
||||||
#define REDISMODULE_SUBEVENT_REPLICA_CHANGE_CONNECTED 0
|
#define REDISMODULE_SUBEVENT_REPLICA_CHANGE_ONLINE 0
|
||||||
#define REDISMODULE_SUBEVENT_REPLICA_CHANGE_DISCONNECTED 1
|
#define REDISMODULE_SUBEVENT_REPLICA_CHANGE_OFFLINE 1
|
||||||
|
|
||||||
|
#define REDISMODULE_EVENT_REPLROLECHANGED_NOW_MASTER 0
|
||||||
|
#define REDISMODULE_EVENT_REPLROLECHANGED_NOW_REPLICA 1
|
||||||
|
|
||||||
#define REDISMODULE_SUBEVENT_FLUSHDB_START 0
|
#define REDISMODULE_SUBEVENT_FLUSHDB_START 0
|
||||||
#define REDISMODULE_SUBEVENT_FLUSHDB_END 1
|
#define REDISMODULE_SUBEVENT_FLUSHDB_END 1
|
||||||
|
|
||||||
|
#define REDISMODULE_SUBEVENT_MODULE_LOADED 0
|
||||||
|
#define REDISMODULE_SUBEVENT_MODULE_UNLOADED 1
|
||||||
|
|
||||||
|
#define REDISMODULE_SUBEVENT_LOADING_PROGRESS_RDB 0
|
||||||
|
#define REDISMODULE_SUBEVENT_LOADING_PROGRESS_AOF 1
|
||||||
|
|
||||||
/* RedisModuleClientInfo flags. */
|
/* RedisModuleClientInfo flags. */
|
||||||
#define REDISMODULE_CLIENTINFO_FLAG_SSL (1<<0)
|
#define REDISMODULE_CLIENTINFO_FLAG_SSL (1<<0)
|
||||||
#define REDISMODULE_CLIENTINFO_FLAG_PUBSUB (1<<1)
|
#define REDISMODULE_CLIENTINFO_FLAG_PUBSUB (1<<1)
|
||||||
@ -286,6 +312,22 @@ typedef struct RedisModuleClientInfo {
|
|||||||
|
|
||||||
#define RedisModuleClientInfo RedisModuleClientInfoV1
|
#define RedisModuleClientInfo RedisModuleClientInfoV1
|
||||||
|
|
||||||
|
#define REDISMODULE_REPLICATIONINFO_VERSION 1
|
||||||
|
typedef struct RedisModuleReplicationInfo {
|
||||||
|
uint64_t version; /* Not used since this structure is never passed
|
||||||
|
from the module to the core right now. Here
|
||||||
|
for future compatibility. */
|
||||||
|
int master; /* true if master, false if replica */
|
||||||
|
char *masterhost; /* master instance hostname for NOW_REPLICA */
|
||||||
|
int masterport; /* master instance port for NOW_REPLICA */
|
||||||
|
char *replid1; /* Main replication ID */
|
||||||
|
char *replid2; /* Secondary replication ID */
|
||||||
|
uint64_t repl1_offset; /* Main replication offset */
|
||||||
|
uint64_t repl2_offset; /* Offset of replid2 validity */
|
||||||
|
} RedisModuleReplicationInfoV1;
|
||||||
|
|
||||||
|
#define RedisModuleReplicationInfo RedisModuleReplicationInfoV1
|
||||||
|
|
||||||
#define REDISMODULE_FLUSHINFO_VERSION 1
|
#define REDISMODULE_FLUSHINFO_VERSION 1
|
||||||
typedef struct RedisModuleFlushInfo {
|
typedef struct RedisModuleFlushInfo {
|
||||||
uint64_t version; /* Not used since this structure is never passed
|
uint64_t version; /* Not used since this structure is never passed
|
||||||
@ -297,6 +339,39 @@ typedef struct RedisModuleFlushInfo {
|
|||||||
|
|
||||||
#define RedisModuleFlushInfo RedisModuleFlushInfoV1
|
#define RedisModuleFlushInfo RedisModuleFlushInfoV1
|
||||||
|
|
||||||
|
#define REDISMODULE_MODULE_CHANGE_VERSION 1
|
||||||
|
typedef struct RedisModuleModuleChange {
|
||||||
|
uint64_t version; /* Not used since this structure is never passed
|
||||||
|
from the module to the core right now. Here
|
||||||
|
for future compatibility. */
|
||||||
|
const char* module_name;/* Name of module loaded or unloaded. */
|
||||||
|
int32_t module_version; /* Module version. */
|
||||||
|
} RedisModuleModuleChangeV1;
|
||||||
|
|
||||||
|
#define RedisModuleModuleChange RedisModuleModuleChangeV1
|
||||||
|
|
||||||
|
#define REDISMODULE_CRON_LOOP_VERSION 1
|
||||||
|
typedef struct RedisModuleCronLoopInfo {
|
||||||
|
uint64_t version; /* Not used since this structure is never passed
|
||||||
|
from the module to the core right now. Here
|
||||||
|
for future compatibility. */
|
||||||
|
int32_t hz; /* Approximate number of events per second. */
|
||||||
|
} RedisModuleCronLoopV1;
|
||||||
|
|
||||||
|
#define RedisModuleCronLoop RedisModuleCronLoopV1
|
||||||
|
|
||||||
|
#define REDISMODULE_LOADING_PROGRESS_VERSION 1
|
||||||
|
typedef struct RedisModuleLoadingProgressInfo {
|
||||||
|
uint64_t version; /* Not used since this structure is never passed
|
||||||
|
from the module to the core right now. Here
|
||||||
|
for future compatibility. */
|
||||||
|
int32_t hz; /* Approximate number of events per second. */
|
||||||
|
int32_t progress; /* Approximate progress between 0 and 1024, or -1
|
||||||
|
* if unknown. */
|
||||||
|
} RedisModuleLoadingProgressV1;
|
||||||
|
|
||||||
|
#define RedisModuleLoadingProgress RedisModuleLoadingProgressV1
|
||||||
|
|
||||||
/* ------------------------- End of common defines ------------------------ */
|
/* ------------------------- End of common defines ------------------------ */
|
||||||
|
|
||||||
#ifndef REDISMODULE_CORE
|
#ifndef REDISMODULE_CORE
|
||||||
@ -319,6 +394,7 @@ typedef struct RedisModuleCommandFilterCtx RedisModuleCommandFilterCtx;
|
|||||||
typedef struct RedisModuleCommandFilter RedisModuleCommandFilter;
|
typedef struct RedisModuleCommandFilter RedisModuleCommandFilter;
|
||||||
typedef struct RedisModuleInfoCtx RedisModuleInfoCtx;
|
typedef struct RedisModuleInfoCtx RedisModuleInfoCtx;
|
||||||
typedef struct RedisModuleServerInfoData RedisModuleServerInfoData;
|
typedef struct RedisModuleServerInfoData RedisModuleServerInfoData;
|
||||||
|
typedef struct RedisModuleScanCursor RedisModuleScanCursor;
|
||||||
|
|
||||||
typedef int (*RedisModuleCmdFunc)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc);
|
typedef int (*RedisModuleCmdFunc)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc);
|
||||||
typedef void (*RedisModuleDisconnectFunc)(RedisModuleCtx *ctx, RedisModuleBlockedClient *bc);
|
typedef void (*RedisModuleDisconnectFunc)(RedisModuleCtx *ctx, RedisModuleBlockedClient *bc);
|
||||||
@ -336,6 +412,8 @@ typedef void (*RedisModuleTimerProc)(RedisModuleCtx *ctx, void *data);
|
|||||||
typedef void (*RedisModuleCommandFilterFunc) (RedisModuleCommandFilterCtx *filter);
|
typedef void (*RedisModuleCommandFilterFunc) (RedisModuleCommandFilterCtx *filter);
|
||||||
typedef void (*RedisModuleForkDoneHandler) (int exitcode, int bysignal, void *user_data);
|
typedef void (*RedisModuleForkDoneHandler) (int exitcode, int bysignal, void *user_data);
|
||||||
typedef void (*RedisModuleInfoFunc)(RedisModuleInfoCtx *ctx, int for_crash_report);
|
typedef void (*RedisModuleInfoFunc)(RedisModuleInfoCtx *ctx, int for_crash_report);
|
||||||
|
typedef void (*RedisModuleScanCB)(RedisModuleCtx *ctx, RedisModuleString *keyname, RedisModuleKey *key, void *privdata);
|
||||||
|
typedef void (*RedisModuleScanKeyCB)(RedisModuleKey *key, RedisModuleString *field, RedisModuleString *value, void *privdata);
|
||||||
|
|
||||||
#define REDISMODULE_TYPE_METHOD_VERSION 2
|
#define REDISMODULE_TYPE_METHOD_VERSION 2
|
||||||
typedef struct RedisModuleTypeMethods {
|
typedef struct RedisModuleTypeMethods {
|
||||||
@ -385,6 +463,7 @@ size_t REDISMODULE_API_FUNC(RedisModule_CallReplyLength)(RedisModuleCallReply *r
|
|||||||
RedisModuleCallReply *REDISMODULE_API_FUNC(RedisModule_CallReplyArrayElement)(RedisModuleCallReply *reply, size_t idx);
|
RedisModuleCallReply *REDISMODULE_API_FUNC(RedisModule_CallReplyArrayElement)(RedisModuleCallReply *reply, size_t idx);
|
||||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateString)(RedisModuleCtx *ctx, const char *ptr, size_t len);
|
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateString)(RedisModuleCtx *ctx, const char *ptr, size_t len);
|
||||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromLongLong)(RedisModuleCtx *ctx, long long ll);
|
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromLongLong)(RedisModuleCtx *ctx, long long ll);
|
||||||
|
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromLongDouble)(RedisModuleCtx *ctx, long double ld, int humanfriendly);
|
||||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromString)(RedisModuleCtx *ctx, const RedisModuleString *str);
|
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromString)(RedisModuleCtx *ctx, const RedisModuleString *str);
|
||||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringPrintf)(RedisModuleCtx *ctx, const char *fmt, ...);
|
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringPrintf)(RedisModuleCtx *ctx, const char *fmt, ...);
|
||||||
void REDISMODULE_API_FUNC(RedisModule_FreeString)(RedisModuleCtx *ctx, RedisModuleString *str);
|
void REDISMODULE_API_FUNC(RedisModule_FreeString)(RedisModuleCtx *ctx, RedisModuleString *str);
|
||||||
@ -402,9 +481,11 @@ int REDISMODULE_API_FUNC(RedisModule_ReplyWithEmptyString)(RedisModuleCtx *ctx);
|
|||||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithVerbatimString)(RedisModuleCtx *ctx, const char *buf, size_t len);
|
int REDISMODULE_API_FUNC(RedisModule_ReplyWithVerbatimString)(RedisModuleCtx *ctx, const char *buf, size_t len);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithNull)(RedisModuleCtx *ctx);
|
int REDISMODULE_API_FUNC(RedisModule_ReplyWithNull)(RedisModuleCtx *ctx);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithDouble)(RedisModuleCtx *ctx, double d);
|
int REDISMODULE_API_FUNC(RedisModule_ReplyWithDouble)(RedisModuleCtx *ctx, double d);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_ReplyWithLongDouble)(RedisModuleCtx *ctx, long double d);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithCallReply)(RedisModuleCtx *ctx, RedisModuleCallReply *reply);
|
int REDISMODULE_API_FUNC(RedisModule_ReplyWithCallReply)(RedisModuleCtx *ctx, RedisModuleCallReply *reply);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_StringToLongLong)(const RedisModuleString *str, long long *ll);
|
int REDISMODULE_API_FUNC(RedisModule_StringToLongLong)(const RedisModuleString *str, long long *ll);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_StringToDouble)(const RedisModuleString *str, double *d);
|
int REDISMODULE_API_FUNC(RedisModule_StringToDouble)(const RedisModuleString *str, double *d);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_StringToLongDouble)(const RedisModuleString *str, long double *d);
|
||||||
void REDISMODULE_API_FUNC(RedisModule_AutoMemory)(RedisModuleCtx *ctx);
|
void REDISMODULE_API_FUNC(RedisModule_AutoMemory)(RedisModuleCtx *ctx);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_Replicate)(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...);
|
int REDISMODULE_API_FUNC(RedisModule_Replicate)(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_ReplicateVerbatim)(RedisModuleCtx *ctx);
|
int REDISMODULE_API_FUNC(RedisModule_ReplicateVerbatim)(RedisModuleCtx *ctx);
|
||||||
@ -417,6 +498,9 @@ char *REDISMODULE_API_FUNC(RedisModule_StringDMA)(RedisModuleKey *key, size_t *l
|
|||||||
int REDISMODULE_API_FUNC(RedisModule_StringTruncate)(RedisModuleKey *key, size_t newlen);
|
int REDISMODULE_API_FUNC(RedisModule_StringTruncate)(RedisModuleKey *key, size_t newlen);
|
||||||
mstime_t REDISMODULE_API_FUNC(RedisModule_GetExpire)(RedisModuleKey *key);
|
mstime_t REDISMODULE_API_FUNC(RedisModule_GetExpire)(RedisModuleKey *key);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_SetExpire)(RedisModuleKey *key, mstime_t expire);
|
int REDISMODULE_API_FUNC(RedisModule_SetExpire)(RedisModuleKey *key, mstime_t expire);
|
||||||
|
void REDISMODULE_API_FUNC(RedisModule_ResetDataset)(int restart_aof, int async);
|
||||||
|
unsigned long long REDISMODULE_API_FUNC(RedisModule_DbSize)(RedisModuleCtx *ctx);
|
||||||
|
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_RandomKey)(RedisModuleCtx *ctx);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_ZsetAdd)(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr);
|
int REDISMODULE_API_FUNC(RedisModule_ZsetAdd)(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_ZsetIncrby)(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr, double *newscore);
|
int REDISMODULE_API_FUNC(RedisModule_ZsetIncrby)(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr, double *newscore);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_ZsetScore)(RedisModuleKey *key, RedisModuleString *ele, double *score);
|
int REDISMODULE_API_FUNC(RedisModule_ZsetScore)(RedisModuleKey *key, RedisModuleString *ele, double *score);
|
||||||
@ -436,10 +520,12 @@ int REDISMODULE_API_FUNC(RedisModule_IsKeysPositionRequest)(RedisModuleCtx *ctx)
|
|||||||
void REDISMODULE_API_FUNC(RedisModule_KeyAtPos)(RedisModuleCtx *ctx, int pos);
|
void REDISMODULE_API_FUNC(RedisModule_KeyAtPos)(RedisModuleCtx *ctx, int pos);
|
||||||
unsigned long long REDISMODULE_API_FUNC(RedisModule_GetClientId)(RedisModuleCtx *ctx);
|
unsigned long long REDISMODULE_API_FUNC(RedisModule_GetClientId)(RedisModuleCtx *ctx);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_GetClientInfoById)(void *ci, uint64_t id);
|
int REDISMODULE_API_FUNC(RedisModule_GetClientInfoById)(void *ci, uint64_t id);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_PublishMessage)(RedisModuleCtx *ctx, RedisModuleString *channel, RedisModuleString *message);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_GetContextFlags)(RedisModuleCtx *ctx);
|
int REDISMODULE_API_FUNC(RedisModule_GetContextFlags)(RedisModuleCtx *ctx);
|
||||||
void *REDISMODULE_API_FUNC(RedisModule_PoolAlloc)(RedisModuleCtx *ctx, size_t bytes);
|
void *REDISMODULE_API_FUNC(RedisModule_PoolAlloc)(RedisModuleCtx *ctx, size_t bytes);
|
||||||
RedisModuleType *REDISMODULE_API_FUNC(RedisModule_CreateDataType)(RedisModuleCtx *ctx, const char *name, int encver, RedisModuleTypeMethods *typemethods);
|
RedisModuleType *REDISMODULE_API_FUNC(RedisModule_CreateDataType)(RedisModuleCtx *ctx, const char *name, int encver, RedisModuleTypeMethods *typemethods);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_ModuleTypeSetValue)(RedisModuleKey *key, RedisModuleType *mt, void *value);
|
int REDISMODULE_API_FUNC(RedisModule_ModuleTypeSetValue)(RedisModuleKey *key, RedisModuleType *mt, void *value);
|
||||||
|
void *REDISMODULE_API_FUNC(RedisModule_ModuleTypeReplaceValue)(RedisModuleKey *key, RedisModuleType *mt, void *new_value);
|
||||||
RedisModuleType *REDISMODULE_API_FUNC(RedisModule_ModuleTypeGetType)(RedisModuleKey *key);
|
RedisModuleType *REDISMODULE_API_FUNC(RedisModule_ModuleTypeGetType)(RedisModuleKey *key);
|
||||||
void *REDISMODULE_API_FUNC(RedisModule_ModuleTypeGetValue)(RedisModuleKey *key);
|
void *REDISMODULE_API_FUNC(RedisModule_ModuleTypeGetValue)(RedisModuleKey *key);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_IsIOError)(RedisModuleIO *io);
|
int REDISMODULE_API_FUNC(RedisModule_IsIOError)(RedisModuleIO *io);
|
||||||
@ -458,6 +544,10 @@ void REDISMODULE_API_FUNC(RedisModule_SaveDouble)(RedisModuleIO *io, double valu
|
|||||||
double REDISMODULE_API_FUNC(RedisModule_LoadDouble)(RedisModuleIO *io);
|
double REDISMODULE_API_FUNC(RedisModule_LoadDouble)(RedisModuleIO *io);
|
||||||
void REDISMODULE_API_FUNC(RedisModule_SaveFloat)(RedisModuleIO *io, float value);
|
void REDISMODULE_API_FUNC(RedisModule_SaveFloat)(RedisModuleIO *io, float value);
|
||||||
float REDISMODULE_API_FUNC(RedisModule_LoadFloat)(RedisModuleIO *io);
|
float REDISMODULE_API_FUNC(RedisModule_LoadFloat)(RedisModuleIO *io);
|
||||||
|
void REDISMODULE_API_FUNC(RedisModule_SaveLongDouble)(RedisModuleIO *io, long double value);
|
||||||
|
long double REDISMODULE_API_FUNC(RedisModule_LoadLongDouble)(RedisModuleIO *io);
|
||||||
|
void *REDISMODULE_API_FUNC(RedisModule_LoadDataTypeFromString)(const RedisModuleString *str, const RedisModuleType *mt);
|
||||||
|
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_SaveDataTypeToString)(RedisModuleCtx *ctx, void *data, const RedisModuleType *mt);
|
||||||
void REDISMODULE_API_FUNC(RedisModule_Log)(RedisModuleCtx *ctx, const char *level, const char *fmt, ...);
|
void REDISMODULE_API_FUNC(RedisModule_Log)(RedisModuleCtx *ctx, const char *level, const char *fmt, ...);
|
||||||
void REDISMODULE_API_FUNC(RedisModule_LogIOError)(RedisModuleIO *io, const char *levelstr, const char *fmt, ...);
|
void REDISMODULE_API_FUNC(RedisModule_LogIOError)(RedisModuleIO *io, const char *levelstr, const char *fmt, ...);
|
||||||
void REDISMODULE_API_FUNC(RedisModule__Assert)(const char *estr, const char *file, int line);
|
void REDISMODULE_API_FUNC(RedisModule__Assert)(const char *estr, const char *file, int line);
|
||||||
@ -511,9 +601,18 @@ long long REDISMODULE_API_FUNC(RedisModule_ServerInfoGetFieldSigned)(RedisModule
|
|||||||
unsigned long long REDISMODULE_API_FUNC(RedisModule_ServerInfoGetFieldUnsigned)(RedisModuleServerInfoData *data, const char* field, int *out_err);
|
unsigned long long REDISMODULE_API_FUNC(RedisModule_ServerInfoGetFieldUnsigned)(RedisModuleServerInfoData *data, const char* field, int *out_err);
|
||||||
double REDISMODULE_API_FUNC(RedisModule_ServerInfoGetFieldDouble)(RedisModuleServerInfoData *data, const char* field, int *out_err);
|
double REDISMODULE_API_FUNC(RedisModule_ServerInfoGetFieldDouble)(RedisModuleServerInfoData *data, const char* field, int *out_err);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_SubscribeToServerEvent)(RedisModuleCtx *ctx, RedisModuleEvent event, RedisModuleEventCallback callback);
|
int REDISMODULE_API_FUNC(RedisModule_SubscribeToServerEvent)(RedisModuleCtx *ctx, RedisModuleEvent event, RedisModuleEventCallback callback);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_SetLRU)(RedisModuleKey *key, mstime_t lru_idle);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_GetLRU)(RedisModuleKey *key, mstime_t *lru_idle);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_SetLFU)(RedisModuleKey *key, long long lfu_freq);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_GetLFU)(RedisModuleKey *key, long long *lfu_freq);
|
||||||
RedisModuleBlockedClient *REDISMODULE_API_FUNC(RedisModule_BlockClientOnKeys)(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(RedisModuleCtx*,void*), long long timeout_ms, RedisModuleString **keys, int numkeys, void *privdata);
|
RedisModuleBlockedClient *REDISMODULE_API_FUNC(RedisModule_BlockClientOnKeys)(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(RedisModuleCtx*,void*), long long timeout_ms, RedisModuleString **keys, int numkeys, void *privdata);
|
||||||
void REDISMODULE_API_FUNC(RedisModule_SignalKeyAsReady)(RedisModuleCtx *ctx, RedisModuleString *key);
|
void REDISMODULE_API_FUNC(RedisModule_SignalKeyAsReady)(RedisModuleCtx *ctx, RedisModuleString *key);
|
||||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_GetBlockedClientReadyKey)(RedisModuleCtx *ctx);
|
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_GetBlockedClientReadyKey)(RedisModuleCtx *ctx);
|
||||||
|
RedisModuleScanCursor *REDISMODULE_API_FUNC(RedisModule_ScanCursorCreate)();
|
||||||
|
void REDISMODULE_API_FUNC(RedisModule_ScanCursorRestart)(RedisModuleScanCursor *cursor);
|
||||||
|
void REDISMODULE_API_FUNC(RedisModule_ScanCursorDestroy)(RedisModuleScanCursor *cursor);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_Scan)(RedisModuleCtx *ctx, RedisModuleScanCursor *cursor, RedisModuleScanCB fn, void *privdata);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_ScanKey)(RedisModuleKey *key, RedisModuleScanCursor *cursor, RedisModuleScanKeyCB fn, void *privdata);
|
||||||
|
|
||||||
/* Experimental APIs */
|
/* Experimental APIs */
|
||||||
#ifdef REDISMODULE_EXPERIMENTAL_API
|
#ifdef REDISMODULE_EXPERIMENTAL_API
|
||||||
@ -559,6 +658,8 @@ int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgDelete)(RedisModuleCommandF
|
|||||||
int REDISMODULE_API_FUNC(RedisModule_Fork)(RedisModuleForkDoneHandler cb, void *user_data);
|
int REDISMODULE_API_FUNC(RedisModule_Fork)(RedisModuleForkDoneHandler cb, void *user_data);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_ExitFromChild)(int retcode);
|
int REDISMODULE_API_FUNC(RedisModule_ExitFromChild)(int retcode);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_KillForkChild)(int child_pid);
|
int REDISMODULE_API_FUNC(RedisModule_KillForkChild)(int child_pid);
|
||||||
|
float REDISMODULE_API_FUNC(RedisModule_GetUsedMemoryRatio)();
|
||||||
|
size_t REDISMODULE_API_FUNC(RedisModule_MallocSize)(void* ptr);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define RedisModule_IsAOFClient(id) ((id) == UINT64_MAX)
|
#define RedisModule_IsAOFClient(id) ((id) == UINT64_MAX)
|
||||||
@ -592,6 +693,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
|||||||
REDISMODULE_GET_API(ReplyWithNull);
|
REDISMODULE_GET_API(ReplyWithNull);
|
||||||
REDISMODULE_GET_API(ReplyWithCallReply);
|
REDISMODULE_GET_API(ReplyWithCallReply);
|
||||||
REDISMODULE_GET_API(ReplyWithDouble);
|
REDISMODULE_GET_API(ReplyWithDouble);
|
||||||
|
REDISMODULE_GET_API(ReplyWithLongDouble);
|
||||||
REDISMODULE_GET_API(GetSelectedDb);
|
REDISMODULE_GET_API(GetSelectedDb);
|
||||||
REDISMODULE_GET_API(SelectDb);
|
REDISMODULE_GET_API(SelectDb);
|
||||||
REDISMODULE_GET_API(OpenKey);
|
REDISMODULE_GET_API(OpenKey);
|
||||||
@ -602,6 +704,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
|||||||
REDISMODULE_GET_API(ListPop);
|
REDISMODULE_GET_API(ListPop);
|
||||||
REDISMODULE_GET_API(StringToLongLong);
|
REDISMODULE_GET_API(StringToLongLong);
|
||||||
REDISMODULE_GET_API(StringToDouble);
|
REDISMODULE_GET_API(StringToDouble);
|
||||||
|
REDISMODULE_GET_API(StringToLongDouble);
|
||||||
REDISMODULE_GET_API(Call);
|
REDISMODULE_GET_API(Call);
|
||||||
REDISMODULE_GET_API(CallReplyProto);
|
REDISMODULE_GET_API(CallReplyProto);
|
||||||
REDISMODULE_GET_API(FreeCallReply);
|
REDISMODULE_GET_API(FreeCallReply);
|
||||||
@ -613,6 +716,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
|||||||
REDISMODULE_GET_API(CreateStringFromCallReply);
|
REDISMODULE_GET_API(CreateStringFromCallReply);
|
||||||
REDISMODULE_GET_API(CreateString);
|
REDISMODULE_GET_API(CreateString);
|
||||||
REDISMODULE_GET_API(CreateStringFromLongLong);
|
REDISMODULE_GET_API(CreateStringFromLongLong);
|
||||||
|
REDISMODULE_GET_API(CreateStringFromLongDouble);
|
||||||
REDISMODULE_GET_API(CreateStringFromString);
|
REDISMODULE_GET_API(CreateStringFromString);
|
||||||
REDISMODULE_GET_API(CreateStringPrintf);
|
REDISMODULE_GET_API(CreateStringPrintf);
|
||||||
REDISMODULE_GET_API(FreeString);
|
REDISMODULE_GET_API(FreeString);
|
||||||
@ -627,6 +731,9 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
|||||||
REDISMODULE_GET_API(StringTruncate);
|
REDISMODULE_GET_API(StringTruncate);
|
||||||
REDISMODULE_GET_API(GetExpire);
|
REDISMODULE_GET_API(GetExpire);
|
||||||
REDISMODULE_GET_API(SetExpire);
|
REDISMODULE_GET_API(SetExpire);
|
||||||
|
REDISMODULE_GET_API(ResetDataset);
|
||||||
|
REDISMODULE_GET_API(DbSize);
|
||||||
|
REDISMODULE_GET_API(RandomKey);
|
||||||
REDISMODULE_GET_API(ZsetAdd);
|
REDISMODULE_GET_API(ZsetAdd);
|
||||||
REDISMODULE_GET_API(ZsetIncrby);
|
REDISMODULE_GET_API(ZsetIncrby);
|
||||||
REDISMODULE_GET_API(ZsetScore);
|
REDISMODULE_GET_API(ZsetScore);
|
||||||
@ -649,6 +756,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
|||||||
REDISMODULE_GET_API(PoolAlloc);
|
REDISMODULE_GET_API(PoolAlloc);
|
||||||
REDISMODULE_GET_API(CreateDataType);
|
REDISMODULE_GET_API(CreateDataType);
|
||||||
REDISMODULE_GET_API(ModuleTypeSetValue);
|
REDISMODULE_GET_API(ModuleTypeSetValue);
|
||||||
|
REDISMODULE_GET_API(ModuleTypeReplaceValue);
|
||||||
REDISMODULE_GET_API(ModuleTypeGetType);
|
REDISMODULE_GET_API(ModuleTypeGetType);
|
||||||
REDISMODULE_GET_API(ModuleTypeGetValue);
|
REDISMODULE_GET_API(ModuleTypeGetValue);
|
||||||
REDISMODULE_GET_API(IsIOError);
|
REDISMODULE_GET_API(IsIOError);
|
||||||
@ -666,6 +774,10 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
|||||||
REDISMODULE_GET_API(LoadDouble);
|
REDISMODULE_GET_API(LoadDouble);
|
||||||
REDISMODULE_GET_API(SaveFloat);
|
REDISMODULE_GET_API(SaveFloat);
|
||||||
REDISMODULE_GET_API(LoadFloat);
|
REDISMODULE_GET_API(LoadFloat);
|
||||||
|
REDISMODULE_GET_API(SaveLongDouble);
|
||||||
|
REDISMODULE_GET_API(LoadLongDouble);
|
||||||
|
REDISMODULE_GET_API(SaveDataTypeToString);
|
||||||
|
REDISMODULE_GET_API(LoadDataTypeFromString);
|
||||||
REDISMODULE_GET_API(EmitAOF);
|
REDISMODULE_GET_API(EmitAOF);
|
||||||
REDISMODULE_GET_API(Log);
|
REDISMODULE_GET_API(Log);
|
||||||
REDISMODULE_GET_API(LogIOError);
|
REDISMODULE_GET_API(LogIOError);
|
||||||
@ -720,10 +832,20 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
|||||||
REDISMODULE_GET_API(ServerInfoGetFieldUnsigned);
|
REDISMODULE_GET_API(ServerInfoGetFieldUnsigned);
|
||||||
REDISMODULE_GET_API(ServerInfoGetFieldDouble);
|
REDISMODULE_GET_API(ServerInfoGetFieldDouble);
|
||||||
REDISMODULE_GET_API(GetClientInfoById);
|
REDISMODULE_GET_API(GetClientInfoById);
|
||||||
|
REDISMODULE_GET_API(PublishMessage);
|
||||||
REDISMODULE_GET_API(SubscribeToServerEvent);
|
REDISMODULE_GET_API(SubscribeToServerEvent);
|
||||||
|
REDISMODULE_GET_API(SetLRU);
|
||||||
|
REDISMODULE_GET_API(GetLRU);
|
||||||
|
REDISMODULE_GET_API(SetLFU);
|
||||||
|
REDISMODULE_GET_API(GetLFU);
|
||||||
REDISMODULE_GET_API(BlockClientOnKeys);
|
REDISMODULE_GET_API(BlockClientOnKeys);
|
||||||
REDISMODULE_GET_API(SignalKeyAsReady);
|
REDISMODULE_GET_API(SignalKeyAsReady);
|
||||||
REDISMODULE_GET_API(GetBlockedClientReadyKey);
|
REDISMODULE_GET_API(GetBlockedClientReadyKey);
|
||||||
|
REDISMODULE_GET_API(ScanCursorCreate);
|
||||||
|
REDISMODULE_GET_API(ScanCursorRestart);
|
||||||
|
REDISMODULE_GET_API(ScanCursorDestroy);
|
||||||
|
REDISMODULE_GET_API(Scan);
|
||||||
|
REDISMODULE_GET_API(ScanKey);
|
||||||
|
|
||||||
#ifdef REDISMODULE_EXPERIMENTAL_API
|
#ifdef REDISMODULE_EXPERIMENTAL_API
|
||||||
REDISMODULE_GET_API(GetThreadSafeContext);
|
REDISMODULE_GET_API(GetThreadSafeContext);
|
||||||
@ -767,6 +889,8 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
|||||||
REDISMODULE_GET_API(Fork);
|
REDISMODULE_GET_API(Fork);
|
||||||
REDISMODULE_GET_API(ExitFromChild);
|
REDISMODULE_GET_API(ExitFromChild);
|
||||||
REDISMODULE_GET_API(KillForkChild);
|
REDISMODULE_GET_API(KillForkChild);
|
||||||
|
REDISMODULE_GET_API(GetUsedMemoryRatio);
|
||||||
|
REDISMODULE_GET_API(MallocSize);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (RedisModule_IsModuleNameBusy && RedisModule_IsModuleNameBusy(name)) return REDISMODULE_ERR;
|
if (RedisModule_IsModuleNameBusy && RedisModule_IsModuleNameBusy(name)) return REDISMODULE_ERR;
|
||||||
|
@ -533,6 +533,12 @@ int masterTryPartialResynchronization(client *c) {
|
|||||||
* has this state from the previous connection with the master. */
|
* has this state from the previous connection with the master. */
|
||||||
|
|
||||||
refreshGoodSlavesCount();
|
refreshGoodSlavesCount();
|
||||||
|
|
||||||
|
/* Fire the replica change modules event. */
|
||||||
|
moduleFireServerEvent(REDISMODULE_EVENT_REPLICA_CHANGE,
|
||||||
|
REDISMODULE_SUBEVENT_REPLICA_CHANGE_ONLINE,
|
||||||
|
NULL);
|
||||||
|
|
||||||
return C_OK; /* The caller can return, no full resync needed. */
|
return C_OK; /* The caller can return, no full resync needed. */
|
||||||
|
|
||||||
need_full_resync:
|
need_full_resync:
|
||||||
@ -868,6 +874,10 @@ void putSlaveOnline(client *slave) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
refreshGoodSlavesCount();
|
refreshGoodSlavesCount();
|
||||||
|
/* Fire the replica change modules event. */
|
||||||
|
moduleFireServerEvent(REDISMODULE_EVENT_REPLICA_CHANGE,
|
||||||
|
REDISMODULE_SUBEVENT_REPLICA_CHANGE_ONLINE,
|
||||||
|
NULL);
|
||||||
serverLog(LL_NOTICE,"Synchronization with replica %s succeeded",
|
serverLog(LL_NOTICE,"Synchronization with replica %s succeeded",
|
||||||
replicationGetSlaveName(slave));
|
replicationGetSlaveName(slave));
|
||||||
}
|
}
|
||||||
@ -1542,11 +1552,11 @@ void readSyncBulkPayload(connection *conn) {
|
|||||||
* We'll restore it when the RDB is received. */
|
* We'll restore it when the RDB is received. */
|
||||||
connBlock(conn);
|
connBlock(conn);
|
||||||
connRecvTimeout(conn, server.repl_timeout*1000);
|
connRecvTimeout(conn, server.repl_timeout*1000);
|
||||||
startLoading(server.repl_transfer_size);
|
startLoading(server.repl_transfer_size, RDBFLAGS_REPLICATION);
|
||||||
|
|
||||||
if (rdbLoadRio(&rdb,&rsi,0) != C_OK) {
|
if (rdbLoadRio(&rdb,RDBFLAGS_REPLICATION,&rsi) != C_OK) {
|
||||||
/* RDB loading failed. */
|
/* RDB loading failed. */
|
||||||
stopLoading();
|
stopLoading(0);
|
||||||
serverLog(LL_WARNING,
|
serverLog(LL_WARNING,
|
||||||
"Failed trying to load the MASTER synchronization DB "
|
"Failed trying to load the MASTER synchronization DB "
|
||||||
"from socket");
|
"from socket");
|
||||||
@ -1567,7 +1577,7 @@ void readSyncBulkPayload(connection *conn) {
|
|||||||
* gets promoted. */
|
* gets promoted. */
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
stopLoading();
|
stopLoading(1);
|
||||||
|
|
||||||
/* RDB loading succeeded if we reach this point. */
|
/* RDB loading succeeded if we reach this point. */
|
||||||
if (server.repl_diskless_load == REPL_DISKLESS_LOAD_SWAPDB) {
|
if (server.repl_diskless_load == REPL_DISKLESS_LOAD_SWAPDB) {
|
||||||
@ -1614,7 +1624,7 @@ void readSyncBulkPayload(connection *conn) {
|
|||||||
cancelReplicationHandshake();
|
cancelReplicationHandshake();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (rdbLoad(server.rdb_filename,&rsi) != C_OK) {
|
if (rdbLoad(server.rdb_filename,&rsi,RDBFLAGS_REPLICATION) != C_OK) {
|
||||||
serverLog(LL_WARNING,
|
serverLog(LL_WARNING,
|
||||||
"Failed trying to load the MASTER synchronization "
|
"Failed trying to load the MASTER synchronization "
|
||||||
"DB from disk");
|
"DB from disk");
|
||||||
@ -1636,6 +1646,11 @@ void readSyncBulkPayload(connection *conn) {
|
|||||||
server.repl_state = REPL_STATE_CONNECTED;
|
server.repl_state = REPL_STATE_CONNECTED;
|
||||||
server.repl_down_since = 0;
|
server.repl_down_since = 0;
|
||||||
|
|
||||||
|
/* Fire the master link modules event. */
|
||||||
|
moduleFireServerEvent(REDISMODULE_EVENT_MASTER_LINK_CHANGE,
|
||||||
|
REDISMODULE_SUBEVENT_MASTER_LINK_UP,
|
||||||
|
NULL);
|
||||||
|
|
||||||
/* After a full resynchroniziation we use the replication ID and
|
/* After a full resynchroniziation we use the replication ID and
|
||||||
* offset of the master. The secondary ID / offset are cleared since
|
* offset of the master. The secondary ID / offset are cleared since
|
||||||
* we are starting a new history. */
|
* we are starting a new history. */
|
||||||
@ -2314,12 +2329,31 @@ void replicationSetMaster(char *ip, int port) {
|
|||||||
replicationDiscardCachedMaster();
|
replicationDiscardCachedMaster();
|
||||||
replicationCacheMasterUsingMyself();
|
replicationCacheMasterUsingMyself();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Fire the role change modules event. */
|
||||||
|
moduleFireServerEvent(REDISMODULE_EVENT_REPLICATION_ROLE_CHANGED,
|
||||||
|
REDISMODULE_EVENT_REPLROLECHANGED_NOW_REPLICA,
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
/* Fire the master link modules event. */
|
||||||
|
if (server.repl_state == REPL_STATE_CONNECTED)
|
||||||
|
moduleFireServerEvent(REDISMODULE_EVENT_MASTER_LINK_CHANGE,
|
||||||
|
REDISMODULE_SUBEVENT_MASTER_LINK_DOWN,
|
||||||
|
NULL);
|
||||||
|
|
||||||
server.repl_state = REPL_STATE_CONNECT;
|
server.repl_state = REPL_STATE_CONNECT;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Cancel replication, setting the instance as a master itself. */
|
/* Cancel replication, setting the instance as a master itself. */
|
||||||
void replicationUnsetMaster(void) {
|
void replicationUnsetMaster(void) {
|
||||||
if (server.masterhost == NULL) return; /* Nothing to do. */
|
if (server.masterhost == NULL) return; /* Nothing to do. */
|
||||||
|
|
||||||
|
/* Fire the master link modules event. */
|
||||||
|
if (server.repl_state == REPL_STATE_CONNECTED)
|
||||||
|
moduleFireServerEvent(REDISMODULE_EVENT_MASTER_LINK_CHANGE,
|
||||||
|
REDISMODULE_SUBEVENT_MASTER_LINK_DOWN,
|
||||||
|
NULL);
|
||||||
|
|
||||||
sdsfree(server.masterhost);
|
sdsfree(server.masterhost);
|
||||||
server.masterhost = NULL;
|
server.masterhost = NULL;
|
||||||
/* When a slave is turned into a master, the current replication ID
|
/* When a slave is turned into a master, the current replication ID
|
||||||
@ -2348,11 +2382,22 @@ void replicationUnsetMaster(void) {
|
|||||||
* starting from now. Otherwise the backlog will be freed after a
|
* starting from now. Otherwise the backlog will be freed after a
|
||||||
* failover if slaves do not connect immediately. */
|
* failover if slaves do not connect immediately. */
|
||||||
server.repl_no_slaves_since = server.unixtime;
|
server.repl_no_slaves_since = server.unixtime;
|
||||||
|
|
||||||
|
/* Fire the role change modules event. */
|
||||||
|
moduleFireServerEvent(REDISMODULE_EVENT_REPLICATION_ROLE_CHANGED,
|
||||||
|
REDISMODULE_EVENT_REPLROLECHANGED_NOW_MASTER,
|
||||||
|
NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This function is called when the slave lose the connection with the
|
/* This function is called when the slave lose the connection with the
|
||||||
* master into an unexpected way. */
|
* master into an unexpected way. */
|
||||||
void replicationHandleMasterDisconnection(void) {
|
void replicationHandleMasterDisconnection(void) {
|
||||||
|
/* Fire the master link modules event. */
|
||||||
|
if (server.repl_state == REPL_STATE_CONNECTED)
|
||||||
|
moduleFireServerEvent(REDISMODULE_EVENT_MASTER_LINK_CHANGE,
|
||||||
|
REDISMODULE_SUBEVENT_MASTER_LINK_DOWN,
|
||||||
|
NULL);
|
||||||
|
|
||||||
server.master = NULL;
|
server.master = NULL;
|
||||||
server.repl_state = REPL_STATE_CONNECT;
|
server.repl_state = REPL_STATE_CONNECT;
|
||||||
server.repl_down_since = server.unixtime;
|
server.repl_down_since = server.unixtime;
|
||||||
|
@ -3993,11 +3993,14 @@ int sentinelSendSlaveOf(sentinelRedisInstance *ri, char *host, int port) {
|
|||||||
* an issue because CLIENT is variadic command, so Redis will not
|
* an issue because CLIENT is variadic command, so Redis will not
|
||||||
* recognized as a syntax error, and the transaction will not fail (but
|
* recognized as a syntax error, and the transaction will not fail (but
|
||||||
* only the unsupported command will fail). */
|
* only the unsupported command will fail). */
|
||||||
|
for (int type = 0; type < 2; type++) {
|
||||||
retval = redisAsyncCommand(ri->link->cc,
|
retval = redisAsyncCommand(ri->link->cc,
|
||||||
sentinelDiscardReplyCallback, ri, "%s KILL TYPE normal",
|
sentinelDiscardReplyCallback, ri, "%s KILL TYPE %s",
|
||||||
sentinelInstanceMapCommand(ri,"CLIENT"));
|
sentinelInstanceMapCommand(ri,"CLIENT"),
|
||||||
|
type == 0 ? "normal" : "pubsub");
|
||||||
if (retval == C_ERR) return retval;
|
if (retval == C_ERR) return retval;
|
||||||
ri->link->pending_commands++;
|
ri->link->pending_commands++;
|
||||||
|
}
|
||||||
|
|
||||||
retval = redisAsyncCommand(ri->link->cc,
|
retval = redisAsyncCommand(ri->link->cc,
|
||||||
sentinelDiscardReplyCallback, ri, "%s",
|
sentinelDiscardReplyCallback, ri, "%s",
|
||||||
|
49
src/server.c
49
src/server.c
@ -1691,7 +1691,6 @@ void databasesCron(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Defrag keys gradually. */
|
/* Defrag keys gradually. */
|
||||||
if (server.active_defrag_enabled)
|
|
||||||
activeDefragCycle();
|
activeDefragCycle();
|
||||||
|
|
||||||
/* Perform hash tables rehashing if needed, but only if there are no
|
/* Perform hash tables rehashing if needed, but only if there are no
|
||||||
@ -1736,20 +1735,29 @@ void databasesCron(void) {
|
|||||||
/* We take a cached value of the unix time in the global state because with
|
/* We take a cached value of the unix time in the global state because with
|
||||||
* virtual memory and aging there is to store the current time in objects at
|
* virtual memory and aging there is to store the current time in objects at
|
||||||
* every object access, and accuracy is not needed. To access a global var is
|
* every object access, and accuracy is not needed. To access a global var is
|
||||||
* a lot faster than calling time(NULL) */
|
* a lot faster than calling time(NULL).
|
||||||
void updateCachedTime(void) {
|
*
|
||||||
server.unixtime = time(NULL);
|
* This function should be fast because it is called at every command execution
|
||||||
server.mstime = mstime();
|
* in call(), so it is possible to decide if to update the daylight saving
|
||||||
|
* info or not using the 'update_daylight_info' argument. Normally we update
|
||||||
|
* such info only when calling this function from serverCron() but not when
|
||||||
|
* calling it from call(). */
|
||||||
|
void updateCachedTime(int update_daylight_info) {
|
||||||
|
server.ustime = ustime();
|
||||||
|
server.mstime = server.ustime / 1000;
|
||||||
|
server.unixtime = server.mstime / 1000;
|
||||||
|
|
||||||
/* To get information about daylight saving time, we need to call
|
/* To get information about daylight saving time, we need to call
|
||||||
* localtime_r and cache the result. However calling localtime_r in this
|
* localtime_r and cache the result. However calling localtime_r in this
|
||||||
* context is safe since we will never fork() while here, in the main
|
* context is safe since we will never fork() while here, in the main
|
||||||
* thread. The logging function will call a thread safe version of
|
* thread. The logging function will call a thread safe version of
|
||||||
* localtime that has no locks. */
|
* localtime that has no locks. */
|
||||||
|
if (update_daylight_info) {
|
||||||
struct tm tm;
|
struct tm tm;
|
||||||
time_t ut = server.unixtime;
|
time_t ut = server.unixtime;
|
||||||
localtime_r(&ut,&tm);
|
localtime_r(&ut,&tm);
|
||||||
server.daylight_active = tm.tm_isdst;
|
server.daylight_active = tm.tm_isdst;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void checkChildrenDone(void) {
|
void checkChildrenDone(void) {
|
||||||
@ -1838,7 +1846,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
|||||||
if (server.watchdog_period) watchdogScheduleSignal(server.watchdog_period);
|
if (server.watchdog_period) watchdogScheduleSignal(server.watchdog_period);
|
||||||
|
|
||||||
/* Update the time cache. */
|
/* Update the time cache. */
|
||||||
updateCachedTime();
|
updateCachedTime(1);
|
||||||
|
|
||||||
server.hz = server.config_hz;
|
server.hz = server.config_hz;
|
||||||
/* Adapt the server.hz value to the number of configured clients. If we have
|
/* Adapt the server.hz value to the number of configured clients. If we have
|
||||||
@ -2056,6 +2064,12 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
|||||||
server.rdb_bgsave_scheduled = 0;
|
server.rdb_bgsave_scheduled = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Fire the cron loop modules event. */
|
||||||
|
RedisModuleCronLoopV1 ei = {REDISMODULE_CRON_LOOP_VERSION,server.hz};
|
||||||
|
moduleFireServerEvent(REDISMODULE_EVENT_CRON_LOOP,
|
||||||
|
0,
|
||||||
|
&ei);
|
||||||
|
|
||||||
server.cronloops++;
|
server.cronloops++;
|
||||||
return 1000/server.hz;
|
return 1000/server.hz;
|
||||||
}
|
}
|
||||||
@ -2252,7 +2266,7 @@ void createSharedObjects(void) {
|
|||||||
void initServerConfig(void) {
|
void initServerConfig(void) {
|
||||||
int j;
|
int j;
|
||||||
|
|
||||||
updateCachedTime();
|
updateCachedTime(1);
|
||||||
getRandomHexChars(server.runid,CONFIG_RUN_ID_SIZE);
|
getRandomHexChars(server.runid,CONFIG_RUN_ID_SIZE);
|
||||||
server.runid[CONFIG_RUN_ID_SIZE] = '\0';
|
server.runid[CONFIG_RUN_ID_SIZE] = '\0';
|
||||||
changeReplicationId();
|
changeReplicationId();
|
||||||
@ -2279,6 +2293,7 @@ void initServerConfig(void) {
|
|||||||
server.maxidletime = CONFIG_DEFAULT_CLIENT_TIMEOUT;
|
server.maxidletime = CONFIG_DEFAULT_CLIENT_TIMEOUT;
|
||||||
server.tcpkeepalive = CONFIG_DEFAULT_TCP_KEEPALIVE;
|
server.tcpkeepalive = CONFIG_DEFAULT_TCP_KEEPALIVE;
|
||||||
server.active_expire_enabled = 1;
|
server.active_expire_enabled = 1;
|
||||||
|
server.active_expire_effort = CONFIG_DEFAULT_ACTIVE_EXPIRE_EFFORT;
|
||||||
server.jemalloc_bg_thread = 1;
|
server.jemalloc_bg_thread = 1;
|
||||||
server.active_defrag_enabled = CONFIG_DEFAULT_ACTIVE_DEFRAG;
|
server.active_defrag_enabled = CONFIG_DEFAULT_ACTIVE_DEFRAG;
|
||||||
server.active_defrag_ignore_bytes = CONFIG_DEFAULT_DEFRAG_IGNORE_BYTES;
|
server.active_defrag_ignore_bytes = CONFIG_DEFAULT_DEFRAG_IGNORE_BYTES;
|
||||||
@ -2730,6 +2745,7 @@ void resetServerStats(void) {
|
|||||||
server.stat_expiredkeys = 0;
|
server.stat_expiredkeys = 0;
|
||||||
server.stat_expired_stale_perc = 0;
|
server.stat_expired_stale_perc = 0;
|
||||||
server.stat_expired_time_cap_reached_count = 0;
|
server.stat_expired_time_cap_reached_count = 0;
|
||||||
|
server.stat_expire_cycle_time_used = 0;
|
||||||
server.stat_evictedkeys = 0;
|
server.stat_evictedkeys = 0;
|
||||||
server.stat_keyspace_misses = 0;
|
server.stat_keyspace_misses = 0;
|
||||||
server.stat_keyspace_hits = 0;
|
server.stat_keyspace_hits = 0;
|
||||||
@ -2771,6 +2787,7 @@ void initServer(void) {
|
|||||||
server.hz = server.config_hz;
|
server.hz = server.config_hz;
|
||||||
server.pid = getpid();
|
server.pid = getpid();
|
||||||
server.current_client = NULL;
|
server.current_client = NULL;
|
||||||
|
server.fixed_time_expire = 0;
|
||||||
server.clients = listCreate();
|
server.clients = listCreate();
|
||||||
server.clients_index = raxNew();
|
server.clients_index = raxNew();
|
||||||
server.clients_to_close = listCreate();
|
server.clients_to_close = listCreate();
|
||||||
@ -2832,12 +2849,14 @@ void initServer(void) {
|
|||||||
for (j = 0; j < server.dbnum; j++) {
|
for (j = 0; j < server.dbnum; j++) {
|
||||||
server.db[j].dict = dictCreate(&dbDictType,NULL);
|
server.db[j].dict = dictCreate(&dbDictType,NULL);
|
||||||
server.db[j].expires = dictCreate(&keyptrDictType,NULL);
|
server.db[j].expires = dictCreate(&keyptrDictType,NULL);
|
||||||
|
server.db[j].expires_cursor = 0;
|
||||||
server.db[j].blocking_keys = dictCreate(&keylistDictType,NULL);
|
server.db[j].blocking_keys = dictCreate(&keylistDictType,NULL);
|
||||||
server.db[j].ready_keys = dictCreate(&objectKeyPointerValueDictType,NULL);
|
server.db[j].ready_keys = dictCreate(&objectKeyPointerValueDictType,NULL);
|
||||||
server.db[j].watched_keys = dictCreate(&keylistDictType,NULL);
|
server.db[j].watched_keys = dictCreate(&keylistDictType,NULL);
|
||||||
server.db[j].id = j;
|
server.db[j].id = j;
|
||||||
server.db[j].avg_ttl = 0;
|
server.db[j].avg_ttl = 0;
|
||||||
server.db[j].defrag_later = listCreate();
|
server.db[j].defrag_later = listCreate();
|
||||||
|
listSetFreeMethod(server.db[j].defrag_later,(void (*)(void*))sdsfree);
|
||||||
}
|
}
|
||||||
evictionPoolAlloc(); /* Initialize the LRU keys pool. */
|
evictionPoolAlloc(); /* Initialize the LRU keys pool. */
|
||||||
server.pubsub_channels = dictCreate(&keylistDictType,NULL);
|
server.pubsub_channels = dictCreate(&keylistDictType,NULL);
|
||||||
@ -3238,10 +3257,13 @@ void preventCommandReplication(client *c) {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
void call(client *c, int flags) {
|
void call(client *c, int flags) {
|
||||||
long long dirty, start, duration;
|
long long dirty;
|
||||||
|
ustime_t start, duration;
|
||||||
int client_old_flags = c->flags;
|
int client_old_flags = c->flags;
|
||||||
struct redisCommand *real_cmd = c->cmd;
|
struct redisCommand *real_cmd = c->cmd;
|
||||||
|
|
||||||
|
server.fixed_time_expire++;
|
||||||
|
|
||||||
/* Sent the command to clients in MONITOR mode, only if the commands are
|
/* Sent the command to clients in MONITOR mode, only if the commands are
|
||||||
* not generated from reading an AOF. */
|
* not generated from reading an AOF. */
|
||||||
if (listLength(server.monitors) &&
|
if (listLength(server.monitors) &&
|
||||||
@ -3259,7 +3281,8 @@ void call(client *c, int flags) {
|
|||||||
|
|
||||||
/* Call the command. */
|
/* Call the command. */
|
||||||
dirty = server.dirty;
|
dirty = server.dirty;
|
||||||
start = ustime();
|
updateCachedTime(0);
|
||||||
|
start = server.ustime;
|
||||||
c->cmd->proc(c);
|
c->cmd->proc(c);
|
||||||
duration = ustime()-start;
|
duration = ustime()-start;
|
||||||
dirty = server.dirty-dirty;
|
dirty = server.dirty-dirty;
|
||||||
@ -3366,6 +3389,7 @@ void call(client *c, int flags) {
|
|||||||
trackingRememberKeys(caller);
|
trackingRememberKeys(caller);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
server.fixed_time_expire--;
|
||||||
server.stat_numcommands++;
|
server.stat_numcommands++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3682,6 +3706,9 @@ int prepareForShutdown(int flags) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Fire the shutdown modules event. */
|
||||||
|
moduleFireServerEvent(REDISMODULE_EVENT_SHUTDOWN,0,NULL);
|
||||||
|
|
||||||
/* Remove the pid file if possible and needed. */
|
/* Remove the pid file if possible and needed. */
|
||||||
if (server.daemonize || server.pidfile) {
|
if (server.daemonize || server.pidfile) {
|
||||||
serverLog(LL_NOTICE,"Removing the pid file.");
|
serverLog(LL_NOTICE,"Removing the pid file.");
|
||||||
@ -4244,6 +4271,7 @@ sds genRedisInfoString(const char *section) {
|
|||||||
"expired_keys:%lld\r\n"
|
"expired_keys:%lld\r\n"
|
||||||
"expired_stale_perc:%.2f\r\n"
|
"expired_stale_perc:%.2f\r\n"
|
||||||
"expired_time_cap_reached_count:%lld\r\n"
|
"expired_time_cap_reached_count:%lld\r\n"
|
||||||
|
"expire_cycle_cpu_milliseconds:%lld\r\n"
|
||||||
"evicted_keys:%lld\r\n"
|
"evicted_keys:%lld\r\n"
|
||||||
"keyspace_hits:%lld\r\n"
|
"keyspace_hits:%lld\r\n"
|
||||||
"keyspace_misses:%lld\r\n"
|
"keyspace_misses:%lld\r\n"
|
||||||
@ -4271,6 +4299,7 @@ sds genRedisInfoString(const char *section) {
|
|||||||
server.stat_expiredkeys,
|
server.stat_expiredkeys,
|
||||||
server.stat_expired_stale_perc*100,
|
server.stat_expired_stale_perc*100,
|
||||||
server.stat_expired_time_cap_reached_count,
|
server.stat_expired_time_cap_reached_count,
|
||||||
|
server.stat_expire_cycle_time_used/1000,
|
||||||
server.stat_evictedkeys,
|
server.stat_evictedkeys,
|
||||||
server.stat_keyspace_hits,
|
server.stat_keyspace_hits,
|
||||||
server.stat_keyspace_misses,
|
server.stat_keyspace_misses,
|
||||||
@ -4767,7 +4796,7 @@ void loadDataFromDisk(void) {
|
|||||||
serverLog(LL_NOTICE,"DB loaded from append only file: %.3f seconds",(float)(ustime()-start)/1000000);
|
serverLog(LL_NOTICE,"DB loaded from append only file: %.3f seconds",(float)(ustime()-start)/1000000);
|
||||||
} else {
|
} else {
|
||||||
rdbSaveInfo rsi = RDB_SAVE_INFO_INIT;
|
rdbSaveInfo rsi = RDB_SAVE_INFO_INIT;
|
||||||
if (rdbLoad(server.rdb_filename,&rsi) == C_OK) {
|
if (rdbLoad(server.rdb_filename,&rsi,RDBFLAGS_NONE) == C_OK) {
|
||||||
serverLog(LL_NOTICE,"DB loaded from disk: %.3f seconds",
|
serverLog(LL_NOTICE,"DB loaded from disk: %.3f seconds",
|
||||||
(float)(ustime()-start)/1000000);
|
(float)(ustime()-start)/1000000);
|
||||||
|
|
||||||
|
37
src/server.h
37
src/server.h
@ -50,6 +50,7 @@
|
|||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
|
||||||
typedef long long mstime_t; /* millisecond time type. */
|
typedef long long mstime_t; /* millisecond time type. */
|
||||||
|
typedef long long ustime_t; /* microsecond time type. */
|
||||||
|
|
||||||
#include "ae.h" /* Event driven programming library */
|
#include "ae.h" /* Event driven programming library */
|
||||||
#include "sds.h" /* Dynamic safe strings */
|
#include "sds.h" /* Dynamic safe strings */
|
||||||
@ -173,15 +174,13 @@ typedef long long mstime_t; /* millisecond time type. */
|
|||||||
#define CONFIG_DEFAULT_DEFRAG_THRESHOLD_LOWER 10 /* don't defrag when fragmentation is below 10% */
|
#define CONFIG_DEFAULT_DEFRAG_THRESHOLD_LOWER 10 /* don't defrag when fragmentation is below 10% */
|
||||||
#define CONFIG_DEFAULT_DEFRAG_THRESHOLD_UPPER 100 /* maximum defrag force at 100% fragmentation */
|
#define CONFIG_DEFAULT_DEFRAG_THRESHOLD_UPPER 100 /* maximum defrag force at 100% fragmentation */
|
||||||
#define CONFIG_DEFAULT_DEFRAG_IGNORE_BYTES (100<<20) /* don't defrag if frag overhead is below 100mb */
|
#define CONFIG_DEFAULT_DEFRAG_IGNORE_BYTES (100<<20) /* don't defrag if frag overhead is below 100mb */
|
||||||
#define CONFIG_DEFAULT_DEFRAG_CYCLE_MIN 5 /* 5% CPU min (at lower threshold) */
|
#define CONFIG_DEFAULT_DEFRAG_CYCLE_MIN 1 /* 1% CPU min (at lower threshold) */
|
||||||
#define CONFIG_DEFAULT_DEFRAG_CYCLE_MAX 75 /* 75% CPU max (at upper threshold) */
|
#define CONFIG_DEFAULT_DEFRAG_CYCLE_MAX 25 /* 25% CPU max (at upper threshold) */
|
||||||
#define CONFIG_DEFAULT_DEFRAG_MAX_SCAN_FIELDS 1000 /* keys with more than 1000 fields will be processed separately */
|
#define CONFIG_DEFAULT_DEFRAG_MAX_SCAN_FIELDS 1000 /* keys with more than 1000 fields will be processed separately */
|
||||||
#define CONFIG_DEFAULT_PROTO_MAX_BULK_LEN (512ll*1024*1024) /* Bulk request max size */
|
#define CONFIG_DEFAULT_PROTO_MAX_BULK_LEN (512ll*1024*1024) /* Bulk request max size */
|
||||||
#define CONFIG_DEFAULT_TRACKING_TABLE_MAX_FILL 10 /* 10% tracking table max fill. */
|
#define CONFIG_DEFAULT_TRACKING_TABLE_MAX_FILL 10 /* 10% tracking table max fill. */
|
||||||
|
#define CONFIG_DEFAULT_ACTIVE_EXPIRE_EFFORT 1 /* From 1 to 10. */
|
||||||
|
|
||||||
#define ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 20 /* Loopkups per loop. */
|
|
||||||
#define ACTIVE_EXPIRE_CYCLE_FAST_DURATION 1000 /* Microseconds */
|
|
||||||
#define ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC 25 /* CPU max % for keys collection */
|
|
||||||
#define ACTIVE_EXPIRE_CYCLE_SLOW 0
|
#define ACTIVE_EXPIRE_CYCLE_SLOW 0
|
||||||
#define ACTIVE_EXPIRE_CYCLE_FAST 1
|
#define ACTIVE_EXPIRE_CYCLE_FAST 1
|
||||||
|
|
||||||
@ -720,6 +719,7 @@ typedef struct redisDb {
|
|||||||
dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */
|
dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */
|
||||||
int id; /* Database ID */
|
int id; /* Database ID */
|
||||||
long long avg_ttl; /* Average TTL, just for stats */
|
long long avg_ttl; /* Average TTL, just for stats */
|
||||||
|
unsigned long expires_cursor; /* Cursor of the active expire cycle. */
|
||||||
list *defrag_later; /* List of key names to attempt to defrag one by one, gradually. */
|
list *defrag_later; /* List of key names to attempt to defrag one by one, gradually. */
|
||||||
} redisDb;
|
} redisDb;
|
||||||
|
|
||||||
@ -1133,7 +1133,8 @@ struct redisServer {
|
|||||||
list *clients_pending_write; /* There is to write or install handler. */
|
list *clients_pending_write; /* There is to write or install handler. */
|
||||||
list *clients_pending_read; /* Client has pending read socket buffers. */
|
list *clients_pending_read; /* Client has pending read socket buffers. */
|
||||||
list *slaves, *monitors; /* List of slaves and MONITORs */
|
list *slaves, *monitors; /* List of slaves and MONITORs */
|
||||||
client *current_client; /* Current client, only used on crash report */
|
client *current_client; /* Current client executing the command. */
|
||||||
|
long fixed_time_expire; /* If > 0, expire keys against server.mstime. */
|
||||||
rax *clients_index; /* Active clients dictionary by client ID. */
|
rax *clients_index; /* Active clients dictionary by client ID. */
|
||||||
int clients_paused; /* True if clients are currently paused */
|
int clients_paused; /* True if clients are currently paused */
|
||||||
mstime_t clients_pause_end_time; /* Time when we undo clients_paused */
|
mstime_t clients_pause_end_time; /* Time when we undo clients_paused */
|
||||||
@ -1165,6 +1166,7 @@ struct redisServer {
|
|||||||
long long stat_expiredkeys; /* Number of expired keys */
|
long long stat_expiredkeys; /* Number of expired keys */
|
||||||
double stat_expired_stale_perc; /* Percentage of keys probably expired */
|
double stat_expired_stale_perc; /* Percentage of keys probably expired */
|
||||||
long long stat_expired_time_cap_reached_count; /* Early expire cylce stops.*/
|
long long stat_expired_time_cap_reached_count; /* Early expire cylce stops.*/
|
||||||
|
long long stat_expire_cycle_time_used; /* Cumulative microseconds used. */
|
||||||
long long stat_evictedkeys; /* Number of evicted keys (maxmemory) */
|
long long stat_evictedkeys; /* Number of evicted keys (maxmemory) */
|
||||||
long long stat_keyspace_hits; /* Number of successful lookups of keys */
|
long long stat_keyspace_hits; /* Number of successful lookups of keys */
|
||||||
long long stat_keyspace_misses; /* Number of failed lookups of keys */
|
long long stat_keyspace_misses; /* Number of failed lookups of keys */
|
||||||
@ -1203,6 +1205,7 @@ struct redisServer {
|
|||||||
int maxidletime; /* Client timeout in seconds */
|
int maxidletime; /* Client timeout in seconds */
|
||||||
int tcpkeepalive; /* Set SO_KEEPALIVE if non-zero. */
|
int tcpkeepalive; /* Set SO_KEEPALIVE if non-zero. */
|
||||||
int active_expire_enabled; /* Can be disabled for testing purposes. */
|
int active_expire_enabled; /* Can be disabled for testing purposes. */
|
||||||
|
int active_expire_effort; /* From 1 (default) to 10, active effort. */
|
||||||
int active_defrag_enabled;
|
int active_defrag_enabled;
|
||||||
int jemalloc_bg_thread; /* Enable jemalloc background thread */
|
int jemalloc_bg_thread; /* Enable jemalloc background thread */
|
||||||
size_t active_defrag_ignore_bytes; /* minimum amount of fragmentation waste to start active defrag */
|
size_t active_defrag_ignore_bytes; /* minimum amount of fragmentation waste to start active defrag */
|
||||||
@ -1400,7 +1403,8 @@ struct redisServer {
|
|||||||
_Atomic time_t unixtime; /* Unix time sampled every cron cycle. */
|
_Atomic time_t unixtime; /* Unix time sampled every cron cycle. */
|
||||||
time_t timezone; /* Cached timezone. As set by tzset(). */
|
time_t timezone; /* Cached timezone. As set by tzset(). */
|
||||||
int daylight_active; /* Currently in daylight saving time. */
|
int daylight_active; /* Currently in daylight saving time. */
|
||||||
long long mstime; /* 'unixtime' with milliseconds resolution. */
|
mstime_t mstime; /* 'unixtime' in milliseconds. */
|
||||||
|
ustime_t ustime; /* 'unixtime' in microseconds. */
|
||||||
/* Pubsub */
|
/* Pubsub */
|
||||||
dict *pubsub_channels; /* Map channels to list of subscribed clients */
|
dict *pubsub_channels; /* Map channels to list of subscribed clients */
|
||||||
list *pubsub_patterns; /* A list of pubsub_patterns */
|
list *pubsub_patterns; /* A list of pubsub_patterns */
|
||||||
@ -1602,6 +1606,7 @@ ssize_t rdbSaveModulesAux(rio *rdb, int when);
|
|||||||
int moduleAllDatatypesHandleErrors();
|
int moduleAllDatatypesHandleErrors();
|
||||||
sds modulesCollectInfo(sds info, const char *section, int for_crash_report, int sections);
|
sds modulesCollectInfo(sds info, const char *section, int for_crash_report, int sections);
|
||||||
void moduleFireServerEvent(uint64_t eid, int subid, void *data);
|
void moduleFireServerEvent(uint64_t eid, int subid, void *data);
|
||||||
|
void processModuleLoadingProgressEvent(int is_aof);
|
||||||
int moduleTryServeClientBlockedOnKey(client *c, robj *key);
|
int moduleTryServeClientBlockedOnKey(client *c, robj *key);
|
||||||
void moduleUnblockClient(client *c);
|
void moduleUnblockClient(client *c);
|
||||||
int moduleClientIsBlockedOnKeys(client *c);
|
int moduleClientIsBlockedOnKeys(client *c);
|
||||||
@ -1834,10 +1839,12 @@ void rdbPipeReadHandler(struct aeEventLoop *eventLoop, int fd, void *clientData,
|
|||||||
void rdbPipeWriteHandlerConnRemoved(struct connection *conn);
|
void rdbPipeWriteHandlerConnRemoved(struct connection *conn);
|
||||||
|
|
||||||
/* Generic persistence functions */
|
/* Generic persistence functions */
|
||||||
void startLoadingFile(FILE* fp, char* filename);
|
void startLoadingFile(FILE* fp, char* filename, int rdbflags);
|
||||||
void startLoading(size_t size);
|
void startLoading(size_t size, int rdbflags);
|
||||||
void loadingProgress(off_t pos);
|
void loadingProgress(off_t pos);
|
||||||
void stopLoading(void);
|
void stopLoading(int success);
|
||||||
|
void startSaving(int rdbflags);
|
||||||
|
void stopSaving(int success);
|
||||||
|
|
||||||
#define DISK_ERROR_TYPE_AOF 1 /* Don't accept writes: AOF errors. */
|
#define DISK_ERROR_TYPE_AOF 1 /* Don't accept writes: AOF errors. */
|
||||||
#define DISK_ERROR_TYPE_RDB 2 /* Don't accept writes: RDB errors. */
|
#define DISK_ERROR_TYPE_RDB 2 /* Don't accept writes: RDB errors. */
|
||||||
@ -1846,7 +1853,6 @@ int writeCommandsDeniedByDiskError(void);
|
|||||||
|
|
||||||
/* RDB persistence */
|
/* RDB persistence */
|
||||||
#include "rdb.h"
|
#include "rdb.h"
|
||||||
int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi);
|
|
||||||
void killRDBChild(void);
|
void killRDBChild(void);
|
||||||
|
|
||||||
/* AOF persistence */
|
/* AOF persistence */
|
||||||
@ -1862,6 +1868,7 @@ void aofRewriteBufferReset(void);
|
|||||||
unsigned long aofRewriteBufferSize(void);
|
unsigned long aofRewriteBufferSize(void);
|
||||||
ssize_t aofReadDiffFromParent(void);
|
ssize_t aofReadDiffFromParent(void);
|
||||||
void killAppendOnlyChild(void);
|
void killAppendOnlyChild(void);
|
||||||
|
void restartAOFAfterSYNC();
|
||||||
|
|
||||||
/* Child info */
|
/* Child info */
|
||||||
void openChildInfoPipe(void);
|
void openChildInfoPipe(void);
|
||||||
@ -1996,7 +2003,7 @@ void populateCommandTable(void);
|
|||||||
void resetCommandTableStats(void);
|
void resetCommandTableStats(void);
|
||||||
void adjustOpenFilesLimit(void);
|
void adjustOpenFilesLimit(void);
|
||||||
void closeListeningSockets(int unlink_unix_socket);
|
void closeListeningSockets(int unlink_unix_socket);
|
||||||
void updateCachedTime(void);
|
void updateCachedTime(int update_daylight_info);
|
||||||
void resetServerStats(void);
|
void resetServerStats(void);
|
||||||
void activeDefragCycle(void);
|
void activeDefragCycle(void);
|
||||||
unsigned int getLRUClock(void);
|
unsigned int getLRUClock(void);
|
||||||
@ -2082,10 +2089,11 @@ robj *lookupKeyWrite(redisDb *db, robj *key);
|
|||||||
robj *lookupKeyReadOrReply(client *c, robj *key, robj *reply);
|
robj *lookupKeyReadOrReply(client *c, robj *key, robj *reply);
|
||||||
robj *lookupKeyWriteOrReply(client *c, robj *key, robj *reply);
|
robj *lookupKeyWriteOrReply(client *c, robj *key, robj *reply);
|
||||||
robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags);
|
robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags);
|
||||||
|
robj *lookupKeyWriteWithFlags(redisDb *db, robj *key, int flags);
|
||||||
robj *objectCommandLookup(client *c, robj *key);
|
robj *objectCommandLookup(client *c, robj *key);
|
||||||
robj *objectCommandLookupOrReply(client *c, robj *key, robj *reply);
|
robj *objectCommandLookupOrReply(client *c, robj *key, robj *reply);
|
||||||
void objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
|
int objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
|
||||||
long long lru_clock);
|
long long lru_clock, int lru_multiplier);
|
||||||
#define LOOKUP_NONE 0
|
#define LOOKUP_NONE 0
|
||||||
#define LOOKUP_NOTOUCH (1<<0)
|
#define LOOKUP_NOTOUCH (1<<0)
|
||||||
void dbAdd(redisDb *db, robj *key, robj *val);
|
void dbAdd(redisDb *db, robj *key, robj *val);
|
||||||
@ -2101,6 +2109,7 @@ robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o);
|
|||||||
#define EMPTYDB_ASYNC (1<<0) /* Reclaim memory in another thread. */
|
#define EMPTYDB_ASYNC (1<<0) /* Reclaim memory in another thread. */
|
||||||
long long emptyDb(int dbnum, int flags, void(callback)(void*));
|
long long emptyDb(int dbnum, int flags, void(callback)(void*));
|
||||||
long long emptyDbGeneric(redisDb *dbarray, int dbnum, int flags, void(callback)(void*));
|
long long emptyDbGeneric(redisDb *dbarray, int dbnum, int flags, void(callback)(void*));
|
||||||
|
void flushAllDataAndResetRDB(int flags);
|
||||||
long long dbTotalServerKeyCount();
|
long long dbTotalServerKeyCount();
|
||||||
|
|
||||||
int selectDb(client *c, int id);
|
int selectDb(client *c, int id);
|
||||||
|
@ -98,6 +98,7 @@ struct client;
|
|||||||
|
|
||||||
stream *streamNew(void);
|
stream *streamNew(void);
|
||||||
void freeStream(stream *s);
|
void freeStream(stream *s);
|
||||||
|
unsigned long streamLength(const robj *subject);
|
||||||
size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end, size_t count, int rev, streamCG *group, streamConsumer *consumer, int flags, streamPropInfo *spi);
|
size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end, size_t count, int rev, streamCG *group, streamConsumer *consumer, int flags, streamPropInfo *spi);
|
||||||
void streamIteratorStart(streamIterator *si, stream *s, streamID *start, streamID *end, int rev);
|
void streamIteratorStart(streamIterator *si, stream *s, streamID *start, streamID *end, int rev);
|
||||||
int streamIteratorGetID(streamIterator *si, streamID *id, int64_t *numfields);
|
int streamIteratorGetID(streamIterator *si, streamID *id, int64_t *numfields);
|
||||||
|
@ -621,7 +621,7 @@ void hincrbyfloatCommand(client *c) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
char buf[MAX_LONG_DOUBLE_CHARS];
|
char buf[MAX_LONG_DOUBLE_CHARS];
|
||||||
int len = ld2string(buf,sizeof(buf),value,1);
|
int len = ld2string(buf,sizeof(buf),value,LD_STR_HUMAN);
|
||||||
new = sdsnewlen(buf,len);
|
new = sdsnewlen(buf,len);
|
||||||
hashTypeSet(o,c->argv[2]->ptr,new,HASH_SET_TAKE_VALUE);
|
hashTypeSet(o,c->argv[2]->ptr,new,HASH_SET_TAKE_VALUE);
|
||||||
addReplyBulkCBuffer(c,buf,len);
|
addReplyBulkCBuffer(c,buf,len);
|
||||||
|
@ -67,6 +67,12 @@ void freeStream(stream *s) {
|
|||||||
zfree(s);
|
zfree(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Return the length of a stream. */
|
||||||
|
unsigned long streamLength(const robj *subject) {
|
||||||
|
stream *s = subject->ptr;
|
||||||
|
return s->length;
|
||||||
|
}
|
||||||
|
|
||||||
/* Generate the next stream item ID given the previous one. If the current
|
/* Generate the next stream item ID given the previous one. If the current
|
||||||
* milliseconds Unix time is greater than the previous one, just use this
|
* milliseconds Unix time is greater than the previous one, just use this
|
||||||
* as time part and start with sequence part of zero. Otherwise we use the
|
* as time part and start with sequence part of zero. Otherwise we use the
|
||||||
@ -173,9 +179,19 @@ int streamCompareID(streamID *a, streamID *b) {
|
|||||||
* C_ERR if an ID was given via 'use_id', but adding it failed since the
|
* C_ERR if an ID was given via 'use_id', but adding it failed since the
|
||||||
* current top ID is greater or equal. */
|
* current top ID is greater or equal. */
|
||||||
int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_id, streamID *use_id) {
|
int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_id, streamID *use_id) {
|
||||||
/* If an ID was given, check that it's greater than the last entry ID
|
|
||||||
* or return an error. */
|
/* Generate the new entry ID. */
|
||||||
if (use_id && streamCompareID(use_id,&s->last_id) <= 0) return C_ERR;
|
streamID id;
|
||||||
|
if (use_id)
|
||||||
|
id = *use_id;
|
||||||
|
else
|
||||||
|
streamNextID(&s->last_id,&id);
|
||||||
|
|
||||||
|
/* Check that the new ID is greater than the last entry ID
|
||||||
|
* or return an error. Automatically generated IDs might
|
||||||
|
* overflow (and wrap-around) when incrementing the sequence
|
||||||
|
part. */
|
||||||
|
if (streamCompareID(&id,&s->last_id) <= 0) return C_ERR;
|
||||||
|
|
||||||
/* Add the new entry. */
|
/* Add the new entry. */
|
||||||
raxIterator ri;
|
raxIterator ri;
|
||||||
@ -192,13 +208,6 @@ int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_
|
|||||||
}
|
}
|
||||||
raxStop(&ri);
|
raxStop(&ri);
|
||||||
|
|
||||||
/* Generate the new entry ID. */
|
|
||||||
streamID id;
|
|
||||||
if (use_id)
|
|
||||||
id = *use_id;
|
|
||||||
else
|
|
||||||
streamNextID(&s->last_id,&id);
|
|
||||||
|
|
||||||
/* We have to add the key into the radix tree in lexicographic order,
|
/* We have to add the key into the radix tree in lexicographic order,
|
||||||
* to do so we consider the ID as a single 128 bit number written in
|
* to do so we consider the ID as a single 128 bit number written in
|
||||||
* big endian, so that the most significant bytes are the first ones. */
|
* big endian, so that the most significant bytes are the first ones. */
|
||||||
@ -1197,6 +1206,14 @@ void xaddCommand(client *c) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Return ASAP if minimal ID (0-0) was given so we avoid possibly creating
|
||||||
|
* a new stream and have streamAppendItem fail, leaving an empty key in the
|
||||||
|
* database. */
|
||||||
|
if (id_given && id.ms == 0 && id.seq == 0) {
|
||||||
|
addReplyError(c,"The ID specified in XADD must be greater than 0-0");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/* Lookup the stream at key. */
|
/* Lookup the stream at key. */
|
||||||
robj *o;
|
robj *o;
|
||||||
stream *s;
|
stream *s;
|
||||||
|
34
src/util.c
34
src/util.c
@ -551,15 +551,17 @@ int d2string(char *buf, size_t len, double value) {
|
|||||||
return len;
|
return len;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Convert a long double into a string. If humanfriendly is non-zero
|
/* Create a string object from a long double.
|
||||||
* it does not use exponential format and trims trailing zeroes at the end,
|
* If mode is humanfriendly it does not use exponential format and trims trailing
|
||||||
* however this results in loss of precision. Otherwise exp format is used
|
* zeroes at the end (may result in loss of precision).
|
||||||
* and the output of snprintf() is not modified.
|
* If mode is default exp format is used and the output of snprintf()
|
||||||
|
* is not modified (may result in loss of precision).
|
||||||
|
* If mode is hex hexadecimal format is used (no loss of precision)
|
||||||
*
|
*
|
||||||
* The function returns the length of the string or zero if there was not
|
* The function returns the length of the string or zero if there was not
|
||||||
* enough buffer room to store it. */
|
* enough buffer room to store it. */
|
||||||
int ld2string(char *buf, size_t len, long double value, int humanfriendly) {
|
int ld2string(char *buf, size_t len, long double value, ld2string_mode mode) {
|
||||||
size_t l;
|
size_t l = 0;
|
||||||
|
|
||||||
if (isinf(value)) {
|
if (isinf(value)) {
|
||||||
/* Libc in odd systems (Hi Solaris!) will format infinite in a
|
/* Libc in odd systems (Hi Solaris!) will format infinite in a
|
||||||
@ -572,13 +574,23 @@ int ld2string(char *buf, size_t len, long double value, int humanfriendly) {
|
|||||||
memcpy(buf,"-inf",4);
|
memcpy(buf,"-inf",4);
|
||||||
l = 4;
|
l = 4;
|
||||||
}
|
}
|
||||||
} else if (humanfriendly) {
|
} else {
|
||||||
|
switch (mode) {
|
||||||
|
case LD_STR_AUTO:
|
||||||
|
l = snprintf(buf,len,"%.17Lg",value);
|
||||||
|
if (l+1 > len) return 0; /* No room. */
|
||||||
|
break;
|
||||||
|
case LD_STR_HEX:
|
||||||
|
l = snprintf(buf,len,"%La",value);
|
||||||
|
if (l+1 > len) return 0; /* No room. */
|
||||||
|
break;
|
||||||
|
case LD_STR_HUMAN:
|
||||||
/* We use 17 digits precision since with 128 bit floats that precision
|
/* We use 17 digits precision since with 128 bit floats that precision
|
||||||
* after rounding is able to represent most small decimal numbers in a
|
* after rounding is able to represent most small decimal numbers in a
|
||||||
* way that is "non surprising" for the user (that is, most small
|
* way that is "non surprising" for the user (that is, most small
|
||||||
* decimal numbers will be represented in a way that when converted
|
* decimal numbers will be represented in a way that when converted
|
||||||
* back into a string are exactly the same as what the user typed.) */
|
* back into a string are exactly the same as what the user typed.) */
|
||||||
l = snprintf(buf,len,"%.17Lf", value);
|
l = snprintf(buf,len,"%.17Lf",value);
|
||||||
if (l+1 > len) return 0; /* No room. */
|
if (l+1 > len) return 0; /* No room. */
|
||||||
/* Now remove trailing zeroes after the '.' */
|
/* Now remove trailing zeroes after the '.' */
|
||||||
if (strchr(buf,'.') != NULL) {
|
if (strchr(buf,'.') != NULL) {
|
||||||
@ -589,9 +601,9 @@ int ld2string(char *buf, size_t len, long double value, int humanfriendly) {
|
|||||||
}
|
}
|
||||||
if (*p == '.') l--;
|
if (*p == '.') l--;
|
||||||
}
|
}
|
||||||
} else {
|
break;
|
||||||
l = snprintf(buf,len,"%.17Lg", value);
|
default: return 0; /* Invalid mode. */
|
||||||
if (l+1 > len) return 0; /* No room. */
|
}
|
||||||
}
|
}
|
||||||
buf[l] = '\0';
|
buf[l] = '\0';
|
||||||
return l;
|
return l;
|
||||||
|
@ -38,6 +38,13 @@
|
|||||||
* This should be the size of the buffer given to ld2string */
|
* This should be the size of the buffer given to ld2string */
|
||||||
#define MAX_LONG_DOUBLE_CHARS 5*1024
|
#define MAX_LONG_DOUBLE_CHARS 5*1024
|
||||||
|
|
||||||
|
/* long double to string convertion options */
|
||||||
|
typedef enum {
|
||||||
|
LD_STR_AUTO, /* %.17Lg */
|
||||||
|
LD_STR_HUMAN, /* %.17Lf + Trimming of trailing zeros */
|
||||||
|
LD_STR_HEX /* %La */
|
||||||
|
} ld2string_mode;
|
||||||
|
|
||||||
int stringmatchlen(const char *p, int plen, const char *s, int slen, int nocase);
|
int stringmatchlen(const char *p, int plen, const char *s, int slen, int nocase);
|
||||||
int stringmatch(const char *p, const char *s, int nocase);
|
int stringmatch(const char *p, const char *s, int nocase);
|
||||||
int stringmatchlen_fuzz_test(void);
|
int stringmatchlen_fuzz_test(void);
|
||||||
@ -51,7 +58,7 @@ int string2l(const char *s, size_t slen, long *value);
|
|||||||
int string2ld(const char *s, size_t slen, long double *dp);
|
int string2ld(const char *s, size_t slen, long double *dp);
|
||||||
int string2d(const char *s, size_t slen, double *dp);
|
int string2d(const char *s, size_t slen, double *dp);
|
||||||
int d2string(char *buf, size_t len, double value);
|
int d2string(char *buf, size_t len, double value);
|
||||||
int ld2string(char *buf, size_t len, long double value, int humanfriendly);
|
int ld2string(char *buf, size_t len, long double value, ld2string_mode mode);
|
||||||
sds getAbsolutePath(char *filename);
|
sds getAbsolutePath(char *filename);
|
||||||
unsigned long getTimeZone(void);
|
unsigned long getTimeZone(void);
|
||||||
int pathIsBaseName(char *path);
|
int pathIsBaseName(char *path);
|
||||||
|
@ -18,7 +18,10 @@ TEST_MODULES = \
|
|||||||
infotest.so \
|
infotest.so \
|
||||||
propagate.so \
|
propagate.so \
|
||||||
misc.so \
|
misc.so \
|
||||||
hooks.so
|
hooks.so \
|
||||||
|
blockonkeys.so \
|
||||||
|
scan.so \
|
||||||
|
datatype.so
|
||||||
|
|
||||||
.PHONY: all
|
.PHONY: all
|
||||||
|
|
||||||
|
261
tests/modules/blockonkeys.c
Normal file
261
tests/modules/blockonkeys.c
Normal file
@ -0,0 +1,261 @@
|
|||||||
|
#define REDISMODULE_EXPERIMENTAL_API
|
||||||
|
#include "redismodule.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#define LIST_SIZE 1024
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
long long list[LIST_SIZE];
|
||||||
|
long long length;
|
||||||
|
} fsl_t; /* Fixed-size list */
|
||||||
|
|
||||||
|
static RedisModuleType *fsltype = NULL;
|
||||||
|
|
||||||
|
fsl_t *fsl_type_create() {
|
||||||
|
fsl_t *o;
|
||||||
|
o = RedisModule_Alloc(sizeof(*o));
|
||||||
|
o->length = 0;
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fsl_type_free(fsl_t *o) {
|
||||||
|
RedisModule_Free(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================== "fsltype" type methods ======================= */
|
||||||
|
|
||||||
|
void *fsl_rdb_load(RedisModuleIO *rdb, int encver) {
|
||||||
|
if (encver != 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
fsl_t *fsl = fsl_type_create();
|
||||||
|
fsl->length = RedisModule_LoadUnsigned(rdb);
|
||||||
|
for (long long i = 0; i < fsl->length; i++)
|
||||||
|
fsl->list[i] = RedisModule_LoadSigned(rdb);
|
||||||
|
return fsl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fsl_rdb_save(RedisModuleIO *rdb, void *value) {
|
||||||
|
fsl_t *fsl = value;
|
||||||
|
RedisModule_SaveUnsigned(rdb,fsl->length);
|
||||||
|
for (long long i = 0; i < fsl->length; i++)
|
||||||
|
RedisModule_SaveSigned(rdb, fsl->list[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fsl_aofrw(RedisModuleIO *aof, RedisModuleString *key, void *value) {
|
||||||
|
fsl_t *fsl = value;
|
||||||
|
for (long long i = 0; i < fsl->length; i++)
|
||||||
|
RedisModule_EmitAOF(aof, "FSL.PUSH","sl", key, fsl->list[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fsl_free(void *value) {
|
||||||
|
fsl_type_free(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================== helper methods ======================= */
|
||||||
|
|
||||||
|
int get_fsl(RedisModuleCtx *ctx, RedisModuleString *keyname, int mode, int create, fsl_t **fsl, int reply_on_failure) {
|
||||||
|
RedisModuleKey *key = RedisModule_OpenKey(ctx, keyname, mode);
|
||||||
|
|
||||||
|
int type = RedisModule_KeyType(key);
|
||||||
|
if (type != REDISMODULE_KEYTYPE_EMPTY && RedisModule_ModuleTypeGetType(key) != fsltype) {
|
||||||
|
RedisModule_CloseKey(key);
|
||||||
|
if (reply_on_failure)
|
||||||
|
RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create an empty value object if the key is currently empty. */
|
||||||
|
if (type == REDISMODULE_KEYTYPE_EMPTY) {
|
||||||
|
if (!create) {
|
||||||
|
/* Key is empty but we cannot create */
|
||||||
|
RedisModule_CloseKey(key);
|
||||||
|
*fsl = NULL;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
*fsl = fsl_type_create();
|
||||||
|
RedisModule_ModuleTypeSetValue(key, fsltype, *fsl);
|
||||||
|
} else {
|
||||||
|
*fsl = RedisModule_ModuleTypeGetValue(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
RedisModule_CloseKey(key);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================== commands ======================= */
|
||||||
|
|
||||||
|
/* FSL.PUSH <key> <int> - Push an integer to the fixed-size list (to the right).
|
||||||
|
* It must be greater than the element in the head of the list. */
|
||||||
|
int fsl_push(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||||
|
if (argc != 3)
|
||||||
|
return RedisModule_WrongArity(ctx);
|
||||||
|
|
||||||
|
long long ele;
|
||||||
|
if (RedisModule_StringToLongLong(argv[2],&ele) != REDISMODULE_OK)
|
||||||
|
return RedisModule_ReplyWithError(ctx,"ERR invalid integer");
|
||||||
|
|
||||||
|
fsl_t *fsl;
|
||||||
|
if (!get_fsl(ctx, argv[1], REDISMODULE_WRITE, 1, &fsl, 1))
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
|
||||||
|
if (fsl->length == LIST_SIZE)
|
||||||
|
return RedisModule_ReplyWithError(ctx,"ERR list is full");
|
||||||
|
|
||||||
|
if (fsl->length != 0 && fsl->list[fsl->length-1] >= ele)
|
||||||
|
return RedisModule_ReplyWithError(ctx,"ERR new element has to be greater than the head element");
|
||||||
|
|
||||||
|
fsl->list[fsl->length++] = ele;
|
||||||
|
|
||||||
|
if (fsl->length >= 2)
|
||||||
|
RedisModule_SignalKeyAsReady(ctx, argv[1]);
|
||||||
|
|
||||||
|
return RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||||
|
}
|
||||||
|
|
||||||
|
int bpop2_reply_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||||
|
REDISMODULE_NOT_USED(argv);
|
||||||
|
REDISMODULE_NOT_USED(argc);
|
||||||
|
RedisModuleString *keyname = RedisModule_GetBlockedClientReadyKey(ctx);
|
||||||
|
|
||||||
|
fsl_t *fsl;
|
||||||
|
if (!get_fsl(ctx, keyname, REDISMODULE_READ, 0, &fsl, 0))
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
|
||||||
|
if (!fsl || fsl->length < 2)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
|
||||||
|
RedisModule_ReplyWithArray(ctx, 2);
|
||||||
|
RedisModule_ReplyWithLongLong(ctx, fsl->list[--fsl->length]);
|
||||||
|
RedisModule_ReplyWithLongLong(ctx, fsl->list[--fsl->length]);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bpop2_timeout_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||||
|
REDISMODULE_NOT_USED(argv);
|
||||||
|
REDISMODULE_NOT_USED(argc);
|
||||||
|
return RedisModule_ReplyWithSimpleString(ctx, "Request timedout");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* FSL.BPOP2 <key> <timeout> - Block clients until list has two or more elements.
|
||||||
|
* When that happens, unblock client and pop the last two elements (from the right). */
|
||||||
|
int fsl_bpop2(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||||
|
if (argc != 3)
|
||||||
|
return RedisModule_WrongArity(ctx);
|
||||||
|
|
||||||
|
long long timeout;
|
||||||
|
if (RedisModule_StringToLongLong(argv[2],&timeout) != REDISMODULE_OK || timeout < 0)
|
||||||
|
return RedisModule_ReplyWithError(ctx,"ERR invalid timeout");
|
||||||
|
|
||||||
|
fsl_t *fsl;
|
||||||
|
if (!get_fsl(ctx, argv[1], REDISMODULE_READ, 0, &fsl, 1))
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
|
||||||
|
if (!fsl || fsl->length < 2) {
|
||||||
|
/* Key is empty or has <2 elements, we must block */
|
||||||
|
RedisModule_BlockClientOnKeys(ctx, bpop2_reply_callback, bpop2_timeout_callback,
|
||||||
|
NULL, timeout, &argv[1], 1, NULL);
|
||||||
|
} else {
|
||||||
|
RedisModule_ReplyWithArray(ctx, 2);
|
||||||
|
RedisModule_ReplyWithLongLong(ctx, fsl->list[--fsl->length]);
|
||||||
|
RedisModule_ReplyWithLongLong(ctx, fsl->list[--fsl->length]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bpopgt_reply_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||||
|
REDISMODULE_NOT_USED(argv);
|
||||||
|
REDISMODULE_NOT_USED(argc);
|
||||||
|
RedisModuleString *keyname = RedisModule_GetBlockedClientReadyKey(ctx);
|
||||||
|
long long gt = (long long)RedisModule_GetBlockedClientPrivateData(ctx);
|
||||||
|
|
||||||
|
fsl_t *fsl;
|
||||||
|
if (!get_fsl(ctx, keyname, REDISMODULE_READ, 0, &fsl, 0))
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
|
||||||
|
if (!fsl || fsl->list[fsl->length-1] <= gt)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
|
||||||
|
RedisModule_ReplyWithLongLong(ctx, fsl->list[--fsl->length]);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bpopgt_timeout_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||||
|
REDISMODULE_NOT_USED(argv);
|
||||||
|
REDISMODULE_NOT_USED(argc);
|
||||||
|
return RedisModule_ReplyWithSimpleString(ctx, "Request timedout");
|
||||||
|
}
|
||||||
|
|
||||||
|
void bpopgt_free_privdata(RedisModuleCtx *ctx, void *privdata) {
|
||||||
|
/* Nothing to do because privdata is actually a 'long long',
|
||||||
|
* not a pointer to the heap */
|
||||||
|
REDISMODULE_NOT_USED(ctx);
|
||||||
|
REDISMODULE_NOT_USED(privdata);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* FSL.BPOPGT <key> <gt> <timeout> - Block clients until list has an element greater than <gt>.
|
||||||
|
* When that happens, unblock client and pop the last element (from the right). */
|
||||||
|
int fsl_bpopgt(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||||
|
if (argc != 4)
|
||||||
|
return RedisModule_WrongArity(ctx);
|
||||||
|
|
||||||
|
long long gt;
|
||||||
|
if (RedisModule_StringToLongLong(argv[2],>) != REDISMODULE_OK)
|
||||||
|
return RedisModule_ReplyWithError(ctx,"ERR invalid integer");
|
||||||
|
|
||||||
|
long long timeout;
|
||||||
|
if (RedisModule_StringToLongLong(argv[3],&timeout) != REDISMODULE_OK || timeout < 0)
|
||||||
|
return RedisModule_ReplyWithError(ctx,"ERR invalid timeout");
|
||||||
|
|
||||||
|
fsl_t *fsl;
|
||||||
|
if (!get_fsl(ctx, argv[1], REDISMODULE_READ, 0, &fsl, 1))
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
|
||||||
|
if (!fsl || fsl->list[fsl->length-1] <= gt) {
|
||||||
|
/* Key is empty or has <2 elements, we must block */
|
||||||
|
RedisModule_BlockClientOnKeys(ctx, bpopgt_reply_callback, bpopgt_timeout_callback,
|
||||||
|
bpopgt_free_privdata, timeout, &argv[1], 1, (void*)gt);
|
||||||
|
} else {
|
||||||
|
RedisModule_ReplyWithLongLong(ctx, fsl->list[--fsl->length]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||||
|
REDISMODULE_NOT_USED(argv);
|
||||||
|
REDISMODULE_NOT_USED(argc);
|
||||||
|
|
||||||
|
if (RedisModule_Init(ctx, "blockonkeys", 1, REDISMODULE_APIVER_1)== REDISMODULE_ERR)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
|
||||||
|
RedisModuleTypeMethods tm = {
|
||||||
|
.version = REDISMODULE_TYPE_METHOD_VERSION,
|
||||||
|
.rdb_load = fsl_rdb_load,
|
||||||
|
.rdb_save = fsl_rdb_save,
|
||||||
|
.aof_rewrite = fsl_aofrw,
|
||||||
|
.mem_usage = NULL,
|
||||||
|
.free = fsl_free,
|
||||||
|
.digest = NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
fsltype = RedisModule_CreateDataType(ctx, "fsltype_t", 0, &tm);
|
||||||
|
if (fsltype == NULL)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
|
||||||
|
if (RedisModule_CreateCommand(ctx,"fsl.push",fsl_push,"",0,0,0) == REDISMODULE_ERR)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
|
||||||
|
if (RedisModule_CreateCommand(ctx,"fsl.bpop2",fsl_bpop2,"",0,0,0) == REDISMODULE_ERR)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
|
||||||
|
if (RedisModule_CreateCommand(ctx,"fsl.bpopgt",fsl_bpopgt,"",0,0,0) == REDISMODULE_ERR)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
161
tests/modules/datatype.c
Normal file
161
tests/modules/datatype.c
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
/* This module current tests a small subset but should be extended in the future
|
||||||
|
* for general ModuleDataType coverage.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "redismodule.h"
|
||||||
|
|
||||||
|
static RedisModuleType *datatype = NULL;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
long long intval;
|
||||||
|
RedisModuleString *strval;
|
||||||
|
} DataType;
|
||||||
|
|
||||||
|
static void *datatype_load(RedisModuleIO *io, int encver) {
|
||||||
|
(void) encver;
|
||||||
|
|
||||||
|
int intval = RedisModule_LoadSigned(io);
|
||||||
|
if (RedisModule_IsIOError(io)) return NULL;
|
||||||
|
|
||||||
|
RedisModuleString *strval = RedisModule_LoadString(io);
|
||||||
|
if (RedisModule_IsIOError(io)) return NULL;
|
||||||
|
|
||||||
|
DataType *dt = (DataType *) RedisModule_Alloc(sizeof(DataType));
|
||||||
|
dt->intval = intval;
|
||||||
|
dt->strval = strval;
|
||||||
|
return dt;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void datatype_save(RedisModuleIO *io, void *value) {
|
||||||
|
DataType *dt = (DataType *) value;
|
||||||
|
RedisModule_SaveSigned(io, dt->intval);
|
||||||
|
RedisModule_SaveString(io, dt->strval);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void datatype_free(void *value) {
|
||||||
|
if (value) {
|
||||||
|
DataType *dt = (DataType *) value;
|
||||||
|
|
||||||
|
if (dt->strval) RedisModule_FreeString(NULL, dt->strval);
|
||||||
|
RedisModule_Free(dt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int datatype_set(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||||
|
if (argc != 4) {
|
||||||
|
RedisModule_WrongArity(ctx);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
long long intval;
|
||||||
|
|
||||||
|
if (RedisModule_StringToLongLong(argv[2], &intval) != REDISMODULE_OK) {
|
||||||
|
RedisModule_ReplyWithError(ctx, "Invalid integr value");
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_WRITE);
|
||||||
|
DataType *dt = RedisModule_Calloc(sizeof(DataType), 1);
|
||||||
|
dt->intval = intval;
|
||||||
|
dt->strval = argv[3];
|
||||||
|
RedisModule_RetainString(ctx, dt->strval);
|
||||||
|
|
||||||
|
RedisModule_ModuleTypeSetValue(key, datatype, dt);
|
||||||
|
RedisModule_CloseKey(key);
|
||||||
|
RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||||
|
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int datatype_restore(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||||
|
if (argc != 3) {
|
||||||
|
RedisModule_WrongArity(ctx);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
DataType *dt = RedisModule_LoadDataTypeFromString(argv[2], datatype);
|
||||||
|
if (!dt) {
|
||||||
|
RedisModule_ReplyWithError(ctx, "Invalid data");
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_WRITE);
|
||||||
|
RedisModule_ModuleTypeSetValue(key, datatype, dt);
|
||||||
|
RedisModule_CloseKey(key);
|
||||||
|
RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||||
|
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int datatype_get(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||||
|
if (argc != 2) {
|
||||||
|
RedisModule_WrongArity(ctx);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ);
|
||||||
|
DataType *dt = RedisModule_ModuleTypeGetValue(key);
|
||||||
|
RedisModule_CloseKey(key);
|
||||||
|
|
||||||
|
RedisModule_ReplyWithArray(ctx, 2);
|
||||||
|
RedisModule_ReplyWithLongLong(ctx, dt->intval);
|
||||||
|
RedisModule_ReplyWithString(ctx, dt->strval);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int datatype_dump(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||||
|
if (argc != 2) {
|
||||||
|
RedisModule_WrongArity(ctx);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ);
|
||||||
|
DataType *dt = RedisModule_ModuleTypeGetValue(key);
|
||||||
|
RedisModule_CloseKey(key);
|
||||||
|
|
||||||
|
RedisModuleString *reply = RedisModule_SaveDataTypeToString(ctx, dt, datatype);
|
||||||
|
if (!reply) {
|
||||||
|
RedisModule_ReplyWithError(ctx, "Failed to save");
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
RedisModule_ReplyWithString(ctx, reply);
|
||||||
|
RedisModule_FreeString(ctx, reply);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||||
|
REDISMODULE_NOT_USED(argv);
|
||||||
|
REDISMODULE_NOT_USED(argc);
|
||||||
|
|
||||||
|
if (RedisModule_Init(ctx,"datatype",1,REDISMODULE_APIVER_1) == REDISMODULE_ERR)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
|
||||||
|
RedisModule_SetModuleOptions(ctx, REDISMODULE_OPTIONS_HANDLE_IO_ERRORS);
|
||||||
|
|
||||||
|
RedisModuleTypeMethods datatype_methods = {
|
||||||
|
.version = REDISMODULE_TYPE_METHOD_VERSION,
|
||||||
|
.rdb_load = datatype_load,
|
||||||
|
.rdb_save = datatype_save,
|
||||||
|
.free = datatype_free,
|
||||||
|
};
|
||||||
|
|
||||||
|
datatype = RedisModule_CreateDataType(ctx, "test___dt", 1, &datatype_methods);
|
||||||
|
if (datatype == NULL)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
|
||||||
|
if (RedisModule_CreateCommand(ctx,"datatype.set", datatype_set,"deny-oom",1,1,1) == REDISMODULE_ERR)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
|
||||||
|
if (RedisModule_CreateCommand(ctx,"datatype.get", datatype_get,"",1,1,1) == REDISMODULE_ERR)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
|
||||||
|
if (RedisModule_CreateCommand(ctx,"datatype.restore", datatype_restore,"deny-oom",1,1,1) == REDISMODULE_ERR)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
|
||||||
|
if (RedisModule_CreateCommand(ctx,"datatype.dump", datatype_dump,"",1,1,1) == REDISMODULE_ERR)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
@ -30,36 +30,227 @@
|
|||||||
* POSSIBILITY OF SUCH DAMAGE.
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define REDISMODULE_EXPERIMENTAL_API
|
|
||||||
#include "redismodule.h"
|
#include "redismodule.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* We need to store events to be able to test and see what we got, and we can't
|
||||||
|
* store them in the key-space since that would mess up rdb loading (duplicates)
|
||||||
|
* and be lost of flushdb. */
|
||||||
|
RedisModuleDict *event_log = NULL;
|
||||||
|
|
||||||
|
typedef struct EventElement {
|
||||||
|
long count;
|
||||||
|
RedisModuleString *last_val_string;
|
||||||
|
long last_val_int;
|
||||||
|
} EventElement;
|
||||||
|
|
||||||
|
void LogStringEvent(RedisModuleCtx *ctx, const char* keyname, const char* data) {
|
||||||
|
EventElement *event = RedisModule_DictGetC(event_log, (void*)keyname, strlen(keyname), NULL);
|
||||||
|
if (!event) {
|
||||||
|
event = RedisModule_Alloc(sizeof(EventElement));
|
||||||
|
memset(event, 0, sizeof(EventElement));
|
||||||
|
RedisModule_DictSetC(event_log, (void*)keyname, strlen(keyname), event);
|
||||||
|
}
|
||||||
|
if (event->last_val_string) RedisModule_FreeString(ctx, event->last_val_string);
|
||||||
|
event->last_val_string = RedisModule_CreateString(ctx, data, strlen(data));
|
||||||
|
event->count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogNumericEvent(RedisModuleCtx *ctx, const char* keyname, long data) {
|
||||||
|
REDISMODULE_NOT_USED(ctx);
|
||||||
|
EventElement *event = RedisModule_DictGetC(event_log, (void*)keyname, strlen(keyname), NULL);
|
||||||
|
if (!event) {
|
||||||
|
event = RedisModule_Alloc(sizeof(EventElement));
|
||||||
|
memset(event, 0, sizeof(EventElement));
|
||||||
|
RedisModule_DictSetC(event_log, (void*)keyname, strlen(keyname), event);
|
||||||
|
}
|
||||||
|
event->last_val_int = data;
|
||||||
|
event->count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FreeEvent(RedisModuleCtx *ctx, EventElement *event) {
|
||||||
|
if (event->last_val_string)
|
||||||
|
RedisModule_FreeString(ctx, event->last_val_string);
|
||||||
|
RedisModule_Free(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
int cmdEventCount(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||||
|
{
|
||||||
|
if (argc != 2){
|
||||||
|
RedisModule_WrongArity(ctx);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
EventElement *event = RedisModule_DictGet(event_log, argv[1], NULL);
|
||||||
|
RedisModule_ReplyWithLongLong(ctx, event? event->count: 0);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int cmdEventLast(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||||
|
{
|
||||||
|
if (argc != 2){
|
||||||
|
RedisModule_WrongArity(ctx);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
EventElement *event = RedisModule_DictGet(event_log, argv[1], NULL);
|
||||||
|
if (event && event->last_val_string)
|
||||||
|
RedisModule_ReplyWithString(ctx, event->last_val_string);
|
||||||
|
else if (event)
|
||||||
|
RedisModule_ReplyWithLongLong(ctx, event->last_val_int);
|
||||||
|
else
|
||||||
|
RedisModule_ReplyWithNull(ctx);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearEvents(RedisModuleCtx *ctx)
|
||||||
|
{
|
||||||
|
RedisModuleString *key;
|
||||||
|
EventElement *event;
|
||||||
|
RedisModuleDictIter *iter = RedisModule_DictIteratorStart(event_log, "^", NULL);
|
||||||
|
while((key = RedisModule_DictNext(ctx, iter, (void**)&event)) != NULL) {
|
||||||
|
event->count = 0;
|
||||||
|
event->last_val_int = 0;
|
||||||
|
if (event->last_val_string) RedisModule_FreeString(ctx, event->last_val_string);
|
||||||
|
event->last_val_string = NULL;
|
||||||
|
RedisModule_DictDel(event_log, key, NULL);
|
||||||
|
RedisModule_Free(event);
|
||||||
|
}
|
||||||
|
RedisModule_DictIteratorStop(iter);
|
||||||
|
}
|
||||||
|
|
||||||
|
int cmdEventsClear(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||||
|
{
|
||||||
|
REDISMODULE_NOT_USED(argc);
|
||||||
|
REDISMODULE_NOT_USED(argv);
|
||||||
|
clearEvents(ctx);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
/* Client state change callback. */
|
/* Client state change callback. */
|
||||||
void clientChangeCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
|
void clientChangeCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
|
||||||
{
|
{
|
||||||
REDISMODULE_NOT_USED(ctx);
|
|
||||||
REDISMODULE_NOT_USED(e);
|
REDISMODULE_NOT_USED(e);
|
||||||
|
|
||||||
RedisModuleClientInfo *ci = data;
|
RedisModuleClientInfo *ci = data;
|
||||||
char *keyname = (sub == REDISMODULE_SUBEVENT_CLIENT_CHANGE_CONNECTED) ?
|
char *keyname = (sub == REDISMODULE_SUBEVENT_CLIENT_CHANGE_CONNECTED) ?
|
||||||
"connected" : "disconnected";
|
"client-connected" : "client-disconnected";
|
||||||
RedisModuleCallReply *reply;
|
LogNumericEvent(ctx, keyname, ci->id);
|
||||||
RedisModule_SelectDb(ctx,9);
|
|
||||||
reply = RedisModule_Call(ctx,"RPUSH","cl",keyname,(long)ci->id);
|
|
||||||
RedisModule_FreeCallReply(reply);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void flushdbCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
|
void flushdbCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
|
||||||
{
|
{
|
||||||
REDISMODULE_NOT_USED(ctx);
|
|
||||||
REDISMODULE_NOT_USED(e);
|
REDISMODULE_NOT_USED(e);
|
||||||
|
|
||||||
RedisModuleFlushInfo *fi = data;
|
RedisModuleFlushInfo *fi = data;
|
||||||
char *keyname = (sub == REDISMODULE_SUBEVENT_FLUSHDB_START) ?
|
char *keyname = (sub == REDISMODULE_SUBEVENT_FLUSHDB_START) ?
|
||||||
"flush-start" : "flush-end";
|
"flush-start" : "flush-end";
|
||||||
RedisModuleCallReply *reply;
|
LogNumericEvent(ctx, keyname, fi->dbnum);
|
||||||
RedisModule_SelectDb(ctx,9);
|
}
|
||||||
reply = RedisModule_Call(ctx,"RPUSH","cl",keyname,(long)fi->dbnum);
|
|
||||||
RedisModule_FreeCallReply(reply);
|
void roleChangeCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
|
||||||
|
{
|
||||||
|
REDISMODULE_NOT_USED(e);
|
||||||
|
REDISMODULE_NOT_USED(data);
|
||||||
|
|
||||||
|
RedisModuleReplicationInfo *ri = data;
|
||||||
|
char *keyname = (sub == REDISMODULE_EVENT_REPLROLECHANGED_NOW_MASTER) ?
|
||||||
|
"role-master" : "role-replica";
|
||||||
|
LogStringEvent(ctx, keyname, ri->masterhost);
|
||||||
|
}
|
||||||
|
|
||||||
|
void replicationChangeCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
|
||||||
|
{
|
||||||
|
REDISMODULE_NOT_USED(e);
|
||||||
|
REDISMODULE_NOT_USED(data);
|
||||||
|
|
||||||
|
char *keyname = (sub == REDISMODULE_SUBEVENT_REPLICA_CHANGE_ONLINE) ?
|
||||||
|
"replica-online" : "replica-offline";
|
||||||
|
LogNumericEvent(ctx, keyname, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void rasterLinkChangeCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
|
||||||
|
{
|
||||||
|
REDISMODULE_NOT_USED(e);
|
||||||
|
REDISMODULE_NOT_USED(data);
|
||||||
|
|
||||||
|
char *keyname = (sub == REDISMODULE_SUBEVENT_MASTER_LINK_UP) ?
|
||||||
|
"masterlink-up" : "masterlink-down";
|
||||||
|
LogNumericEvent(ctx, keyname, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void persistenceCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
|
||||||
|
{
|
||||||
|
REDISMODULE_NOT_USED(e);
|
||||||
|
REDISMODULE_NOT_USED(data);
|
||||||
|
|
||||||
|
char *keyname = NULL;
|
||||||
|
switch (sub) {
|
||||||
|
case REDISMODULE_SUBEVENT_PERSISTENCE_RDB_START: keyname = "persistence-rdb-start"; break;
|
||||||
|
case REDISMODULE_SUBEVENT_PERSISTENCE_AOF_START: keyname = "persistence-aof-start"; break;
|
||||||
|
case REDISMODULE_SUBEVENT_PERSISTENCE_SYNC_RDB_START: keyname = "persistence-syncrdb-start"; break;
|
||||||
|
case REDISMODULE_SUBEVENT_PERSISTENCE_ENDED: keyname = "persistence-end"; break;
|
||||||
|
case REDISMODULE_SUBEVENT_PERSISTENCE_FAILED: keyname = "persistence-failed"; break;
|
||||||
|
}
|
||||||
|
/* modifying the keyspace from the fork child is not an option, using log instead */
|
||||||
|
RedisModule_Log(ctx, "warning", "module-event-%s", keyname);
|
||||||
|
if (sub == REDISMODULE_SUBEVENT_PERSISTENCE_SYNC_RDB_START)
|
||||||
|
LogNumericEvent(ctx, keyname, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadingCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
|
||||||
|
{
|
||||||
|
REDISMODULE_NOT_USED(e);
|
||||||
|
REDISMODULE_NOT_USED(data);
|
||||||
|
|
||||||
|
char *keyname = NULL;
|
||||||
|
switch (sub) {
|
||||||
|
case REDISMODULE_SUBEVENT_LOADING_RDB_START: keyname = "loading-rdb-start"; break;
|
||||||
|
case REDISMODULE_SUBEVENT_LOADING_AOF_START: keyname = "loading-aof-start"; break;
|
||||||
|
case REDISMODULE_SUBEVENT_LOADING_REPL_START: keyname = "loading-repl-start"; break;
|
||||||
|
case REDISMODULE_SUBEVENT_LOADING_ENDED: keyname = "loading-end"; break;
|
||||||
|
case REDISMODULE_SUBEVENT_LOADING_FAILED: keyname = "loading-failed"; break;
|
||||||
|
}
|
||||||
|
LogNumericEvent(ctx, keyname, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadingProgressCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
|
||||||
|
{
|
||||||
|
REDISMODULE_NOT_USED(e);
|
||||||
|
|
||||||
|
RedisModuleLoadingProgress *ei = data;
|
||||||
|
char *keyname = (sub == REDISMODULE_SUBEVENT_LOADING_PROGRESS_RDB) ?
|
||||||
|
"loading-progress-rdb" : "loading-progress-aof";
|
||||||
|
LogNumericEvent(ctx, keyname, ei->progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
void shutdownCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
|
||||||
|
{
|
||||||
|
REDISMODULE_NOT_USED(e);
|
||||||
|
REDISMODULE_NOT_USED(data);
|
||||||
|
REDISMODULE_NOT_USED(sub);
|
||||||
|
|
||||||
|
RedisModule_Log(ctx, "warning", "module-event-%s", "shutdown");
|
||||||
|
}
|
||||||
|
|
||||||
|
void cronLoopCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
|
||||||
|
{
|
||||||
|
REDISMODULE_NOT_USED(e);
|
||||||
|
REDISMODULE_NOT_USED(sub);
|
||||||
|
|
||||||
|
RedisModuleCronLoop *ei = data;
|
||||||
|
LogNumericEvent(ctx, "cron-loop", ei->hz);
|
||||||
|
}
|
||||||
|
|
||||||
|
void moduleChangeCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
|
||||||
|
{
|
||||||
|
REDISMODULE_NOT_USED(e);
|
||||||
|
|
||||||
|
RedisModuleModuleChange *ei = data;
|
||||||
|
char *keyname = (sub == REDISMODULE_SUBEVENT_MODULE_LOADED) ?
|
||||||
|
"module-loaded" : "module-unloaded";
|
||||||
|
LogStringEvent(ctx, keyname, ei->module_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This function must be present on each Redis module. It is used in order to
|
/* This function must be present on each Redis module. It is used in order to
|
||||||
@ -71,9 +262,50 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
|||||||
if (RedisModule_Init(ctx,"testhook",1,REDISMODULE_APIVER_1)
|
if (RedisModule_Init(ctx,"testhook",1,REDISMODULE_APIVER_1)
|
||||||
== REDISMODULE_ERR) return REDISMODULE_ERR;
|
== REDISMODULE_ERR) return REDISMODULE_ERR;
|
||||||
|
|
||||||
|
/* replication related hooks */
|
||||||
|
RedisModule_SubscribeToServerEvent(ctx,
|
||||||
|
RedisModuleEvent_ReplicationRoleChanged, roleChangeCallback);
|
||||||
|
RedisModule_SubscribeToServerEvent(ctx,
|
||||||
|
RedisModuleEvent_ReplicaChange, replicationChangeCallback);
|
||||||
|
RedisModule_SubscribeToServerEvent(ctx,
|
||||||
|
RedisModuleEvent_MasterLinkChange, rasterLinkChangeCallback);
|
||||||
|
|
||||||
|
/* persistence related hooks */
|
||||||
|
RedisModule_SubscribeToServerEvent(ctx,
|
||||||
|
RedisModuleEvent_Persistence, persistenceCallback);
|
||||||
|
RedisModule_SubscribeToServerEvent(ctx,
|
||||||
|
RedisModuleEvent_Loading, loadingCallback);
|
||||||
|
RedisModule_SubscribeToServerEvent(ctx,
|
||||||
|
RedisModuleEvent_LoadingProgress, loadingProgressCallback);
|
||||||
|
|
||||||
|
/* other hooks */
|
||||||
RedisModule_SubscribeToServerEvent(ctx,
|
RedisModule_SubscribeToServerEvent(ctx,
|
||||||
RedisModuleEvent_ClientChange, clientChangeCallback);
|
RedisModuleEvent_ClientChange, clientChangeCallback);
|
||||||
RedisModule_SubscribeToServerEvent(ctx,
|
RedisModule_SubscribeToServerEvent(ctx,
|
||||||
RedisModuleEvent_FlushDB, flushdbCallback);
|
RedisModuleEvent_FlushDB, flushdbCallback);
|
||||||
|
RedisModule_SubscribeToServerEvent(ctx,
|
||||||
|
RedisModuleEvent_Shutdown, shutdownCallback);
|
||||||
|
RedisModule_SubscribeToServerEvent(ctx,
|
||||||
|
RedisModuleEvent_CronLoop, cronLoopCallback);
|
||||||
|
RedisModule_SubscribeToServerEvent(ctx,
|
||||||
|
RedisModuleEvent_ModuleChange, moduleChangeCallback);
|
||||||
|
|
||||||
|
event_log = RedisModule_CreateDict(ctx);
|
||||||
|
|
||||||
|
if (RedisModule_CreateCommand(ctx,"hooks.event_count", cmdEventCount,"",0,0,0) == REDISMODULE_ERR)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
if (RedisModule_CreateCommand(ctx,"hooks.event_last", cmdEventLast,"",0,0,0) == REDISMODULE_ERR)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
if (RedisModule_CreateCommand(ctx,"hooks.clear", cmdEventsClear,"",0,0,0) == REDISMODULE_ERR)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
|
||||||
return REDISMODULE_OK;
|
return REDISMODULE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int RedisModule_OnUnload(RedisModuleCtx *ctx) {
|
||||||
|
clearEvents(ctx);
|
||||||
|
RedisModule_FreeDict(ctx, event_log);
|
||||||
|
event_log = NULL;
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
|
||||||
|
#define UNUSED(x) (void)(x)
|
||||||
|
|
||||||
int test_call_generic(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
int test_call_generic(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||||
{
|
{
|
||||||
if (argc<2) {
|
if (argc<2) {
|
||||||
@ -40,6 +42,146 @@ int test_call_info(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
|||||||
return REDISMODULE_OK;
|
return REDISMODULE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int test_ld_conv(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||||
|
UNUSED(argv);
|
||||||
|
UNUSED(argc);
|
||||||
|
long double ld = 0.00000000000000001L;
|
||||||
|
const char *ldstr = "0.00000000000000001";
|
||||||
|
RedisModuleString *s1 = RedisModule_CreateStringFromLongDouble(ctx, ld, 1);
|
||||||
|
RedisModuleString *s2 =
|
||||||
|
RedisModule_CreateString(ctx, ldstr, strlen(ldstr));
|
||||||
|
if (RedisModule_StringCompare(s1, s2) != 0) {
|
||||||
|
char err[4096];
|
||||||
|
snprintf(err, 4096,
|
||||||
|
"Failed to convert long double to string ('%s' != '%s')",
|
||||||
|
RedisModule_StringPtrLen(s1, NULL),
|
||||||
|
RedisModule_StringPtrLen(s2, NULL));
|
||||||
|
RedisModule_ReplyWithError(ctx, err);
|
||||||
|
goto final;
|
||||||
|
}
|
||||||
|
long double ld2 = 0;
|
||||||
|
if (RedisModule_StringToLongDouble(s2, &ld2) == REDISMODULE_ERR) {
|
||||||
|
RedisModule_ReplyWithError(ctx,
|
||||||
|
"Failed to convert string to long double");
|
||||||
|
goto final;
|
||||||
|
}
|
||||||
|
if (ld2 != ld) {
|
||||||
|
char err[4096];
|
||||||
|
snprintf(err, 4096,
|
||||||
|
"Failed to convert string to long double (%.40Lf != %.40Lf)",
|
||||||
|
ld2,
|
||||||
|
ld);
|
||||||
|
RedisModule_ReplyWithError(ctx, err);
|
||||||
|
goto final;
|
||||||
|
}
|
||||||
|
RedisModule_ReplyWithLongDouble(ctx, ld2);
|
||||||
|
final:
|
||||||
|
RedisModule_FreeString(ctx, s1);
|
||||||
|
RedisModule_FreeString(ctx, s2);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int test_flushall(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||||
|
{
|
||||||
|
REDISMODULE_NOT_USED(argv);
|
||||||
|
REDISMODULE_NOT_USED(argc);
|
||||||
|
RedisModule_ResetDataset(1, 0);
|
||||||
|
RedisModule_ReplyWithCString(ctx, "Ok");
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int test_dbsize(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||||
|
{
|
||||||
|
REDISMODULE_NOT_USED(argv);
|
||||||
|
REDISMODULE_NOT_USED(argc);
|
||||||
|
long long ll = RedisModule_DbSize(ctx);
|
||||||
|
RedisModule_ReplyWithLongLong(ctx, ll);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int test_randomkey(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||||
|
{
|
||||||
|
REDISMODULE_NOT_USED(argv);
|
||||||
|
REDISMODULE_NOT_USED(argc);
|
||||||
|
RedisModuleString *str = RedisModule_RandomKey(ctx);
|
||||||
|
RedisModule_ReplyWithString(ctx, str);
|
||||||
|
RedisModule_FreeString(ctx, str);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
RedisModuleKey *open_key_or_reply(RedisModuleCtx *ctx, RedisModuleString *keyname, int mode) {
|
||||||
|
RedisModuleKey *key = RedisModule_OpenKey(ctx, keyname, mode);
|
||||||
|
if (!key) {
|
||||||
|
RedisModule_ReplyWithError(ctx, "key not found");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
int test_getlru(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||||
|
{
|
||||||
|
if (argc<2) {
|
||||||
|
RedisModule_WrongArity(ctx);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
RedisModuleKey *key = open_key_or_reply(ctx, argv[1], REDISMODULE_READ|REDISMODULE_OPEN_KEY_NOTOUCH);
|
||||||
|
mstime_t lru;
|
||||||
|
RedisModule_GetLRU(key, &lru);
|
||||||
|
RedisModule_ReplyWithLongLong(ctx, lru);
|
||||||
|
RedisModule_CloseKey(key);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int test_setlru(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||||
|
{
|
||||||
|
if (argc<3) {
|
||||||
|
RedisModule_WrongArity(ctx);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
RedisModuleKey *key = open_key_or_reply(ctx, argv[1], REDISMODULE_READ|REDISMODULE_OPEN_KEY_NOTOUCH);
|
||||||
|
mstime_t lru;
|
||||||
|
if (RedisModule_StringToLongLong(argv[2], &lru) != REDISMODULE_OK) {
|
||||||
|
RedisModule_ReplyWithError(ctx, "invalid idle time");
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
int was_set = RedisModule_SetLRU(key, lru)==REDISMODULE_OK;
|
||||||
|
RedisModule_ReplyWithLongLong(ctx, was_set);
|
||||||
|
RedisModule_CloseKey(key);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int test_getlfu(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||||
|
{
|
||||||
|
if (argc<2) {
|
||||||
|
RedisModule_WrongArity(ctx);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
RedisModuleKey *key = open_key_or_reply(ctx, argv[1], REDISMODULE_READ|REDISMODULE_OPEN_KEY_NOTOUCH);
|
||||||
|
mstime_t lfu;
|
||||||
|
RedisModule_GetLFU(key, &lfu);
|
||||||
|
RedisModule_ReplyWithLongLong(ctx, lfu);
|
||||||
|
RedisModule_CloseKey(key);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int test_setlfu(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||||
|
{
|
||||||
|
if (argc<3) {
|
||||||
|
RedisModule_WrongArity(ctx);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
RedisModuleKey *key = open_key_or_reply(ctx, argv[1], REDISMODULE_READ|REDISMODULE_OPEN_KEY_NOTOUCH);
|
||||||
|
mstime_t lfu;
|
||||||
|
if (RedisModule_StringToLongLong(argv[2], &lfu) != REDISMODULE_OK) {
|
||||||
|
RedisModule_ReplyWithError(ctx, "invalid freq");
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
int was_set = RedisModule_SetLFU(key, lfu)==REDISMODULE_OK;
|
||||||
|
RedisModule_ReplyWithLongLong(ctx, was_set);
|
||||||
|
RedisModule_CloseKey(key);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||||
REDISMODULE_NOT_USED(argv);
|
REDISMODULE_NOT_USED(argv);
|
||||||
REDISMODULE_NOT_USED(argc);
|
REDISMODULE_NOT_USED(argc);
|
||||||
@ -50,6 +192,22 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
|||||||
return REDISMODULE_ERR;
|
return REDISMODULE_ERR;
|
||||||
if (RedisModule_CreateCommand(ctx,"test.call_info", test_call_info,"",0,0,0) == REDISMODULE_ERR)
|
if (RedisModule_CreateCommand(ctx,"test.call_info", test_call_info,"",0,0,0) == REDISMODULE_ERR)
|
||||||
return REDISMODULE_ERR;
|
return REDISMODULE_ERR;
|
||||||
|
if (RedisModule_CreateCommand(ctx,"test.ld_conversion", test_ld_conv, "",0,0,0) == REDISMODULE_ERR)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
if (RedisModule_CreateCommand(ctx,"test.flushall", test_flushall,"",0,0,0) == REDISMODULE_ERR)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
if (RedisModule_CreateCommand(ctx,"test.dbsize", test_dbsize,"",0,0,0) == REDISMODULE_ERR)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
if (RedisModule_CreateCommand(ctx,"test.randomkey", test_randomkey,"",0,0,0) == REDISMODULE_ERR)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
if (RedisModule_CreateCommand(ctx,"test.setlru", test_setlru,"",0,0,0) == REDISMODULE_ERR)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
if (RedisModule_CreateCommand(ctx,"test.getlru", test_getlru,"",0,0,0) == REDISMODULE_ERR)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
if (RedisModule_CreateCommand(ctx,"test.setlfu", test_setlfu,"",0,0,0) == REDISMODULE_ERR)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
if (RedisModule_CreateCommand(ctx,"test.getlfu", test_getlfu,"",0,0,0) == REDISMODULE_ERR)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
|
||||||
return REDISMODULE_OK;
|
return REDISMODULE_OK;
|
||||||
}
|
}
|
||||||
|
109
tests/modules/scan.c
Normal file
109
tests/modules/scan.c
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
#include "redismodule.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
size_t nkeys;
|
||||||
|
} scan_strings_pd;
|
||||||
|
|
||||||
|
void scan_strings_callback(RedisModuleCtx *ctx, RedisModuleString* keyname, RedisModuleKey* key, void *privdata) {
|
||||||
|
scan_strings_pd* pd = privdata;
|
||||||
|
int was_opened = 0;
|
||||||
|
if (!key) {
|
||||||
|
key = RedisModule_OpenKey(ctx, keyname, REDISMODULE_READ);
|
||||||
|
was_opened = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RedisModule_KeyType(key) == REDISMODULE_KEYTYPE_STRING) {
|
||||||
|
size_t len;
|
||||||
|
char * data = RedisModule_StringDMA(key, &len, REDISMODULE_READ);
|
||||||
|
RedisModule_ReplyWithArray(ctx, 2);
|
||||||
|
RedisModule_ReplyWithString(ctx, keyname);
|
||||||
|
RedisModule_ReplyWithStringBuffer(ctx, data, len);
|
||||||
|
pd->nkeys++;
|
||||||
|
}
|
||||||
|
if (was_opened)
|
||||||
|
RedisModule_CloseKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
int scan_strings(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||||
|
{
|
||||||
|
REDISMODULE_NOT_USED(argv);
|
||||||
|
REDISMODULE_NOT_USED(argc);
|
||||||
|
scan_strings_pd pd = {
|
||||||
|
.nkeys = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);
|
||||||
|
|
||||||
|
RedisModuleScanCursor* cursor = RedisModule_ScanCursorCreate();
|
||||||
|
while(RedisModule_Scan(ctx, cursor, scan_strings_callback, &pd));
|
||||||
|
RedisModule_ScanCursorDestroy(cursor);
|
||||||
|
|
||||||
|
RedisModule_ReplySetArrayLength(ctx, pd.nkeys);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
RedisModuleCtx *ctx;
|
||||||
|
size_t nreplies;
|
||||||
|
} scan_key_pd;
|
||||||
|
|
||||||
|
void scan_key_callback(RedisModuleKey *key, RedisModuleString* field, RedisModuleString* value, void *privdata) {
|
||||||
|
REDISMODULE_NOT_USED(key);
|
||||||
|
scan_key_pd* pd = privdata;
|
||||||
|
RedisModule_ReplyWithArray(pd->ctx, 2);
|
||||||
|
RedisModule_ReplyWithString(pd->ctx, field);
|
||||||
|
if (value)
|
||||||
|
RedisModule_ReplyWithString(pd->ctx, value);
|
||||||
|
else
|
||||||
|
RedisModule_ReplyWithNull(pd->ctx);
|
||||||
|
pd->nreplies++;
|
||||||
|
}
|
||||||
|
|
||||||
|
int scan_key(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||||
|
{
|
||||||
|
if (argc != 2) {
|
||||||
|
RedisModule_WrongArity(ctx);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
scan_key_pd pd = {
|
||||||
|
.ctx = ctx,
|
||||||
|
.nreplies = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ);
|
||||||
|
if (!key) {
|
||||||
|
RedisModule_ReplyWithError(ctx, "not found");
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);
|
||||||
|
|
||||||
|
RedisModuleScanCursor* cursor = RedisModule_ScanCursorCreate();
|
||||||
|
while(RedisModule_ScanKey(key, cursor, scan_key_callback, &pd));
|
||||||
|
RedisModule_ScanCursorDestroy(cursor);
|
||||||
|
|
||||||
|
RedisModule_ReplySetArrayLength(ctx, pd.nreplies);
|
||||||
|
RedisModule_CloseKey(key);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||||
|
REDISMODULE_NOT_USED(argv);
|
||||||
|
REDISMODULE_NOT_USED(argc);
|
||||||
|
if (RedisModule_Init(ctx, "scan", 1, REDISMODULE_APIVER_1)== REDISMODULE_ERR)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
|
||||||
|
if (RedisModule_CreateCommand(ctx, "scan.scan_strings", scan_strings, "", 0, 0, 0) == REDISMODULE_ERR)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
|
||||||
|
if (RedisModule_CreateCommand(ctx, "scan.scan_key", scan_key, "", 0, 0, 0) == REDISMODULE_ERR)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -15,11 +15,19 @@ RedisModuleString *after_str = NULL;
|
|||||||
|
|
||||||
void *testrdb_type_load(RedisModuleIO *rdb, int encver) {
|
void *testrdb_type_load(RedisModuleIO *rdb, int encver) {
|
||||||
int count = RedisModule_LoadSigned(rdb);
|
int count = RedisModule_LoadSigned(rdb);
|
||||||
if (RedisModule_IsIOError(rdb))
|
RedisModuleString *str = RedisModule_LoadString(rdb);
|
||||||
|
float f = RedisModule_LoadFloat(rdb);
|
||||||
|
long double ld = RedisModule_LoadLongDouble(rdb);
|
||||||
|
if (RedisModule_IsIOError(rdb)) {
|
||||||
|
RedisModuleCtx *ctx = RedisModule_GetContextFromIO(rdb);
|
||||||
|
RedisModule_FreeString(ctx, str);
|
||||||
return NULL;
|
return NULL;
|
||||||
|
}
|
||||||
|
/* Using the values only after checking for io errors. */
|
||||||
assert(count==1);
|
assert(count==1);
|
||||||
assert(encver==1);
|
assert(encver==1);
|
||||||
RedisModuleString *str = RedisModule_LoadString(rdb);
|
assert(f==1.5f);
|
||||||
|
assert(ld==0.333333333333333333L);
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,6 +35,8 @@ void testrdb_type_save(RedisModuleIO *rdb, void *value) {
|
|||||||
RedisModuleString *str = (RedisModuleString*)value;
|
RedisModuleString *str = (RedisModuleString*)value;
|
||||||
RedisModule_SaveSigned(rdb, 1);
|
RedisModule_SaveSigned(rdb, 1);
|
||||||
RedisModule_SaveString(rdb, str);
|
RedisModule_SaveString(rdb, str);
|
||||||
|
RedisModule_SaveFloat(rdb, 1.5);
|
||||||
|
RedisModule_SaveLongDouble(rdb, 0.333333333333333333L);
|
||||||
}
|
}
|
||||||
|
|
||||||
void testrdb_aux_save(RedisModuleIO *rdb, int when) {
|
void testrdb_aux_save(RedisModuleIO *rdb, int when) {
|
||||||
|
@ -11,28 +11,55 @@ proc fail {msg} {
|
|||||||
|
|
||||||
proc assert {condition} {
|
proc assert {condition} {
|
||||||
if {![uplevel 1 [list expr $condition]]} {
|
if {![uplevel 1 [list expr $condition]]} {
|
||||||
error "assertion:Expected condition '$condition' to be true ([uplevel 1 [list subst -nocommands $condition]])"
|
set context "(context: [info frame -1])"
|
||||||
|
error "assertion:Expected [uplevel 1 [list subst -nocommands $condition]] $context"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
proc assert_no_match {pattern value} {
|
proc assert_no_match {pattern value} {
|
||||||
if {[string match $pattern $value]} {
|
if {[string match $pattern $value]} {
|
||||||
error "assertion:Expected '$value' to not match '$pattern'"
|
set context "(context: [info frame -1])"
|
||||||
|
error "assertion:Expected '$value' to not match '$pattern' $context"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
proc assert_match {pattern value} {
|
proc assert_match {pattern value} {
|
||||||
if {![string match $pattern $value]} {
|
if {![string match $pattern $value]} {
|
||||||
error "assertion:Expected '$value' to match '$pattern'"
|
set context "(context: [info frame -1])"
|
||||||
|
error "assertion:Expected '$value' to match '$pattern' $context"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
proc assert_equal {expected value {detail ""}} {
|
proc assert_equal {value expected {detail ""}} {
|
||||||
if {$expected ne $value} {
|
if {$expected ne $value} {
|
||||||
if {$detail ne ""} {
|
if {$detail ne ""} {
|
||||||
set detail " (detail: $detail)"
|
set detail "(detail: $detail)"
|
||||||
|
} else {
|
||||||
|
set detail "(context: [info frame -1])"
|
||||||
}
|
}
|
||||||
error "assertion:Expected '$value' to be equal to '$expected'$detail"
|
error "assertion:Expected '$value' to be equal to '$expected' $detail"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
proc assert_lessthan {value expected {detail ""}} {
|
||||||
|
if {!($value < $expected)} {
|
||||||
|
if {$detail ne ""} {
|
||||||
|
set detail "(detail: $detail)"
|
||||||
|
} else {
|
||||||
|
set detail "(context: [info frame -1])"
|
||||||
|
}
|
||||||
|
error "assertion:Expected '$value' to be lessthan to '$expected' $detail"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
proc assert_range {value min max {detail ""}} {
|
||||||
|
if {!($value <= $max && $value >= $min)} {
|
||||||
|
if {$detail ne ""} {
|
||||||
|
set detail "(detail: $detail)"
|
||||||
|
} else {
|
||||||
|
set detail "(context: [info frame -1])"
|
||||||
|
}
|
||||||
|
error "assertion:Expected '$value' to be between to '$min' and '$max' $detail"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
85
tests/unit/moduleapi/blockonkeys.tcl
Normal file
85
tests/unit/moduleapi/blockonkeys.tcl
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
set testmodule [file normalize tests/modules/blockonkeys.so]
|
||||||
|
|
||||||
|
start_server {tags {"modules"}} {
|
||||||
|
r module load $testmodule
|
||||||
|
|
||||||
|
test {Module client blocked on keys (no metadata): No block} {
|
||||||
|
r del k
|
||||||
|
r fsl.push k 33
|
||||||
|
r fsl.push k 34
|
||||||
|
r fsl.bpop2 k 0
|
||||||
|
} {34 33}
|
||||||
|
|
||||||
|
test {Module client blocked on keys (no metadata): Timeout} {
|
||||||
|
r del k
|
||||||
|
set rd [redis_deferring_client]
|
||||||
|
r fsl.push k 33
|
||||||
|
$rd fsl.bpop2 k 1
|
||||||
|
assert_equal {Request timedout} [$rd read]
|
||||||
|
}
|
||||||
|
|
||||||
|
test {Module client blocked on keys (no metadata): Blocked, case 1} {
|
||||||
|
r del k
|
||||||
|
set rd [redis_deferring_client]
|
||||||
|
r fsl.push k 33
|
||||||
|
$rd fsl.bpop2 k 0
|
||||||
|
r fsl.push k 34
|
||||||
|
assert_equal {34 33} [$rd read]
|
||||||
|
}
|
||||||
|
|
||||||
|
test {Module client blocked on keys (no metadata): Blocked, case 2} {
|
||||||
|
r del k
|
||||||
|
set rd [redis_deferring_client]
|
||||||
|
r fsl.push k 33
|
||||||
|
r fsl.push k 34
|
||||||
|
$rd fsl.bpop2 k 0
|
||||||
|
assert_equal {34 33} [$rd read]
|
||||||
|
}
|
||||||
|
|
||||||
|
test {Module client blocked on keys (with metadata): No block} {
|
||||||
|
r del k
|
||||||
|
r fsl.push k 34
|
||||||
|
r fsl.bpopgt k 30 0
|
||||||
|
} {34}
|
||||||
|
|
||||||
|
test {Module client blocked on keys (with metadata): Timeout} {
|
||||||
|
r del k
|
||||||
|
set rd [redis_deferring_client]
|
||||||
|
r fsl.push k 33
|
||||||
|
$rd fsl.bpopgt k 35 1
|
||||||
|
assert_equal {Request timedout} [$rd read]
|
||||||
|
}
|
||||||
|
|
||||||
|
test {Module client blocked on keys (with metadata): Blocked, case 1} {
|
||||||
|
r del k
|
||||||
|
set rd [redis_deferring_client]
|
||||||
|
r fsl.push k 33
|
||||||
|
$rd fsl.bpopgt k 33 0
|
||||||
|
r fsl.push k 34
|
||||||
|
assert_equal {34} [$rd read]
|
||||||
|
}
|
||||||
|
|
||||||
|
test {Module client blocked on keys (with metadata): Blocked, case 2} {
|
||||||
|
r del k
|
||||||
|
set rd [redis_deferring_client]
|
||||||
|
$rd fsl.bpopgt k 35 0
|
||||||
|
r fsl.push k 33
|
||||||
|
r fsl.push k 34
|
||||||
|
r fsl.push k 35
|
||||||
|
r fsl.push k 36
|
||||||
|
assert_equal {36} [$rd read]
|
||||||
|
}
|
||||||
|
|
||||||
|
test {Module client blocked on keys does not wake up on wrong type} {
|
||||||
|
r del k
|
||||||
|
set rd [redis_deferring_client]
|
||||||
|
$rd fsl.bpop2 k 0
|
||||||
|
r lpush k 12
|
||||||
|
r lpush k 13
|
||||||
|
r lpush k 14
|
||||||
|
r del k
|
||||||
|
r fsl.push k 33
|
||||||
|
r fsl.push k 34
|
||||||
|
assert_equal {34 33} [$rd read]
|
||||||
|
}
|
||||||
|
}
|
27
tests/unit/moduleapi/datatype.tcl
Normal file
27
tests/unit/moduleapi/datatype.tcl
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
set testmodule [file normalize tests/modules/datatype.so]
|
||||||
|
|
||||||
|
start_server {tags {"modules"}} {
|
||||||
|
r module load $testmodule
|
||||||
|
|
||||||
|
test {DataType: Test module is sane, GET/SET work.} {
|
||||||
|
r datatype.set dtkey 100 stringval
|
||||||
|
assert {[r datatype.get dtkey] eq {100 stringval}}
|
||||||
|
}
|
||||||
|
|
||||||
|
test {DataType: RM_SaveDataTypeToString(), RM_LoadDataTypeFromString() work} {
|
||||||
|
r datatype.set dtkey -1111 MyString
|
||||||
|
set encoded [r datatype.dump dtkey]
|
||||||
|
|
||||||
|
r datatype.restore dtkeycopy $encoded
|
||||||
|
assert {[r datatype.get dtkeycopy] eq {-1111 MyString}}
|
||||||
|
}
|
||||||
|
|
||||||
|
test {DataType: Handle truncated RM_LoadDataTypeFromString()} {
|
||||||
|
r datatype.set dtkey -1111 MyString
|
||||||
|
set encoded [r datatype.dump dtkey]
|
||||||
|
set truncated [string range $encoded 0 end-1]
|
||||||
|
|
||||||
|
catch {r datatype.restore dtkeycopy $truncated} e
|
||||||
|
set e
|
||||||
|
} {*Invalid*}
|
||||||
|
}
|
@ -3,26 +3,138 @@ set testmodule [file normalize tests/modules/hooks.so]
|
|||||||
tags "modules" {
|
tags "modules" {
|
||||||
start_server {} {
|
start_server {} {
|
||||||
r module load $testmodule
|
r module load $testmodule
|
||||||
|
r config set appendonly yes
|
||||||
|
|
||||||
test {Test clients connection / disconnection hooks} {
|
test {Test clients connection / disconnection hooks} {
|
||||||
for {set j 0} {$j < 2} {incr j} {
|
for {set j 0} {$j < 2} {incr j} {
|
||||||
set rd1 [redis_deferring_client]
|
set rd1 [redis_deferring_client]
|
||||||
$rd1 close
|
$rd1 close
|
||||||
}
|
}
|
||||||
assert {[r llen connected] > 1}
|
assert {[r hooks.event_count client-connected] > 1}
|
||||||
assert {[r llen disconnected] > 1}
|
assert {[r hooks.event_count client-disconnected] > 1}
|
||||||
|
}
|
||||||
|
|
||||||
|
test {Test module cron hook} {
|
||||||
|
after 100
|
||||||
|
assert {[r hooks.event_count cron-loop] > 0}
|
||||||
|
set hz [r hooks.event_last cron-loop]
|
||||||
|
assert_equal $hz 10
|
||||||
|
}
|
||||||
|
|
||||||
|
test {Test module loaded / unloaded hooks} {
|
||||||
|
set othermodule [file normalize tests/modules/infotest.so]
|
||||||
|
r module load $othermodule
|
||||||
|
r module unload infotest
|
||||||
|
assert_equal [r hooks.event_last module-loaded] "infotest"
|
||||||
|
assert_equal [r hooks.event_last module-unloaded] "infotest"
|
||||||
|
}
|
||||||
|
|
||||||
|
test {Test module aofrw hook} {
|
||||||
|
r debug populate 1000 foo 10000 ;# 10mb worth of data
|
||||||
|
r config set rdbcompression no ;# rdb progress is only checked once in 2mb
|
||||||
|
r BGREWRITEAOF
|
||||||
|
waitForBgrewriteaof r
|
||||||
|
assert_equal [string match {*module-event-persistence-aof-start*} [exec tail -20 < [srv 0 stdout]]] 1
|
||||||
|
assert_equal [string match {*module-event-persistence-end*} [exec tail -20 < [srv 0 stdout]]] 1
|
||||||
|
}
|
||||||
|
|
||||||
|
test {Test module aof load and rdb/aof progress hooks} {
|
||||||
|
# create some aof tail (progress is checked only once in 1000 commands)
|
||||||
|
for {set j 0} {$j < 4000} {incr j} {
|
||||||
|
r set "bar$j" x
|
||||||
|
}
|
||||||
|
# set some configs that will cause many loading progress events during aof loading
|
||||||
|
r config set key-load-delay 1
|
||||||
|
r config set dynamic-hz no
|
||||||
|
r config set hz 500
|
||||||
|
r DEBUG LOADAOF
|
||||||
|
assert_equal [r hooks.event_last loading-aof-start] 0
|
||||||
|
assert_equal [r hooks.event_last loading-end] 0
|
||||||
|
assert {[r hooks.event_count loading-rdb-start] == 0}
|
||||||
|
assert {[r hooks.event_count loading-progress-rdb] >= 2} ;# comes from the preamble section
|
||||||
|
assert {[r hooks.event_count loading-progress-aof] >= 2}
|
||||||
|
}
|
||||||
|
# undo configs before next test
|
||||||
|
r config set dynamic-hz yes
|
||||||
|
r config set key-load-delay 0
|
||||||
|
|
||||||
|
test {Test module rdb save hook} {
|
||||||
|
# debug reload does: save, flush, load:
|
||||||
|
assert {[r hooks.event_count persistence-syncrdb-start] == 0}
|
||||||
|
assert {[r hooks.event_count loading-rdb-start] == 0}
|
||||||
|
r debug reload
|
||||||
|
assert {[r hooks.event_count persistence-syncrdb-start] == 1}
|
||||||
|
assert {[r hooks.event_count loading-rdb-start] == 1}
|
||||||
}
|
}
|
||||||
|
|
||||||
test {Test flushdb hooks} {
|
test {Test flushdb hooks} {
|
||||||
r flushall ;# Note: only the "end" RPUSH will survive
|
|
||||||
r select 1
|
|
||||||
r flushdb
|
r flushdb
|
||||||
r select 2
|
assert_equal [r hooks.event_last flush-start] 9
|
||||||
r flushdb
|
assert_equal [r hooks.event_last flush-end] 9
|
||||||
r select 9
|
r flushall
|
||||||
assert {[r llen flush-start] == 2}
|
assert_equal [r hooks.event_last flush-start] -1
|
||||||
assert {[r llen flush-end] == 3}
|
assert_equal [r hooks.event_last flush-end] -1
|
||||||
assert {[r lrange flush-start 0 -1] eq {1 2}}
|
|
||||||
assert {[r lrange flush-end 0 -1] eq {-1 1 2}}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# replication related tests
|
||||||
|
set master [srv 0 client]
|
||||||
|
set master_host [srv 0 host]
|
||||||
|
set master_port [srv 0 port]
|
||||||
|
start_server {} {
|
||||||
|
r module load $testmodule
|
||||||
|
set replica [srv 0 client]
|
||||||
|
set replica_host [srv 0 host]
|
||||||
|
set replica_port [srv 0 port]
|
||||||
|
$replica replicaof $master_host $master_port
|
||||||
|
|
||||||
|
wait_for_condition 50 100 {
|
||||||
|
[string match {*master_link_status:up*} [r info replication]]
|
||||||
|
} else {
|
||||||
|
fail "Can't turn the instance into a replica"
|
||||||
|
}
|
||||||
|
|
||||||
|
test {Test master link up hook} {
|
||||||
|
assert_equal [r hooks.event_count masterlink-up] 1
|
||||||
|
assert_equal [r hooks.event_count masterlink-down] 0
|
||||||
|
}
|
||||||
|
|
||||||
|
test {Test role-replica hook} {
|
||||||
|
assert_equal [r hooks.event_count role-replica] 1
|
||||||
|
assert_equal [r hooks.event_count role-master] 0
|
||||||
|
assert_equal [r hooks.event_last role-replica] [s 0 master_host]
|
||||||
|
}
|
||||||
|
|
||||||
|
test {Test replica-online hook} {
|
||||||
|
assert_equal [r -1 hooks.event_count replica-online] 1
|
||||||
|
assert_equal [r -1 hooks.event_count replica-offline] 0
|
||||||
|
}
|
||||||
|
|
||||||
|
test {Test master link down hook} {
|
||||||
|
r client kill type master
|
||||||
|
assert_equal [r hooks.event_count masterlink-down] 1
|
||||||
|
}
|
||||||
|
|
||||||
|
$replica replicaof no one
|
||||||
|
|
||||||
|
test {Test role-master hook} {
|
||||||
|
assert_equal [r hooks.event_count role-replica] 1
|
||||||
|
assert_equal [r hooks.event_count role-master] 1
|
||||||
|
assert_equal [r hooks.event_last role-master] {}
|
||||||
|
}
|
||||||
|
|
||||||
|
test {Test replica-offline hook} {
|
||||||
|
assert_equal [r -1 hooks.event_count replica-online] 1
|
||||||
|
assert_equal [r -1 hooks.event_count replica-offline] 1
|
||||||
|
}
|
||||||
|
# get the replica stdout, to be used by the next test
|
||||||
|
set replica_stdout [srv 0 stdout]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# look into the log file of the server that just exited
|
||||||
|
test {Test shutdown hook} {
|
||||||
|
assert_equal [string match {*module-event-shutdown*} [exec tail -5 < $replica_stdout]] 1
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,4 +16,55 @@ start_server {tags {"modules"}} {
|
|||||||
assert { [string match "*cmdstat_module*" $info] }
|
assert { [string match "*cmdstat_module*" $info] }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test {test long double conversions} {
|
||||||
|
set ld [r test.ld_conversion]
|
||||||
|
assert {[string match $ld "0.00000000000000001"]}
|
||||||
|
}
|
||||||
|
|
||||||
|
test {test module db commands} {
|
||||||
|
r set x foo
|
||||||
|
set key [r test.randomkey]
|
||||||
|
assert_equal $key "x"
|
||||||
|
assert_equal [r test.dbsize] 1
|
||||||
|
r test.flushall
|
||||||
|
assert_equal [r test.dbsize] 0
|
||||||
|
}
|
||||||
|
|
||||||
|
test {test modle lru api} {
|
||||||
|
r config set maxmemory-policy allkeys-lru
|
||||||
|
r set x foo
|
||||||
|
set lru [r test.getlru x]
|
||||||
|
assert { $lru <= 1000 }
|
||||||
|
set was_set [r test.setlru x 100000]
|
||||||
|
assert { $was_set == 1 }
|
||||||
|
set idle [r object idletime x]
|
||||||
|
assert { $idle >= 100 }
|
||||||
|
set lru [r test.getlru x]
|
||||||
|
assert { $lru >= 100000 }
|
||||||
|
r config set maxmemory-policy allkeys-lfu
|
||||||
|
set lru [r test.getlru x]
|
||||||
|
assert { $lru == -1 }
|
||||||
|
set was_set [r test.setlru x 100000]
|
||||||
|
assert { $was_set == 0 }
|
||||||
|
}
|
||||||
|
r config set maxmemory-policy allkeys-lru
|
||||||
|
|
||||||
|
test {test modle lfu api} {
|
||||||
|
r config set maxmemory-policy allkeys-lfu
|
||||||
|
r set x foo
|
||||||
|
set lfu [r test.getlfu x]
|
||||||
|
assert { $lfu >= 1 }
|
||||||
|
set was_set [r test.setlfu x 100]
|
||||||
|
assert { $was_set == 1 }
|
||||||
|
set freq [r object freq x]
|
||||||
|
assert { $freq <= 100 }
|
||||||
|
set lfu [r test.getlfu x]
|
||||||
|
assert { $lfu <= 100 }
|
||||||
|
r config set maxmemory-policy allkeys-lru
|
||||||
|
set lfu [r test.getlfu x]
|
||||||
|
assert { $lfu == -1 }
|
||||||
|
set was_set [r test.setlfu x 100]
|
||||||
|
assert { $was_set == 0 }
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
47
tests/unit/moduleapi/scan.tcl
Normal file
47
tests/unit/moduleapi/scan.tcl
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
set testmodule [file normalize tests/modules/scan.so]
|
||||||
|
|
||||||
|
start_server {tags {"modules"}} {
|
||||||
|
r module load $testmodule
|
||||||
|
|
||||||
|
test {Module scan keyspace} {
|
||||||
|
# the module create a scan command with filtering which also return values
|
||||||
|
r set x 1
|
||||||
|
r set y 2
|
||||||
|
r set z 3
|
||||||
|
r hset h f v
|
||||||
|
lsort [r scan.scan_strings]
|
||||||
|
} {{x 1} {y 2} {z 3}}
|
||||||
|
|
||||||
|
test {Module scan hash ziplist} {
|
||||||
|
r hmset hh f1 v1 f2 v2
|
||||||
|
lsort [r scan.scan_key hh]
|
||||||
|
} {{f1 v1} {f2 v2}}
|
||||||
|
|
||||||
|
test {Module scan hash dict} {
|
||||||
|
r config set hash-max-ziplist-entries 2
|
||||||
|
r hmset hh f3 v3
|
||||||
|
lsort [r scan.scan_key hh]
|
||||||
|
} {{f1 v1} {f2 v2} {f3 v3}}
|
||||||
|
|
||||||
|
test {Module scan zset ziplist} {
|
||||||
|
r zadd zz 1 f1 2 f2
|
||||||
|
lsort [r scan.scan_key zz]
|
||||||
|
} {{f1 1} {f2 2}}
|
||||||
|
|
||||||
|
test {Module scan zset dict} {
|
||||||
|
r config set zset-max-ziplist-entries 2
|
||||||
|
r zadd zz 3 f3
|
||||||
|
lsort [r scan.scan_key zz]
|
||||||
|
} {{f1 1} {f2 2} {f3 3}}
|
||||||
|
|
||||||
|
test {Module scan set intset} {
|
||||||
|
r sadd ss 1 2
|
||||||
|
lsort [r scan.scan_key ss]
|
||||||
|
} {{1 {}} {2 {}}}
|
||||||
|
|
||||||
|
test {Module scan set dict} {
|
||||||
|
r config set set-max-intset-entries 2
|
||||||
|
r sadd ss 3
|
||||||
|
lsort [r scan.scan_key ss]
|
||||||
|
} {{1 {}} {2 {}} {3 {}}}
|
||||||
|
}
|
@ -536,7 +536,7 @@ foreach cmdrepl {0 1} {
|
|||||||
start_server {tags {"scripting repl"}} {
|
start_server {tags {"scripting repl"}} {
|
||||||
start_server {} {
|
start_server {} {
|
||||||
if {$cmdrepl == 1} {
|
if {$cmdrepl == 1} {
|
||||||
set rt "(commmands replication)"
|
set rt "(commands replication)"
|
||||||
} else {
|
} else {
|
||||||
set rt "(scripts replication)"
|
set rt "(scripts replication)"
|
||||||
r debug lua-always-replicate-commands 1
|
r debug lua-always-replicate-commands 1
|
||||||
|
@ -79,6 +79,12 @@ start_server {
|
|||||||
assert {[streamCompareID $id2 $id3] == -1}
|
assert {[streamCompareID $id2 $id3] == -1}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test {XADD IDs correctly report an error when overflowing} {
|
||||||
|
r DEL mystream
|
||||||
|
r xadd mystream 18446744073709551615-18446744073709551615 a b
|
||||||
|
assert_error ERR* {r xadd mystream * c d}
|
||||||
|
}
|
||||||
|
|
||||||
test {XADD with MAXLEN option} {
|
test {XADD with MAXLEN option} {
|
||||||
r DEL mystream
|
r DEL mystream
|
||||||
for {set j 0} {$j < 1000} {incr j} {
|
for {set j 0} {$j < 1000} {incr j} {
|
||||||
@ -117,6 +123,12 @@ start_server {
|
|||||||
assert {[r xlen mystream] == $j}
|
assert {[r xlen mystream] == $j}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test {XADD with ID 0-0} {
|
||||||
|
r DEL otherstream
|
||||||
|
catch {r XADD otherstream 0-0 k v} err
|
||||||
|
assert {[r EXISTS otherstream] == 0}
|
||||||
|
}
|
||||||
|
|
||||||
test {XRANGE COUNT works as expected} {
|
test {XRANGE COUNT works as expected} {
|
||||||
assert {[llength [r xrange mystream - + COUNT 10]] == 10}
|
assert {[llength [r xrange mystream - + COUNT 10]] == 10}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user