Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 0 additions & 31 deletions .github/actions/rpm-distros-tcl8/action.yml

This file was deleted.

24 changes: 11 additions & 13 deletions .github/workflows/daily.yml
Original file line number Diff line number Diff line change
Expand Up @@ -947,12 +947,10 @@ jobs:
run: dnf -y install epel-release
- name: make
run: |
dnf -y install gcc make procps-ng openssl-devel openssl which /usr/bin/kill /usr/bin/awk
dnf -y install gcc make procps-ng which /usr/bin/kill /usr/bin/awk
make -j SERVER_CFLAGS='-Werror'
- name: testprep
uses: ./.github/actions/rpm-distros-tcl8
with:
matrix_name: ${{ matrix.name }}
run: dnf -y install tcl tcltls
- name: test
if: true && !contains(github.event.inputs.skiptests, 'valkey')
run: ./runtest ${{ github.event_name != 'pull_request' && '--accurate' || '' }} --verbose --dump-logs ${{github.event.inputs.test_args}}
Expand Down Expand Up @@ -1018,9 +1016,9 @@ jobs:
dnf -y install make gcc openssl-devel openssl procps-ng which /usr/bin/kill /usr/bin/awk
make -j BUILD_TLS=module SERVER_CFLAGS='-Werror'
- name: testprep
uses: ./.github/actions/rpm-distros-tcl8
with:
matrix_name: ${{ matrix.name }}
run: |
dnf -y install tcl tcltls
./utils/gen-test-certs.sh
- name: test
if: true && !contains(github.event.inputs.skiptests, 'valkey')
run: |
Expand Down Expand Up @@ -1090,9 +1088,9 @@ jobs:
dnf -y install make gcc openssl-devel openssl procps-ng which /usr/bin/kill /usr/bin/awk
make -j BUILD_TLS=module SERVER_CFLAGS='-Werror'
- name: testprep
uses: ./.github/actions/rpm-distros-tcl8
with:
matrix_name: ${{ matrix.name }}
run: |
dnf -y install tcl tcltls
./utils/gen-test-certs.sh
- name: test
if: true && !contains(github.event.inputs.skiptests, 'valkey')
run: |
Expand Down Expand Up @@ -1201,7 +1199,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [macos-13, macos-14]
os: [macos-14]
runs-on: ${{ matrix.os }}
if: |
(github.event_name == 'workflow_dispatch' ||
Expand Down Expand Up @@ -1230,7 +1228,7 @@ jobs:
run: make SERVER_CFLAGS='-Werror'

test-freebsd:
runs-on: macos-13
runs-on: ubuntu-latest
if: |
(github.event_name == 'workflow_dispatch' ||
(github.event_name == 'schedule' && github.repository == 'valkey-io/valkey') ||
Expand All @@ -1248,7 +1246,7 @@ jobs:
repository: ${{ env.GITHUB_REPOSITORY }}
ref: ${{ env.GITHUB_HEAD_REF }}
- name: test
uses: cross-platform-actions/action@5800fa0060a22edf69992a779adac3d2bb3a6f8a # v0.22.0
uses: cross-platform-actions/action@46e8d7fb25520a8d6c64fd2b7a1192611da98eda # v0.30.0
with:
operating_system: freebsd
environment_variables: MAKE
Expand Down
30 changes: 30 additions & 0 deletions 00-RELEASENOTES
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,36 @@ Upgrade urgency levels:
| CRITICAL | There is a critical bug affecting MOST USERS. Upgrade ASAP. |
| SECURITY | There are security fixes in the release. |

Valkey 9.0.2 - February 3, 2026
-------------------------------

Upgrade urgency HIGH: There are critical bugs that may affect a subset of users.

### Bug fixes

* Avoid memory leak of new argv when HEXPIRE commands target only non-exiting fields (#2973)
* Fix HINCRBY and HINCRBYFLOAT to update volatile key tracking (#2974)
* Avoid empty hash object when HSETEX added no fields (#2998)
* Fix case-sensitive check for the FNX and FXX arguments in HSETEX (#3000)
* Prevent assertion in active expiration job after a hash with volatile fields is overwritten (#3003, #3007)
* Fix HRANDFIELD to return null response when no field could be found (#3022)
* Fix HEXPIRE to not delete items when validation rules fail and expiration is in the past (#3023, #3048)
* Fix how hash is handling overriding of expired fields overwrite (#3060)
* HSETEX - Always issue keyspace notifications after validation (#3001)
* Make zero a valid TTL for hash fields during import mode and data loading (#3006)
* Trigger prepareCommand on argc change in module command filters (#2945)
* Restrict TTL from being negative and avoid crash in import-mode (#2944)
* Fix chained replica crash when doing dual channel replication (#2983)
* Skip slot cache optimization for AOF client to prevent key duplication and data corruption (#3004)
* Fix used_memory_dataset underflow due to miscalculated used_memory_overhead (#3005)
* Avoid duplicate calculations of network-bytes-out in slot stats with copy-avoidance (#3046)
* Fix XREAD returning error on empty stream with + ID (#2742)

### Performance/Efficiency Improvements

* Track reply bytes in I/O threads if commandlog-reply-larger-than is -1 (#3086, #3126).
This makes it possible to mitigate a performance regression in 9.0.1 caused by the bug fix #2652.

Valkey 9.0.1 - December 9, 2025
-------------------------------

Expand Down
2 changes: 1 addition & 1 deletion runtest
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/bin/sh
TCL_VERSIONS="8.5 8.6 8.7"
TCL_VERSIONS="8.5 8.6 9.0"
TCLSH=""

for VERSION in $TCL_VERSIONS; do
Expand Down
2 changes: 1 addition & 1 deletion runtest-cluster
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/bin/sh
TCL_VERSIONS="8.5 8.6 8.7"
TCL_VERSIONS="8.5 8.6 9.0"
TCLSH=""

for VERSION in $TCL_VERSIONS; do
Expand Down
2 changes: 1 addition & 1 deletion runtest-moduleapi
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/bin/sh
TCL_VERSIONS="8.5 8.6 8.7"
TCL_VERSIONS="8.5 8.6 9.0"
TCLSH=""
[ -z "$MAKE" ] && MAKE=make

Expand Down
2 changes: 1 addition & 1 deletion runtest-sentinel
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/bin/sh
TCL_VERSIONS="8.5 8.6 8.7"
TCL_VERSIONS="8.5 8.6 9.0"
TCLSH=""

for VERSION in $TCL_VERSIONS; do
Expand Down
2 changes: 0 additions & 2 deletions src/cluster_slot_stats.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ void clusterSlotStatsInvalidateSlotIfApplicable(scriptRunCtx *ctx);

/* network-bytes-in metric. */
void clusterSlotStatsAddNetworkBytesInForUserClient(client *c);
void clusterSlotStatsSetClusterMsgLength(uint32_t len);
void clusterSlotStatsResetClusterMsgLength(void);

/* network-bytes-out metric. */
void clusterSlotStatsAddNetworkBytesOutForSlot(int slot, unsigned long long net_bytes_out);
Expand Down
4 changes: 2 additions & 2 deletions src/db.c
Original file line number Diff line number Diff line change
Expand Up @@ -258,10 +258,10 @@ int getKeySlot(sds key) {
* the key slot would fallback to keyHashSlot.
*
* Modules and scripts executed on the primary may get replicated as multi-execs that operate on multiple slots,
* so we must always recompute the slot for commands coming from the primary.
* so we must always recompute the slot for commands coming from the primary or AOF.
*/
if (server.current_client && server.current_client->slot >= 0 && server.current_client->flag.executing_command &&
!isReplicatedClient(server.current_client)) {
!mustObeyClient(server.current_client)) {
debugServerAssertWithInfo(server.current_client, NULL,
(int)keyHashSlot(key, (int)sdslen(key)) == server.current_client->slot);
return server.current_client->slot;
Expand Down
7 changes: 7 additions & 0 deletions src/expire.c
Original file line number Diff line number Diff line change
Expand Up @@ -781,6 +781,13 @@ void expireGenericCommand(client *c, long long basetime, int unit) {
return;
}
when += basetime;
/* A negative expiration time should cause a key to expire and be deleted immediately.
* However, in some cases (such as import-mode), we might need to pause expiration,
* and we don't want keys with negative expiration times (could cause a crash during active expiration).
* Therefore, we simply change the expiration time to 0 to mark the key as expired. */
if (when < 0) {
when = 0;
}

robj *obj = lookupKeyWrite(c->db, key);

Expand Down
16 changes: 12 additions & 4 deletions src/module.c
Original file line number Diff line number Diff line change
Expand Up @@ -11022,8 +11022,10 @@ void moduleCallCommandFilters(client *c) {

ValkeyModuleCommandFilterCtx filter = {.argv = c->argv, .argv_len = c->argv_len, .argc = c->argc, .c = c};

robj *tmp = c->argv[0];
incrRefCount(tmp);
robj *pre_filter_command = c->argv[0];
incrRefCount(pre_filter_command);
const int pre_filter_argc = c->argc;

while ((ln = listNext(&li))) {
ValkeyModuleCommandFilter *f = ln->value;

Expand All @@ -11036,15 +11038,21 @@ void moduleCallCommandFilters(client *c) {
f->callback(&filter);
}

/* Apply filter output */
c->argv = filter.argv;
c->argv_len = filter.argv_len;
c->argc = filter.argc;
if (tmp != c->argv[0]) {

/* If filter changed the command or number of arguments, redo prepareCommand */
const bool command_changed = (c->argv[0] != pre_filter_command);
const bool argc_changed = (c->argc != pre_filter_argc);

if (command_changed || argc_changed) {
/* Reset and lookup the command and cluster slot again. */
unprepareCommand(c);
prepareCommand(c);
}
decrRefCount(tmp);
decrRefCount(pre_filter_command);
}

/* Return the number of arguments a filtered command has. The number of
Expand Down
65 changes: 46 additions & 19 deletions src/networking.c
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,12 @@ typedef enum {
* The packed attribute is specified because buffer is accessed at arbitrary offsets,
* so no benefit in data structure padding and applying packed saves the space in the buffer */
typedef struct __attribute__((__packed__)) payloadHeader {
size_t payload_len; /* payload length in a reply buffer */
size_t reply_len; /* actual reply length for non-plain payloads */
uint8_t payload_type; /* one of payloadType */
int16_t slot; /* to report network-bytes-out for BULK_STR_REF chunks */
size_t payload_len; /* payload length in a reply buffer */
size_t reply_len; /* actual reply length for non-plain payloads */
int16_t slot; /* to report network-bytes-out for BULK_STR_REF chunks */
uint8_t payload_type : 1; /* one of payloadType */
uint8_t track_bytes : 1; /* 1 if net bytes tracking was enabled when reply was added */
uint8_t reserved : 6;
} payloadHeader;

/* To avoid copy of whole string in reply buffer
Expand Down Expand Up @@ -506,7 +508,14 @@ void deleteCachedResponseClient(client *recording_client) {

/* Updates an existing header, if possible; otherwise inserts a new one
* Returns the length of data that can be added to the reply buffer (i.e. min(available, requested)) */
static size_t upsertPayloadHeader(char *buf, size_t *bufpos, payloadHeader **last_header, uint8_t type, size_t len, int slot, size_t available) {
static size_t upsertPayloadHeader(char *buf,
size_t *bufpos,
payloadHeader **last_header,
uint8_t type,
size_t len,
int slot,
int track_bytes,
size_t available) {
/* Enforce min len for BULK_STR_REF chunks as whole pointers must be written to the buffer */
size_t min_len = (type == BULK_STR_REF ? len : 1);
if (min_len > available) return 0;
Expand All @@ -516,7 +525,8 @@ static size_t upsertPayloadHeader(char *buf, size_t *bufpos, payloadHeader **las
if (!clusterSlotStatsEnabled(slot)) slot = -1;

/* Try to add payload to last chunk if possible */
if (*last_header != NULL && (*last_header)->payload_type == type && (*last_header)->slot == slot) {
if (*last_header != NULL && (*last_header)->payload_type == type && (*last_header)->slot == slot &&
(*last_header)->track_bytes == track_bytes) {
(*last_header)->payload_len += allowed_len;
return allowed_len;
}
Expand All @@ -533,6 +543,8 @@ static size_t upsertPayloadHeader(char *buf, size_t *bufpos, payloadHeader **las
(*last_header)->payload_len = allowed_len;
(*last_header)->slot = slot;
(*last_header)->reply_len = 0;
(*last_header)->track_bytes = track_bytes;
(*last_header)->reserved = 0;

*bufpos += sizeof(payloadHeader);

Expand All @@ -556,7 +568,8 @@ static size_t _addReplyPayloadToBuffer(client *c, const void *payload, size_t le
size_t available = c->buf_usable_size - c->bufpos;
size_t reply_len = min(available, len);
if (c->flag.buf_encoded) {
reply_len = upsertPayloadHeader(c->buf, &c->bufpos, &c->last_header, payload_type, len, c->slot, available);
int track_bytes = (server.commandlog[COMMANDLOG_TYPE_LARGE_REPLY].threshold != -1);
reply_len = upsertPayloadHeader(c->buf, &c->bufpos, &c->last_header, payload_type, len, c->slot, track_bytes, available);
}
if (!reply_len) return 0;

Expand Down Expand Up @@ -606,7 +619,8 @@ static void _addReplyPayloadToList(client *c, list *reply_list, const char *payl
size_t copy = avail >= len ? len : avail;

if (tail->flag.buf_encoded) {
copy = upsertPayloadHeader(tail->buf, &tail->used, &tail->last_header, payload_type, len, c->slot, avail);
int track_bytes = (server.commandlog[COMMANDLOG_TYPE_LARGE_REPLY].threshold != -1);
copy = upsertPayloadHeader(tail->buf, &tail->used, &tail->last_header, payload_type, len, c->slot, track_bytes, avail);
} else if (encoded) {
/* If encoded buffer is required but tail is unencoded then pretend nothing can be added to it
* and, as consequence, cause addition of a new tail */
Expand Down Expand Up @@ -634,7 +648,8 @@ static void _addReplyPayloadToList(client *c, list *reply_list, const char *payl
tail->flag.buf_encoded = encoded;
tail->last_header = NULL;
if (tail->flag.buf_encoded) {
upsertPayloadHeader(tail->buf, &tail->used, &tail->last_header, payload_type, len, c->slot, tail->size);
int track_bytes = (server.commandlog[COMMANDLOG_TYPE_LARGE_REPLY].threshold != -1);
upsertPayloadHeader(tail->buf, &tail->used, &tail->last_header, payload_type, len, c->slot, track_bytes, tail->size);
}
memcpy(tail->buf + tail->used, payload, len);
tail->used += len;
Expand Down Expand Up @@ -1392,14 +1407,17 @@ static int tryAvoidBulkStrCopyToReply(client *c, robj *obj) {
/* Add an Object as a bulk reply */
void addReplyBulk(client *c, robj *obj) {
if (tryAvoidBulkStrCopyToReply(c, obj) == C_OK) {
/* If copy avoidance allowed, then we explicitly maintain net_output_bytes_curr_cmd. */
serverAssert(obj->encoding == OBJ_ENCODING_RAW);
size_t str_len = sdslen(obj->ptr);
uint32_t num_len = digits10(str_len);
/* RESP encodes bulk strings as $<length>\r\n<data>\r\n */
c->net_output_bytes_curr_cmd += (num_len + 3); /* $<length>\r\n */
c->net_output_bytes_curr_cmd += str_len; /* <data> */
c->net_output_bytes_curr_cmd += 2; /* \r\n */
/* If copy avoidance allowed, then we explicitly maintain net_output_bytes_curr_cmd.
* We determine per-reply if tracking is enabled by checking the config in the main thread. */
if (server.commandlog[COMMANDLOG_TYPE_LARGE_REPLY].threshold != -1) {
serverAssert(obj->encoding == OBJ_ENCODING_RAW);
size_t str_len = sdslen(obj->ptr);
uint32_t num_len = digits10(str_len);
/* RESP encodes bulk strings as $<length>\r\n<data>\r\n */
c->net_output_bytes_curr_cmd += (num_len + 3); /* $<length>\r\n */
c->net_output_bytes_curr_cmd += str_len; /* <data> */
c->net_output_bytes_curr_cmd += 2; /* \r\n */
}
return;
}
addReplyBulkLen(c, obj);
Expand Down Expand Up @@ -1856,7 +1874,10 @@ void disconnectReplicas(void) {
listNode *ln;
listRewind(server.replicas, &li);
while ((ln = listNext(&li))) {
freeClient((client *)ln->value);
client *replica = (client *)ln->value;
/* If we are going to disconnect all replicas, there is no need to protect the rdb channel. */
replica->flag.protected_rdb_channel = 0;
freeClient(replica);
}
}

Expand Down Expand Up @@ -2755,7 +2776,13 @@ static void releaseBufReferences(char *buf, size_t bufpos) {
ptr += sizeof(payloadHeader);

if (header->payload_type == BULK_STR_REF) {
clusterSlotStatsAddNetworkBytesOutForSlot(header->slot, header->reply_len);
/* When net byte tracking was disabled in the main thread (commandlog-reply-larger-than -1)
* at the time this reply was added, we account for cluster slot stats here in the IO thread
* after writing the reply. When tracking was enabled, it's already accounted in the main thread
* via afterCommand() -> clusterSlotStatsAddNetworkBytesOutForUserClient(). */
if (!header->track_bytes) {
clusterSlotStatsAddNetworkBytesOutForSlot(header->slot, header->reply_len);
}

bulkStrRef *str_ref = (bulkStrRef *)ptr;
size_t len = header->payload_len;
Expand Down
2 changes: 1 addition & 1 deletion src/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -1380,7 +1380,7 @@ struct serverMemOverhead *getMemoryOverheadData(void) {

for (j = 0; j < server.dbnum; j++) {
serverDb *db = server.db[j];
if (db == NULL || !kvstoreNumAllocatedHashtables(db->keys)) continue;
if (db == NULL) continue;

unsigned long long keyscount = kvstoreSize(db->keys);

Expand Down
8 changes: 7 additions & 1 deletion src/server.c
Original file line number Diff line number Diff line change
Expand Up @@ -3135,7 +3135,13 @@ void InitServerLast(void) {
bioInit();
initIOThreads();
set_jemalloc_bg_thread(server.jemalloc_bg_thread);
server.initial_memory_usage = zmalloc_used_memory();

/* First set initial_memory_usage to zero as baseline for getMemoryOverheadData(). */
server.initial_memory_usage = 0;
struct serverMemOverhead *mh = getMemoryOverheadData();
/* Exclude current overhead memory to avoid double counting in the future. */
server.initial_memory_usage = zmalloc_used_memory() - mh->overhead_total;
freeMemoryOverheadData(mh);
}

/* The purpose of this function is to try to "glue" consecutive range
Expand Down
Loading
Loading