mirror of https://mirror.osredm.com/root/redis.git
Fix KSN for HSETEX command when FXX/FNX is used (#14150)
When HSETEX fails due to FXX/FNX, it may still expire some fields due to lazy expiry. Though, it does not send “hexpired” notification in this case.
This commit is contained in:
parent
1e388d8b95
commit
a4ff8d6ab6
29
src/t_hash.c
29
src/t_hash.c
|
@ -2331,6 +2331,7 @@ err_expiration:
|
|||
void hsetexCommand(client *c) {
|
||||
int flags = 0, first_field_pos = 0, field_count = 0, expire_time_pos = -1;
|
||||
int updated = 0, deleted = 0, set_expiry;
|
||||
int expired = 0, fields_set = 0;
|
||||
long long expire_time = EB_EXPIRE_TIME_INVALID;
|
||||
int64_t oldlen, newlen;
|
||||
HashTypeSetEx setex;
|
||||
|
@ -2358,12 +2359,18 @@ void hsetexCommand(client *c) {
|
|||
int found = 0;
|
||||
for (int i = 0; i < field_count; i++) {
|
||||
sds field = c->argv[first_field_pos + (i * 2)]->ptr;
|
||||
unsigned char *vstr = NULL;
|
||||
unsigned int vlen = UINT_MAX;
|
||||
long long vll = LLONG_MAX;
|
||||
const int opt = HFE_LAZY_NO_NOTIFICATION |
|
||||
HFE_LAZY_NO_SIGNAL |
|
||||
HFE_LAZY_AVOID_HASH_DEL |
|
||||
HFE_LAZY_NO_UPDATE_KEYSIZES;
|
||||
int exists = hashTypeExists(c->db, o, field, opt, NULL);
|
||||
found += (exists != 0);
|
||||
|
||||
GetFieldRes res = hashTypeGetValue(c->db, o, field, &vstr, &vlen, &vll, opt, NULL);
|
||||
int exists = (res == GETF_OK);
|
||||
expired += (res == GETF_EXPIRED);
|
||||
found += exists;
|
||||
|
||||
/* Check for early exit if the condition is already invalid. */
|
||||
if (((flags & HFE_FXX) && !exists) ||
|
||||
|
@ -2400,7 +2407,7 @@ void hsetexCommand(client *c) {
|
|||
opt |= HASH_SET_KEEP_TTL;
|
||||
|
||||
hashTypeSet(c->db, o, field, value, opt);
|
||||
|
||||
fields_set = 1;
|
||||
/* Update the expiration time. */
|
||||
if (set_expiry) {
|
||||
int ret = hashTypeSetEx(o, field, expire_time, &setex);
|
||||
|
@ -2413,11 +2420,6 @@ void hsetexCommand(client *c) {
|
|||
hashTypeSetExDone(&setex);
|
||||
|
||||
server.dirty += field_count;
|
||||
signalModifiedKey(c, c->db, c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_HASH, "hset", c->argv[1], c->db->id);
|
||||
if (deleted || updated)
|
||||
notifyKeyspaceEvent(NOTIFY_HASH, deleted ? "hdel": "hexpire",
|
||||
c->argv[1], c->db->id);
|
||||
|
||||
if (deleted) {
|
||||
/* If fields are deleted due to timestamp is being in the past, hdel's
|
||||
|
@ -2437,6 +2439,17 @@ void hsetexCommand(client *c) {
|
|||
addReplyLongLong(c, 1);
|
||||
|
||||
out:
|
||||
/* Emit keyspace notifications based on field expiry, mutation, or key deletion */
|
||||
if (fields_set || expired) {
|
||||
signalModifiedKey(c, c->db, c->argv[1]);
|
||||
if (expired)
|
||||
notifyKeyspaceEvent(NOTIFY_HASH, "hexpired", c->argv[1], c->db->id);
|
||||
if (fields_set) {
|
||||
notifyKeyspaceEvent(NOTIFY_HASH, "hset", c->argv[1], c->db->id);
|
||||
if (deleted || updated)
|
||||
notifyKeyspaceEvent(NOTIFY_HASH, deleted ? "hdel" : "hexpire", c->argv[1], c->db->id);
|
||||
}
|
||||
}
|
||||
/* Key may become empty due to lazy expiry in hashTypeExists()
|
||||
* or the new expiration time is in the past.*/
|
||||
newlen = (int64_t) hashTypeLength(o, 0);
|
||||
|
|
|
@ -556,6 +556,82 @@ start_server {tags {"pubsub network"}} {
|
|||
$rd1 close
|
||||
}
|
||||
|
||||
test "Keyspace notifications:FXX/FNX with HSETEX cmd" {
|
||||
r config set notify-keyspace-events Khxg
|
||||
r del myhash
|
||||
set rd1 [redis_deferring_client]
|
||||
assert_equal {1} [psubscribe $rd1 *]
|
||||
r debug set-active-expire 0
|
||||
|
||||
# FXX on logically expired field
|
||||
r hset myhash f v
|
||||
r hset myhash f2 v
|
||||
assert_equal "pmessage * __keyspace@${db}__:myhash hset" [$rd1 read]
|
||||
assert_equal "pmessage * __keyspace@${db}__:myhash hset" [$rd1 read]
|
||||
r hpexpire myhash 10 FIELDS 1 f
|
||||
assert_equal "pmessage * __keyspace@${db}__:myhash hexpire" [$rd1 read]
|
||||
after 15
|
||||
assert_equal [r HSETEX myhash FXX PX 10 FIELDS 1 f v] 0
|
||||
assert_equal "pmessage * __keyspace@${db}__:myhash hexpired" [$rd1 read]
|
||||
r hdel myhash f2
|
||||
assert_equal "pmessage * __keyspace@${db}__:myhash hdel" [$rd1 read]
|
||||
assert_equal 0 [r exists myhash]
|
||||
assert_equal "pmessage * __keyspace@${db}__:myhash del" [$rd1 read]
|
||||
|
||||
# FXX with past expiry
|
||||
r HSET myhash f1 v1
|
||||
assert_equal "pmessage * __keyspace@${db}__:myhash hset" [$rd1 read]
|
||||
set past [expr {[clock seconds] - 2}]
|
||||
assert_equal [r hsetex myhash FXX EXAT $past FIELDS 1 f1 v1] 1
|
||||
assert_equal "pmessage * __keyspace@${db}__:myhash hset" [$rd1 read]
|
||||
assert_equal "pmessage * __keyspace@${db}__:myhash hdel" [$rd1 read]
|
||||
assert_equal "pmessage * __keyspace@${db}__:myhash del" [$rd1 read]
|
||||
|
||||
# FXX overwrite + full key expiry
|
||||
r hset myhash f v
|
||||
r hset myhash f2 v
|
||||
assert_equal "pmessage * __keyspace@${db}__:myhash hset" [$rd1 read]
|
||||
assert_equal "pmessage * __keyspace@${db}__:myhash hset" [$rd1 read]
|
||||
r hpexpire myhash 10 FIELDS 1 f
|
||||
assert_equal "pmessage * __keyspace@${db}__:myhash hexpire" [$rd1 read]
|
||||
after 15
|
||||
set past [expr {[clock milliseconds] - 5000}]
|
||||
assert_equal [r hsetex myhash FXX PXAT $past FIELDS 1 f v] 0
|
||||
assert_equal "pmessage * __keyspace@${db}__:myhash hexpired" [$rd1 read]
|
||||
r hpexpire myhash 10 FIELDS 1 f2
|
||||
after 15
|
||||
r hget myhash f2
|
||||
assert_equal "pmessage * __keyspace@${db}__:myhash hexpire" [$rd1 read]
|
||||
assert_equal "pmessage * __keyspace@${db}__:myhash hexpired" [$rd1 read]
|
||||
assert_equal "pmessage * __keyspace@${db}__:myhash del" [$rd1 read]
|
||||
|
||||
# FNX on logically expired field
|
||||
r del myhash
|
||||
r hset myhash f v
|
||||
assert_equal "pmessage * __keyspace@${db}__:myhash hset" [$rd1 read]
|
||||
r hpexpire myhash 10 FIELDS 1 f
|
||||
assert_equal "pmessage * __keyspace@${db}__:myhash hexpire" [$rd1 read]
|
||||
after 15
|
||||
assert_equal [r HSETEX myhash FNX PX 1000 FIELDS 1 f v] 1
|
||||
assert_equal "pmessage * __keyspace@${db}__:myhash hexpired" [$rd1 read]
|
||||
assert_equal "pmessage * __keyspace@${db}__:myhash hset" [$rd1 read]
|
||||
assert_equal "pmessage * __keyspace@${db}__:myhash hexpire" [$rd1 read]
|
||||
|
||||
# FNX with past expiry
|
||||
r del myhash
|
||||
r hset myhash f v
|
||||
assert_equal "pmessage * __keyspace@${db}__:myhash del" [$rd1 read]
|
||||
assert_equal "pmessage * __keyspace@${db}__:myhash hset" [$rd1 read]
|
||||
set past [expr {[clock seconds] - 2}]
|
||||
assert_equal [r hsetex myhash FNX EXAT $past FIELDS 1 f1 v1] 1
|
||||
# f1 is created and immediately expired
|
||||
assert_equal "pmessage * __keyspace@${db}__:myhash hset" [$rd1 read]
|
||||
assert_equal "pmessage * __keyspace@${db}__:myhash hdel" [$rd1 read]
|
||||
|
||||
r debug set-active-expire 1
|
||||
$rd1 close
|
||||
} {0} {needs:debug}
|
||||
|
||||
test "Keyspace notifications: expired events (triggered expire)" {
|
||||
r config set notify-keyspace-events Ex
|
||||
r del foo
|
||||
|
|
Loading…
Reference in New Issue