summaryrefslogtreecommitdiff
path: root/src/t_string.c
diff options
context:
space:
mode:
authorny0312 <49037844+ny0312@users.noreply.github.com>2021-05-29 23:20:32 -0700
committerGitHub <noreply@github.com>2021-05-30 09:20:32 +0300
commit53d1acd598b689b2bbc470d907b9e40e548d63f6 (patch)
tree5c2795c16dfced605f0eaadc7c289058ff5e5310 /src/t_string.c
parent501d7755831527b4237f9ed6050ec84203934e4d (diff)
downloadredis-53d1acd598b689b2bbc470d907b9e40e548d63f6.tar.gz
Always replicate time-to-live(TTL) as absolute timestamps in milliseconds (#8474)
Till now, on replica full-sync we used to transfer absolute time for TTL, however when a command arrived (EXPIRE or EXPIREAT), we used to propagate it as is to replicas (possibly with relative time), but always translate it to EXPIREAT (absolute time) to AOF. This commit changes that and will always use absolute time for propagation. see discussion in #8433 Furthermore, we Introduce new commands: `EXPIRETIME/PEXPIRETIME` that allow extracting the absolute TTL time from a key.
Diffstat (limited to 'src/t_string.c')
-rw-r--r--src/t_string.c121
1 files changed, 62 insertions, 59 deletions
diff --git a/src/t_string.c b/src/t_string.c
index db6f7042e..99843c863 100644
--- a/src/t_string.c
+++ b/src/t_string.c
@@ -72,26 +72,13 @@ static int checkStringLength(client *c, long long size) {
#define OBJ_PXAT (1<<7) /* Set if timestamp in ms is given */
#define OBJ_PERSIST (1<<8) /* Set if we need to remove the ttl */
-void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire, int unit, robj *ok_reply, robj *abort_reply) {
- long long milliseconds = 0, when = 0; /* initialized to avoid any harmness warning */
+/* Forward declaration */
+static int getExpireMillisecondsOrReply(client *c, robj *expire, int flags, int unit, long long *milliseconds);
- if (expire) {
- if (getLongLongFromObjectOrReply(c, expire, &milliseconds, NULL) != C_OK)
- return;
- if (milliseconds <= 0 || (unit == UNIT_SECONDS && milliseconds > LLONG_MAX / 1000)) {
- /* Negative value provided or multiplication is gonna overflow. */
- addReplyErrorFormat(c, "invalid expire time in %s", c->cmd->name);
- return;
- }
- if (unit == UNIT_SECONDS) milliseconds *= 1000;
- when = milliseconds;
- if ((flags & OBJ_PX) || (flags & OBJ_EX))
- when += mstime();
- if (when <= 0) {
- /* Overflow detected. */
- addReplyErrorFormat(c, "invalid expire time in %s", c->cmd->name);
- return;
- }
+void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire, int unit, robj *ok_reply, robj *abort_reply) {
+ long long milliseconds = 0; /* initialized to avoid any harmness warning */
+ if (expire && getExpireMillisecondsOrReply(c, expire, flags, unit, &milliseconds) != C_OK) {
+ return;
}
if ((flags & OBJ_SET_NX && lookupKeyWrite(c->db,key) != NULL) ||
@@ -108,24 +95,17 @@ void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire,
genericSetKey(c,c->db,key, val,flags & OBJ_KEEPTTL,1);
server.dirty++;
notifyKeyspaceEvent(NOTIFY_STRING,"set",key,c->db->id);
+
if (expire) {
- setExpire(c,c->db,key,when);
+ setExpire(c,c->db,key,milliseconds);
+ /* Propagate as SET Key Value PXAT millisecond-timestamp if there is
+ * EX/PX/EXAT/PXAT flag. */
+ robj *milliseconds_obj = createStringObjectFromLongLong(milliseconds);
+ rewriteClientCommandVector(c, 5, shared.set, key, val, shared.pxat, milliseconds_obj);
+ decrRefCount(milliseconds_obj);
notifyKeyspaceEvent(NOTIFY_GENERIC,"expire",key,c->db->id);
-
- /* Propagate as SET Key Value PXAT millisecond-timestamp if there is EXAT/PXAT or
- * propagate as SET Key Value PX millisecond if there is EX/PX flag.
- *
- * Additionally when we propagate the SET with PX (relative millisecond) we translate
- * it again to SET with PXAT for the AOF.
- *
- * Additional care is required while modifying the argument order. AOF relies on the
- * exp argument being at index 3. (see feedAppendOnlyFile)
- * */
- robj *exp = (flags & OBJ_PXAT) || (flags & OBJ_EXAT) ? shared.pxat : shared.px;
- robj *millisecondObj = createStringObjectFromLongLong(milliseconds);
- rewriteClientCommandVector(c,5,shared.set,key,val,exp,millisecondObj);
- decrRefCount(millisecondObj);
}
+
if (!(flags & OBJ_SET_GET)) {
addReply(c, ok_reply ? ok_reply : shared.ok);
}
@@ -150,6 +130,45 @@ void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire,
}
}
+/*
+ * Extract the `expire` argument of a given GET/SET command as an absolute timestamp in milliseconds.
+ *
+ * "client" is the client that sent the `expire` argument.
+ * "expire" is the `expire` argument to be extracted.
+ * "flags" represents the behavior of the command (e.g. PX or EX).
+ * "unit" is the original unit of the given `expire` argument (e.g. UNIT_SECONDS).
+ * "milliseconds" is output argument.
+ *
+ * If return C_OK, "milliseconds" output argument will be set to the resulting absolute timestamp.
+ * If return C_ERR, an error reply has been added to the given client.
+ */
+static int getExpireMillisecondsOrReply(client *c, robj *expire, int flags, int unit, long long *milliseconds) {
+ int ret = getLongLongFromObjectOrReply(c, expire, milliseconds, NULL);
+ if (ret != C_OK) {
+ return ret;
+ }
+
+ if (*milliseconds <= 0 || (unit == UNIT_SECONDS && *milliseconds > LLONG_MAX / 1000)) {
+ /* Negative value provided or multiplication is gonna overflow. */
+ addReplyErrorFormat(c, "invalid expire time in %s", c->cmd->name);
+ return C_ERR;
+ }
+
+ if (unit == UNIT_SECONDS) *milliseconds *= 1000;
+
+ if ((flags & OBJ_PX) || (flags & OBJ_EX)) {
+ *milliseconds += mstime();
+ }
+
+ if (*milliseconds <= 0) {
+ /* Overflow detected. */
+ addReplyErrorFormat(c,"invalid expire time in %s",c->cmd->name);
+ return C_ERR;
+ }
+
+ return C_OK;
+}
+
#define COMMAND_GET 0
#define COMMAND_SET 1
/*
@@ -338,26 +357,10 @@ void getexCommand(client *c) {
return;
}
- long long milliseconds = 0, when = 0;
-
/* Validate the expiration time value first */
- if (expire) {
- if (getLongLongFromObjectOrReply(c, expire, &milliseconds, NULL) != C_OK)
- return;
- if (milliseconds <= 0 || (unit == UNIT_SECONDS && milliseconds > LLONG_MAX / 1000)) {
- /* Negative value provided or multiplication is gonna overflow. */
- addReplyErrorFormat(c, "invalid expire time in %s", c->cmd->name);
- return;
- }
- if (unit == UNIT_SECONDS) milliseconds *= 1000;
- when = milliseconds;
- if ((flags & OBJ_PX) || (flags & OBJ_EX))
- when += mstime();
- if (when <= 0) {
- /* Overflow detected. */
- addReplyErrorFormat(c, "invalid expire time in %s", c->cmd->name);
- return;
- }
+ long long milliseconds = 0;
+ if (expire && getExpireMillisecondsOrReply(c, expire, flags, unit, &milliseconds) != C_OK) {
+ return;
}
/* We need to do this before we expire the key or delete it */
@@ -377,12 +380,12 @@ void getexCommand(client *c) {
notifyKeyspaceEvent(NOTIFY_GENERIC, "del", c->argv[1], c->db->id);
server.dirty++;
} else if (expire) {
- setExpire(c,c->db,c->argv[1],when);
- /* Propagate */
- robj *exp = (flags & OBJ_PXAT) || (flags & OBJ_EXAT) ? shared.pexpireat : shared.pexpire;
- robj* millisecondObj = createStringObjectFromLongLong(milliseconds);
- rewriteClientCommandVector(c,3,exp,c->argv[1],millisecondObj);
- decrRefCount(millisecondObj);
+ setExpire(c,c->db,c->argv[1],milliseconds);
+ /* Propagate as PXEXPIREAT millisecond-timestamp if there is
+ * EX/PX/EXAT/PXAT flag and the key has not expired. */
+ robj *milliseconds_obj = createStringObjectFromLongLong(milliseconds);
+ rewriteClientCommandVector(c,3,shared.pexpireat,c->argv[1],milliseconds_obj);
+ decrRefCount(milliseconds_obj);
signalModifiedKey(c, c->db, c->argv[1]);
notifyKeyspaceEvent(NOTIFY_GENERIC,"expire",c->argv[1],c->db->id);
server.dirty++;