Skip to content

Add client blocking mechanism for keys in use (forkless-pre-bgiterator) - #3349

Merged
JimB123 merged 1 commit into
valkey-io:forkless-pre-bgiteratorfrom
harrylin98:blockedInUse-for-pr
Mar 11, 2026
Merged

Add client blocking mechanism for keys in use (forkless-pre-bgiterator)#3349
JimB123 merged 1 commit into
valkey-io:forkless-pre-bgiteratorfrom
harrylin98:blockedInUse-for-pr

Conversation

@harrylin98

Copy link
Copy Markdown
Contributor

This is pre-submission into the forkless-pre-bgiterator branch for client blocking mechanism. The actual PR review for this is here: #3341 Submitting to this branch to enable PR review for bgIterator.

A client blocking system (blockedInUse) that prevents concurrent access to keys
actively being modified by internal operations (e.g., bgIteration). The mechanism
blocks clients attempting to access in-use keys and automatically unblocks them
when keys become available.

Signed-off-by: harrylin98 <harrylin980107@gmail.com>
@JimB123 JimB123 changed the title Add client blocking mechanism for keys in use Add client blocking mechanism for keys in use (forkless-pre-bgiterator) Mar 10, 2026
@harrylin98
harrylin98 requested a review from JimB123 March 10, 2026 17:23
@madolson
madolson requested a review from Copilot March 11, 2026 08:42

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Introduces a new blocked_inuse subsystem to block/unblock clients attempting to access keys currently “in use” by internal operations, and wires it into the core client lifecycle (unblocking, timeouts, client unlink/free) along with unit tests.

Changes:

  • Add blocked_inuse.{c,h} implementing key-based client blocking with mappings between clients and keys.
  • Integrate the new blocking state into client timeout handling, processUnblockedClients(), unlinkClient(), and client info flags.
  • Add unit tests and build-system registration (Makefile + CMake) for the new module.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/blocked_inuse.c Implements the new blocking mechanism and data structures.
src/blocked_inuse.h Public header for the blocking API.
src/blocked.c Restores read handlers for unblocked clients and adds pause interaction/assertions.
src/timeout.c Prevents idle timeout for blockInuse-blocked clients.
src/networking.c Ensures blocked-inuse clients are unlinked safely; updates CLIENT LIST flags; adjusts pending processing for closing clients.
src/server.h Adds a new client flag bit for blockInuse state.
src/server.c Initializes/releases blockInuse; adds TCP-close detection logic in clients cron; asserts blocked state isn’t processed as a command.
src/unit/test_blockedInuse.cpp Adds gtest coverage for the new blocking behavior.
src/unit/wrappers.h Adds wrappers needed to mock networking/db functions in unit tests.
src/Makefile Adds blocked_inuse.o to server objects.
cmake/Modules/SourceFiles.cmake Adds blocked_inuse.c to CMake source list.

You can also share your feedback on Copilot code review. Take the survey.

Comment thread src/blocked_inuse.c
Comment on lines +240 to +251
for (int i = 0; i < nKeys; ++i) {
serverAssert(keys[i]->type == OBJ_STRING);
// Verify key exists in at least one database across the server
int found = 0;
for (int j = 0; j < server.dbnum; ++j) {
if (lookupKeyRead(server.db[j], keys[i]) != NULL) {
found = 1;
break;
}
}
serverAssert(found);
}

Copilot AI Mar 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The existence check calls lookupKeyRead(server.db[j], keys[i]), but lookupKeyRead() uses objectGetVal(key) as the key name. Here keys[i] appears to be the DB value object (with embedded key via objectSetKeyAndExpire), so objectGetVal(keys[i]) is the value payload, not the key name—this makes the check incorrect and can serverAssert(found) for valid keys. Either remove this validation or look up using the embedded key name (objectGetKey(keys[i])) and verify the returned value matches the expected object.

Copilot uses AI. Check for mistakes.
Comment thread src/server.c
Comment on lines +1178 to +1193
#if defined(__linux__)
// Check TCP socket state using Linux TCP_INFO
struct tcp_info info;
socklen_t infolen = sizeof(info);
if (getsockopt(c->conn->fd, IPPROTO_TCP, TCP_INFO, &info, &infolen) != 0 || infolen < sizeof(info)) return false; // Cannot retrieve TCP info
bool connection_is_closing = (info.tcpi_state == TCP_CLOSE_WAIT || info.tcpi_state == TCP_CLOSE);
#elif defined(__APPLE__)
// Check TCP socket state using macOS TCP_CONNECTION_INFO
struct tcp_connection_info info;
socklen_t infolen = sizeof(info);
if (getsockopt(c->conn->fd, IPPROTO_TCP, TCP_CONNECTION_INFO, &info, &infolen) != 0 || infolen < sizeof(info)) return false; // Cannot retrieve TCP info
bool connection_is_closing = (info.tcpi_state == TCPS_CLOSE_WAIT || info.tcpi_state == TCPS_CLOSED);
#endif

if (connection_is_closing) {
if (server.verbosity <= LL_VERBOSE) {

Copilot AI Mar 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clientsCronTcpIsClosing() only defines connection_is_closing inside the linux/APPLE branches. On other supported platforms this will not compile (connection_is_closing is undefined). Add a fallback (#else) that returns false (or implement the platform-specific check) and guard platform headers accordingly.

Copilot uses AI. Check for mistakes.
Comment thread src/Makefile
bio.o \
bitops.o \
blocked.o \
blocked_inuse.o \

Copilot AI Mar 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new object list entry uses a leading tab while the surrounding ENGINE_SERVER_OBJ entries use spaces. This can make the Makefile harder to read and can cause whitespace churn in future edits. Align indentation with the surrounding lines (spaces).

Suggested change
blocked_inuse.o \
blocked_inuse.o \

Copilot uses AI. Check for mistakes.
ConnectionType BlockedInuseTest::dummyConnType = {0};


TEST_F(BlockedInuseTest, blockInitialState) {

Copilot AI Mar 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test case is empty and doesn't assert any behavior, so it won't catch regressions and adds noise to the test suite. Either add assertions for the initial blockInuse state or remove the test.

Suggested change
TEST_F(BlockedInuseTest, blockInitialState) {
TEST_F(BlockedInuseTest, blockInitialState) {
/* Verify that the blockInuse state is clean at the beginning of the test. */
EXPECT_EQ(blockInuse_getNumberOfBlockedClients(), 0);
EXPECT_EQ(blockInuse_getNumberOfBlockedKeys(), 0);
EXPECT_EQ(listLength(server.unblocked_clients), 0UL);
/* Verify that a newly created client is not blocked or unblocked yet. */
client *c = createFakeClient(1);
verifyClientBlockState(c, 0, 0);
EXPECT_NE(c->conn->read_handler, nullptr);
freeFakeClient(c);

Copilot uses AI. Check for mistakes.
Comment thread src/blocked_inuse.c
Comment on lines +134 to +137
static hashtableType keyToClientsHashtableType = {
.entryGetKey = keyToClientsGetKey,
.hashFunction = dictEncObjHash,
.keyCompare = hashtableEncObjKeyCompare,

Copilot AI Mar 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

key_to_clients is using dictEncObjHash/hashtableEncObjKeyCompare, which hash/compare based on objectGetVal(). For the value objects stored in the DB, objectGetVal() is the value (mutable and not unique per key), so different keys with the same value will collide and updates to the value will break hashtable lookups. This will cause clients to be blocked/unblocked on the wrong keys or never unblocked. Key the table by the key name (objectGetKey()) or an sds key string (using sds hash/compare), rather than hashing the value payload.

Suggested change
static hashtableType keyToClientsHashtableType = {
.entryGetKey = keyToClientsGetKey,
.hashFunction = dictEncObjHash,
.keyCompare = hashtableEncObjKeyCompare,
/*
* Hash and compare functions for key_to_clients:
* We must key the hashtable by the key *name*, not by the mutable value
* object stored in the DB. We therefore derive the key-name object via
* objectGetKey() and delegate hashing/comparison to the generic helpers.
*/
static uint64_t keyToClientsHashByKeyName(const void *key) {
robj *o = (robj *)key;
robj *keyname = objectGetKey(o);
serverAssert(keyname != NULL);
return dictEncObjHash(keyname);
}
static int keyToClientsKeyCompareByKeyName(const void *key1, const void *key2) {
robj *o1 = (robj *)key1;
robj *o2 = (robj *)key2;
robj *k1 = objectGetKey(o1);
robj *k2 = objectGetKey(o2);
serverAssert(k1 != NULL && k2 != NULL);
return hashtableEncObjKeyCompare(k1, k2);
}
static hashtableType keyToClientsHashtableType = {
.entryGetKey = keyToClientsGetKey,
.hashFunction = keyToClientsHashByKeyName,
.keyCompare = keyToClientsKeyCompareByKeyName,

Copilot uses AI. Check for mistakes.
@JimB123
JimB123 merged commit c039e4e into valkey-io:forkless-pre-bgiterator Mar 11, 2026
57 checks passed
nitaicaro pushed a commit to nitaicaro/valkey that referenced this pull request Mar 13, 2026
…r) (valkey-io#3349)

This is pre-submission into the `forkless-pre-bgiterator` branch for
client blocking mechanism. The actual PR review for this is here:
valkey-io#3341 Submitting to this branch
to enable PR review for bgIterator.

Signed-off-by: harrylin98 <harrylin980107@gmail.com>
nitaicaro pushed a commit to nitaicaro/valkey that referenced this pull request Apr 15, 2026
…r) (valkey-io#3349)

This is pre-submission into the `forkless-pre-bgiterator` branch for
client blocking mechanism. The actual PR review for this is here:
valkey-io#3341 Submitting to this branch
to enable PR review for bgIterator.

Signed-off-by: harrylin98 <harrylin980107@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants