Compare commits

..
22 Commits
Author SHA1 Message Date
John Sully 07cb1f4581 Merge branch 'unstable' into RELEASE_5 2020-03-30 00:51:55 -04:00
John Sully f2ca2aa1f7 Allow active replicas to connect to each other when replica-serve-stale-data is set 2020-03-30 00:51:40 -04:00
John Sully b20bf10fda Don't send build log to stderr 2020-03-28 23:36:57 -04:00
John Sully fa6870a268 Merge branch 'unstable' into RELEASE_5 2020-03-27 14:36:35 -04:00
John Sully c3d3c82f3a Raspberry Pi fixes (compile and replication) 2020-03-27 12:46:25 -04:00
John Sully f534b5b3a2 OS X Build break fix 2020-03-26 15:21:35 -04:00
John Sully 27da034819 bump version 2020-03-26 01:19:21 -04:00
John Sully 6ec1641294 Fix issue #143 2020-03-25 21:55:31 -04:00
John Sully 7b79ec360b Give a better error when handling std::terminate 2020-03-25 16:27:24 -04:00
John Sully 56fc6b7deb Add roundtrip test for subkey expires 2020-03-25 15:34:30 -04:00
John Sully 528a43bd6c Fix failure to load subkey expires 2020-03-25 01:38:58 -04:00
John Sully b357803362 Expire entry needs to be resorted after a subkey expires 2020-03-25 01:06:40 -04:00
John Sully 448808529a Unstable branch should always be version 0.0.0 2020-03-19 15:52:24 -04:00
John Sully a0bc208412 Merge branch 'RELEASE_5' into unstable 2020-03-19 15:38:16 -04:00
John Sully 6b0172b9bf Print stack traces of other threads in the deadlock detector 2020-03-19 15:37:49 -04:00
John Sully bc1eccb66d Log which thread a message came from 2020-03-19 15:37:24 -04:00
John Sully a9249d4a82 Fix lock inversion in processEventsWhileBlocked 2020-03-19 15:28:39 -04:00
John Sully 3c373494d6 Prevent dangling lock when we can't free the client 2020-03-19 14:34:23 -04:00
John Sully 5aef606d3b Fix issue #153: Config get replicaof returns a corrupt response 2020-03-16 20:43:46 -04:00
John Sully 2ec0b2cd20 Handle HTTP error codes with MOTD 2020-03-15 18:47:01 -04:00
John Sully cd76a71de3 Update MOTD URL 2020-03-15 18:32:05 -04:00
John Sully c2a98a60f9 Fix CLANG build break 2020-03-04 17:23:40 -05:00
14 changed files with 201 additions and 95 deletions

No files matched your search

+8 -4
View File
@@ -82,6 +82,10 @@ ifneq (,$(findstring armv,$(uname_M)))
endif
endif
ifneq (,$(findstring armv,$(uname_M)))
FINAL_LIBS+=-latomic
endif
# Backwards compatibility for selecting an allocator
ifeq ($(USE_TCMALLOC),yes)
MALLOC=tcmalloc
@@ -105,7 +109,7 @@ endif
FINAL_CFLAGS=$(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS) $(REDIS_CFLAGS)
FINAL_CXXFLAGS=$(CXX_STD) $(WARN) $(OPT) $(DEBUG) $(CXXFLAGS) $(REDIS_CFLAGS)
FINAL_LDFLAGS=$(LDFLAGS) $(REDIS_LDFLAGS) $(DEBUG)
FINAL_LIBS=-lm -lcurl
FINAL_LIBS+=-lm -lcurl
DEBUG=-g -ggdb
ifeq ($(uname_S),SunOS)
@@ -206,9 +210,9 @@ MAKECOLOR="\033[32;1m"
ENDCOLOR="\033[0m"
ifndef V
QUIET_CC = @printf ' %b %b\n' $(CCCOLOR)CC$(ENDCOLOR) $(SRCCOLOR)$@$(ENDCOLOR) 1>&2;
QUIET_LINK = @printf ' %b %b\n' $(LINKCOLOR)LINK$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR) 1>&2;
QUIET_INSTALL = @printf ' %b %b\n' $(LINKCOLOR)INSTALL$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR) 1>&2;
QUIET_CC = @printf ' %b %b\n' $(CCCOLOR)CC$(ENDCOLOR) $(SRCCOLOR)$@$(ENDCOLOR);
QUIET_LINK = @printf ' %b %b\n' $(LINKCOLOR)LINK$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR);
QUIET_INSTALL = @printf ' %b %b\n' $(LINKCOLOR)INSTALL$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR);
endif
REDIS_SERVER_NAME=keydb-server
+7
View File
@@ -1496,8 +1496,15 @@ void configGetCommand(client *c) {
listIter li;
listNode *ln;
listRewind(g_pserver->masters, &li);
bool fFirst = true;
while ((ln = listNext(&li)))
{
if (!fFirst)
{
addReplyBulkCString(c,optname);
matches++;
}
fFirst = false;
struct redisMaster *mi = (struct redisMaster*)listNodeValue(ln);
snprintf(buf,sizeof(buf),"%s %d",
mi->masterhost, mi->masterport);
+9
View File
@@ -135,6 +135,15 @@ void activeExpireCycleExpire(redisDb *db, expireEntry &e, long long now) {
if (deleted)
{
if (!pfat->FEmpty())
{
// We need to resort the expire entry since it may no longer be in the correct position
auto itr = db->setexpire->find(e.key());
expireEntry eT = std::move(e);
db->setexpire->erase(itr);
db->setexpire->insert(eT);
}
robj objT;
switch (val->type)
{
+67 -26
View File
@@ -27,6 +27,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "fmacros.h"
#include "fastlock.h"
#include <unistd.h>
#include <sys/syscall.h>
@@ -43,6 +44,7 @@
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include "config.h"
#ifdef __APPLE__
#include <TargetConditionals.h>
@@ -60,6 +62,11 @@
#define UNUSED(x) ((void)x)
#endif
#ifdef HAVE_BACKTRACE
#include <ucontext.h>
__attribute__((weak)) void logStackTrace(ucontext_t *) {}
#endif
extern int g_fInCrash;
/****************************************************
@@ -149,6 +156,43 @@ __attribute__((weak)) void serverLog(int , const char *fmt, ...)
printf("\n");
}
extern "C" pid_t gettid()
{
static thread_local int pidCache = -1;
#ifdef __linux__
if (pidCache == -1)
pidCache = syscall(SYS_gettid);
#else
if (pidCache == -1) {
uint64_t tidT;
pthread_threadid_np(nullptr, &tidT);
assert(tidT < UINT_MAX);
pidCache = (int)tidT;
}
#endif
return pidCache;
}
void printTrace()
{
#ifdef HAVE_BACKTRACE
serverLog(3 /*LL_WARNING*/, "printing backtrace for thread %d", gettid());
ucontext_t ctxt;
getcontext(&ctxt);
logStackTrace(&ctxt);
#endif
}
#ifdef __linux__
static int futex(volatile unsigned *uaddr, int futex_op, int val,
const struct timespec *timeout, int val3)
{
return syscall(SYS_futex, uaddr, futex_op, val,
timeout, uaddr, val3);
}
#endif
class DeadlockDetector
{
std::map<pid_t, fastlock *> m_mapwait;
@@ -156,9 +200,19 @@ class DeadlockDetector
public:
void registerwait(fastlock *lock, pid_t thispid)
{
static volatile bool fInDeadlock = false;
if (lock == &m_lock || g_fInCrash)
return;
fastlock_lock(&m_lock);
if (fInDeadlock)
{
printTrace();
fastlock_unlock(&m_lock);
return;
}
m_mapwait.insert(std::make_pair(thispid, lock));
// Detect cycles
@@ -184,6 +238,19 @@ public:
if (pidCheck == thispid)
break;
}
// Wake All sleeping threads so they can print their callstacks
#ifdef HAVE_BACKTRACE
#ifdef __linux__
int mask = -1;
fInDeadlock = true;
fastlock_unlock(&m_lock);
futex(&lock->m_ticket.u, FUTEX_WAKE_BITSET_PRIVATE, INT_MAX, nullptr, mask);
futex(&itr->second->m_ticket.u, FUTEX_WAKE_BITSET_PRIVATE, INT_MAX, nullptr, mask);
sleep(2);
fastlock_lock(&m_lock);
printTrace();
#endif
#endif
serverLog(3 /*LL_WARNING*/, "!!! KeyDB Will Now Crash !!!");
_serverPanic(__FILE__, __LINE__, "Deadlock detected");
}
@@ -222,32 +289,6 @@ uint64_t fastlock_getlongwaitcount()
return rval;
}
#ifdef __linux__
static int futex(volatile unsigned *uaddr, int futex_op, int val,
const struct timespec *timeout, int val3)
{
return syscall(SYS_futex, uaddr, futex_op, val,
timeout, uaddr, val3);
}
#endif
extern "C" pid_t gettid()
{
static thread_local int pidCache = -1;
#ifdef __linux__
if (pidCache == -1)
pidCache = syscall(SYS_gettid);
#else
if (pidCache == -1) {
uint64_t tidT;
pthread_threadid_np(nullptr, &tidT);
assert(tidT < UINT_MAX);
pidCache = (int)tidT;
}
#endif
return pidCache;
}
extern "C" void fastlock_sleep(fastlock *lock, pid_t pid, unsigned wake, unsigned mask)
{
#ifdef __linux__
+13 -2
View File
@@ -23,7 +23,7 @@ static const char *szMotdCachePath()
return "";
const char *homedir = pw->pw_dir;
sdsMotdCachePath = sdsnew(homedir);
sdsMotdCachePath = sdscat(sdsMotdCachePath, "/.keydb-cli-motd");
sdsMotdCachePath = sdscat(sdsMotdCachePath, motd_cache_file);
return sdsMotdCachePath;
}
static size_t motd_write_callback(void *ptr, size_t size, size_t nmemb, sds *str)
@@ -87,7 +87,7 @@ extern "C" char *fetchMOTD(int cache)
str = sdsnew("");
curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_URL, "http://api.keydb.dev/motd/motd.txt");
curl_easy_setopt(curl, CURLOPT_URL, motd_url);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); // follow redirects
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 2); // take no more than two seconds
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, motd_write_callback);
@@ -101,6 +101,17 @@ extern "C" char *fetchMOTD(int cache)
sdsfree(str);
str = NULL;
}
else
{
long response_code;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
if ((response_code / 100) != 2)
{
// An error code not in the 200s implies an error
sdsfree(str);
str = NULL;
}
}
/* always cleanup */
curl_easy_cleanup(curl);
+3
View File
@@ -1,5 +1,8 @@
#pragma once
extern const char *motd_url;
extern const char *motd_cache_file;
#ifdef __cplusplus
extern "C" {
#endif
+26 -12
View File
@@ -496,7 +496,7 @@ void addReplyErrorLengthCore(client *c, const char *s, size_t len, bool fAsync)
if (c->querybuf && sdslen(c->querybuf)) {
std::string str = escapeString(c->querybuf);
serverLog(LL_WARNING, "\tquerybuf: %s", str.c_str());
printf("\tquerybuf: %s\n", str.c_str());
}
c->master_error = 1;
}
@@ -1633,7 +1633,10 @@ void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) {
c->lock.lock();
ae.arm(c);
if (c->flags & CLIENT_CLOSE_ASAP)
freeClient(c);
{
if (!freeClient(c))
c->lock.unlock();
}
}
}
@@ -3104,12 +3107,23 @@ int processEventsWhileBlocked(int iel) {
int iterations = 4; /* See the function top-comment. */
int count = 0;
client *c = serverTL->current_client;
if (c != nullptr)
std::vector<client*> vecclients;
listIter li;
listNode *ln;
listRewind(g_pserver->clients, &li);
// All client locks must be acquired *after* the global lock is reacquired to prevent deadlocks
// so unlock here, and save them for reacquisition later
while ((ln = listNext(&li)) != nullptr)
{
serverAssert(c->flags & CLIENT_PROTECTED);
c->lock.unlock();
client *c = (client*)listNodeValue(ln);
if (c->lock.fOwnLock()) {
serverAssert(c->flags & CLIENT_PROTECTED); // If the client is not protected we have no gurantee they won't be free'd in the event loop
c->lock.unlock();
vecclients.push_back(c);
}
}
aeReleaseLock();
try
@@ -3126,18 +3140,18 @@ int processEventsWhileBlocked(int iel) {
{
// Caller expects us to be locked so fix and rethrow
AeLocker locker;
if (c != nullptr)
c->lock.lock();
locker.arm(c);
locker.arm(nullptr);
locker.release();
for (client *c : vecclients)
c->lock.lock();
throw;
}
AeLocker locker;
if (c != nullptr)
c->lock.lock();
locker.arm(c);
locker.arm(nullptr);
locker.release();
for (client *c : vecclients)
c->lock.lock();
return count;
}
+15 -3
View File
@@ -2095,7 +2095,7 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
incrRefCount(subexpireKey);
} else if (!strcasecmp(szFromObj(auxkey), "keydb-subexpire-when")) {
if (key == nullptr || subexpireKey == nullptr) {
serverLog(LL_WARNING, "Corrupt subexpire entry in RDB skipping.");
serverLog(LL_WARNING, "Corrupt subexpire entry in RDB skipping. key: %s subkey: %s", key != nullptr ? szFromObj(key) : "(null)", subexpireKey != nullptr ? szFromObj(subexpireKey) : "(null)");
}
else {
setExpire(NULL, db, key, subexpireKey, strtoll(szFromObj(auxval), nullptr, 10));
@@ -2187,8 +2187,6 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
decrRefCount(val);
val = nullptr;
}
decrRefCount(key);
key = nullptr;
}
/* Reset the state that is key-specified and is populated by
@@ -2199,7 +2197,10 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
}
if (key != nullptr)
{
decrRefCount(key);
key = nullptr;
}
if (subexpireKey != nullptr)
{
@@ -2226,6 +2227,17 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
return C_OK;
eoferr: /* unexpected end of file is handled here with a fatal exit */
if (key != nullptr)
{
decrRefCount(key);
key = nullptr;
}
if (subexpireKey != nullptr)
{
decrRefCount(subexpireKey);
subexpireKey = nullptr;
}
serverLog(LL_WARNING,"Short read or OOM loading DB. Unrecoverable error, aborting now.");
rdbExitReportCorruptRDB("Unexpected EOF reading RDB file");
return C_ERR; /* Just to avoid warning */
+3
View File
@@ -93,6 +93,9 @@ int spectrum_palette_size;
int g_fInCrash = 0;
const char *motd_url = "http://api.keydb.dev/motd/motd_cli.txt";
const char *motd_cache_file = "/.keydb-cli-motd";
/*------------------------------------------------------------------------------
* Utility functions
*--------------------------------------------------------------------------- */
+22 -32
View File
@@ -357,8 +357,8 @@ void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc) {
char szMvcc[128];
incrementMvccTstamp();
uint64_t mvccTstamp = getMvccTstamp();
int cchMvccNum = snprintf(szMvcc, sizeof(szMvcc), "%lu", mvccTstamp);
int cchMvcc = snprintf(szMvcc, sizeof(szMvcc), "$%d\r\n%lu\r\n", cchMvccNum, mvccTstamp);
int cchMvccNum = snprintf(szMvcc, sizeof(szMvcc), "%" PRIu64, mvccTstamp);
int cchMvcc = snprintf(szMvcc, sizeof(szMvcc), "$%d\r\n%" PRIu64 "\r\n", cchMvccNum, mvccTstamp);
cchMvcc = std::min<int>(cchMvcc, sizeof(szMvcc)); // tricky snprintf
/* Write the command to the replication backlog if any. */
@@ -2425,8 +2425,6 @@ void freeMasterInfo(redisMaster *mi)
{
zfree(mi->masterauth);
zfree(mi->masteruser);
if (mi->clientFake)
freeClient(mi->clientFake);
delete mi->staleKeyMap;
zfree(mi);
}
@@ -2484,11 +2482,6 @@ void replicationHandleMasterDisconnection(redisMaster *mi) {
mi->master = NULL;
mi->repl_state = REPL_STATE_CONNECT;
mi->repl_down_since = g_pserver->unixtime;
if (mi->clientFake) {
freeClient(mi->clientFake);
mi->clientFake = nullptr;
}
/* We lost connection with our master, don't disconnect slaves yet,
* maybe we'll be able to PSYNC with our master later. We'll disconnect
* the slaves only if we'll have to do a full resync with our master. */
@@ -3378,8 +3371,13 @@ bool FInReplicaReplay()
return s_pstate != nullptr && s_pstate->nesting() > 0;
}
struct RemoteMasterState
{
uint64_t mvcc = 0;
client *cFake = nullptr;
};
static std::unordered_map<std::string, uint64_t> g_mapmvcc;
static std::unordered_map<std::string, RemoteMasterState> g_mapremote;
void replicaReplayCommand(client *c)
{
@@ -3455,12 +3453,15 @@ void replicaReplayCommand(client *c)
if (!s_pstate->FPush())
return;
redisMaster *mi = s_pstate->getMi(c);
client *cFake = mi->clientFake;
if (mi->clientFakeNesting != s_pstate->nesting())
cFake = nullptr;
serverAssert(mi != nullptr);
if (mvcc != 0 && g_mapmvcc[uuid] >= mvcc)
RemoteMasterState &remoteState = g_mapremote[uuid];
if (remoteState.cFake == nullptr)
remoteState.cFake = createClient(-1, c->iel);
else
remoteState.cFake->iel = c->iel;
client *cFake = remoteState.cFake;
if (mvcc != 0 && remoteState.mvcc >= mvcc)
{
s_pstate->Cancel();
s_pstate->Pop();
@@ -3469,8 +3470,6 @@ void replicaReplayCommand(client *c)
// OK We've recieved a command lets execute
client *current_clientSave = serverTL->current_client;
if (cFake == nullptr)
cFake = createClient(-1, c->iel);
cFake->lock.lock();
cFake->authenticated = c->authenticated;
cFake->puser = c->puser;
@@ -3483,13 +3482,15 @@ void replicaReplayCommand(client *c)
bool fExec = ccmdPrev != serverTL->commandsExecuted;
cFake->lock.unlock();
if (cFake->master_error)
addReplyError(c, "Error in rreplay command, please check logs");
{
addReplyError(c, "Error in rreplay command, please check logs.");
}
if (fExec || cFake->flags & CLIENT_MULTI)
{
addReply(c, shared.ok);
selectDb(c, cFake->db->id);
if (mvcc > g_mapmvcc[uuid])
g_mapmvcc[uuid] = mvcc;
if (mvcc > remoteState.mvcc)
remoteState.mvcc = mvcc;
}
else
{
@@ -3497,17 +3498,6 @@ void replicaReplayCommand(client *c)
addReplyError(c, "command did not execute");
}
serverAssert(sdslen(cFake->querybuf) == 0);
if (cFake->flags & CLIENT_MULTI)
{
mi->clientFake = cFake;
mi->clientFakeNesting = s_pstate->nesting();
}
else
{
if (mi->clientFake == cFake)
mi->clientFake = nullptr;
freeClient(cFake);
}
serverTL->current_client = current_clientSave;
// call() will not propogate this for us, so we do so here
+14 -9
View File
@@ -65,6 +65,8 @@
#include "motd.h"
int g_fTestMode = false;
const char *motd_url = "http://api.keydb.dev/motd/motd_server.txt";
const char *motd_cache_file = "/.keydb-server-motd";
/* Our shared "common" objects */
@@ -1037,6 +1039,7 @@ struct redisCommand redisCommandTable[] = {
/* We use a private localtime implementation which is fork-safe. The logging
* function of Redis may be called from other threads. */
extern "C" void nolocks_localtime(struct tm *tmp, time_t t, time_t tz, int dst);
extern "C" pid_t gettid();
/* Low level logging. To use only for very big messages, otherwise
* serverLog() is to prefer. */
@@ -1074,8 +1077,8 @@ void serverLogRaw(int level, const char *msg) {
} else {
role_char = (listLength(g_pserver->masters) ? 'S':'M'); /* Slave or Master. */
}
fprintf(fp,"%d:%c %s %c %s\n",
(int)getpid(),role_char, buf,c[level],msg);
fprintf(fp,"%d:%d:%c %s %c %s\n",
(int)getpid(),(int)gettid(),role_char, buf,c[level],msg);
}
fflush(fp);
@@ -3590,12 +3593,12 @@ int processCommand(client *c, int callFlags) {
return C_OK;
}
}
incrementMvccTstamp();
if (!locker.isArmed())
locker.arm(c);
incrementMvccTstamp();
/* Handle the maxmemory directive.
*
* Note that we do not want to reclaim memory if we are here re-entering
@@ -3679,7 +3682,8 @@ int processCommand(client *c, int callFlags) {
* link with master. */
if (FBrokenLinkToMaster() &&
g_pserver->repl_serve_stale_data == 0 &&
!(c->cmd->flags & CMD_STALE))
!(c->cmd->flags & CMD_STALE)
&& !(g_pserver->fActiveReplica && c->cmd->proc == syncCommand))
{
flagTransaction(c);
addReply(c, shared.masterdownerr);
@@ -5015,14 +5019,15 @@ void incrementMvccTstamp()
msPrev >>= MVCC_MS_SHIFT; // convert to milliseconds
long long mst;
__atomic_load(&g_pserver->mstime, &mst, __ATOMIC_RELAXED);
__atomic_load(&g_pserver->mstime, &mst, __ATOMIC_ACQUIRE);
if (msPrev >= (uint64_t)mst) // we can be greater if the count overflows
{
atomicIncr(g_pserver->mvcc_tstamp, 1);
__atomic_fetch_add(&g_pserver->mvcc_tstamp, 1, __ATOMIC_RELEASE);
}
else
{
atomicSet(g_pserver->mvcc_tstamp, ((uint64_t)g_pserver->mstime) << MVCC_MS_SHIFT);
uint64_t val = ((uint64_t)g_pserver->mstime) << MVCC_MS_SHIFT;
__atomic_store(&g_pserver->mvcc_tstamp, &val, __ATOMIC_RELEASE);
}
}
@@ -5033,7 +5038,7 @@ void OnTerminate()
The easiest way to achieve that is to acutally segfault, so we assert
here.
*/
serverAssert(false);
serverPanic("std::teminate() called");
}
void *workerThreadMain(void *parg)
-2
View File
@@ -1541,8 +1541,6 @@ struct redisMaster {
int masterport; /* Port of master */
client *cached_master; /* Cached master to be reused for PSYNC. */
client *master;
client *clientFake;
int clientFakeNesting;
/* The following two fields is where we store master PSYNC replid/offset
* while the PSYNC is in progress. At the end we'll copy the fields into
* the server->master client structure. */
+1 -1
View File
@@ -1,3 +1,3 @@
#define KEYDB_REAL_VERSION "5.3.2"
#define KEYDB_REAL_VERSION "5.3.3"
extern const char *KEYDB_SET_VERSION; // Unlike real version, this can be overriden by the config
+13 -4
View File
@@ -220,7 +220,7 @@ start_server {tags {"expire"}} {
assert {$ttl <= 98 && $ttl > 90}
}
test { EXPIREMEMBER works (set) } {
test {EXPIREMEMBER works (set)} {
r flushall
r sadd testkey foo bar baz
r expiremember testkey foo 1
@@ -228,7 +228,7 @@ start_server {tags {"expire"}} {
assert_equal {2} [r scard testkey]
}
test { EXPIREMEMBER works (hash) } {
test {EXPIREMEMBER works (hash)} {
r flushall
r hset testkey foo bar
r expiremember testkey foo 1
@@ -236,7 +236,7 @@ start_server {tags {"expire"}} {
r exists testkey
} {0}
test { EXPIREMEMBER works (zset) } {
test {EXPIREMEMBER works (zset)} {
r flushall
r zadd testkey 1 foo
r zadd testkey 2 bar
@@ -246,10 +246,19 @@ start_server {tags {"expire"}} {
assert_equal {1} [r zcard testkey]
}
test { TTL for subkey expires works } {
test {TTL for subkey expires works} {
r flushall
r sadd testkey foo bar baz
r expiremember testkey foo 10000
assert [expr [r ttl testkey foo] > 0]
}
test {Roundtrip for subkey expires works} {
r flushall
r sadd testkey foo bar baz
r expiremember testkey foo 10000
r save
r debug reload
assert [expr [r ttl testkey foo] > 0]
}
}