diff --git a/cmake/Modules/SourceFiles.cmake b/cmake/Modules/SourceFiles.cmake index 6081c2d2e45..699146024c4 100644 --- a/cmake/Modules/SourceFiles.cmake +++ b/cmake/Modules/SourceFiles.cmake @@ -64,6 +64,7 @@ set(VALKEY_SERVER_SRCS ${CMAKE_SOURCE_DIR}/src/notify.c ${CMAKE_SOURCE_DIR}/src/setproctitle.c ${CMAKE_SOURCE_DIR}/src/blocked.c + ${CMAKE_SOURCE_DIR}/src/blocked_inuse.c ${CMAKE_SOURCE_DIR}/src/hyperloglog.c ${CMAKE_SOURCE_DIR}/src/latency.c ${CMAKE_SOURCE_DIR}/src/sparkline.c diff --git a/src/Makefile b/src/Makefile index a0fe81f5b0c..7c80d33c3c1 100644 --- a/src/Makefile +++ b/src/Makefile @@ -460,6 +460,7 @@ ENGINE_SERVER_OBJ = \ bio.o \ bitops.o \ blocked.o \ + blocked_inuse.o \ call_reply.o \ childinfo.o \ cluster.o \ diff --git a/src/blocked.c b/src/blocked.c index 83437dcf144..8ef69751720 100644 --- a/src/blocked.c +++ b/src/blocked.c @@ -70,6 +70,7 @@ #include "monotonic.h" #include "cluster_slot_stats.h" #include "module.h" +#include "blocked_inuse.h" /* forward declarations */ static void unblockClientWaitingData(client *c); @@ -160,10 +161,13 @@ void processUnblockedClients(void) { client *c; while (listLength(server.unblocked_clients)) { + // If one of the unblocked clients executed pause command, then we stop processing further. + if (isPausedActionsWithUpdate(PAUSE_ACTIONS_CLIENT_ALL_SET)) return; ln = listFirst(server.unblocked_clients); serverAssert(ln != NULL); c = ln->value; listDelNode(server.unblocked_clients, ln); + serverAssert(!blockInuse_clientBlocked(c)); c->flag.unblocked = 0; if (c->flag.module) { @@ -173,6 +177,14 @@ void processUnblockedClients(void) { continue; } + /* Reinstall read handler if it was removed (e.g. by blockInuse) */ + if (c->conn && !connHasReadHandler(c->conn)) { + // If it fails because epoll_ctl failed then freeClient. + if (connSetReadHandler(c->conn, readQueryFromClient) == C_ERR) { + freeClient(c); + return; + } + } /* Process remaining data in the input buffer, unless the client * is blocked again. Actually processInputBuffer() checks that the * client is not blocked before to proceed, but things may change and @@ -183,7 +195,7 @@ void processUnblockedClients(void) { continue; } } - beforeNextClient(c); + if (!c->flag.close_asap) beforeNextClient(c); } } diff --git a/src/blocked_inuse.c b/src/blocked_inuse.c new file mode 100644 index 00000000000..91539a3da71 --- /dev/null +++ b/src/blocked_inuse.c @@ -0,0 +1,378 @@ +/* + * Copyright (c) Valkey Contributors + * All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + * + * BlockInuse - Client blocking mechanism for keys that are currently + * in use by other operations. + */ + +#include "server.h" +#include "blocked_inuse.h" + +/* External hashtable functions from server.c */ +extern uint64_t dictEncObjHash(const void *key); +extern int hashtableEncObjKeyCompare(const void *key1, const void *key2); +extern uint64_t hashtableClientHash(const void *key); +extern int hashtableClientKeyCompare(const void *key1, const void *key2); + +// Internal blockInuse data structure +static hashtable *client_to_keys; /* Maps client pointers to blockInuse_clientMetadata (list of keys). */ +static hashtable *key_to_clients; /* Maps keys to a list of clients blocked on them. */ +static uint32_t blocked_clients_on_keys; /* Current number of clients blocked on keys. */ + +/* Metadata for a blocked client */ +typedef struct blockInuse_clientMetadata { + robj **keys; /* Array of keys the client is blocked on */ + int n_keys; /* Number of keys in the array */ + long long blocked_at; /* Timestamp when client was blocked (from server.mstime) */ +} blockInuse_clientMetadata; + +/* ----------------------------- client_to_keys Hashtable util ------------------------- */ +/* Entry for client_to_keys hashtable */ +typedef struct { + client *c; + blockInuse_clientMetadata metadata; +} clientDataEntry; + +/* Hashtable callbacks for client -> blockInuse_clientMetadata */ +static const void *clientDataEntryGetKey(const void *entry) { + return ((clientDataEntry *)entry)->c; +} + +static void clientDataEntryDestructor(void *entry) { + clientDataEntry *e = entry; + if (e->metadata.keys) { + // Refcounts for all keys should be decreased before calling this function + // Ensure that n_keys is 0 before freeing keys + serverAssert(e->metadata.n_keys == 0); + + // Decrease refcount for each key (defensive, in case n_keys > 0) + for (int i = 0; i < e->metadata.n_keys; i++) { + decrRefCount(e->metadata.keys[i]); + } + zfree(e->metadata.keys); + } + zfree(entry); +} + +static hashtableType clientDataHashtableType = { + .entryGetKey = clientDataEntryGetKey, + .hashFunction = hashtableClientHash, + .keyCompare = hashtableClientKeyCompare, + .entryDestructor = clientDataEntryDestructor, +}; + +/* Utility functions for client_to_keys hashtable */ + +// Find metadata for a client; create if not found +static blockInuse_clientMetadata *addOrFindClientMetadata(client *c) { + clientDataEntry *entry; + if (hashtableFind(client_to_keys, c, (void **)&entry)) { + return &entry->metadata; + } + + entry = zcalloc(sizeof(clientDataEntry)); + entry->c = c; + hashtableAdd(client_to_keys, entry); + return &entry->metadata; +} + +// Get metadata for a client; return NULL if not found +static blockInuse_clientMetadata *getClientMetadata(client *c) { + clientDataEntry *entry; + if (hashtableFind(client_to_keys, c, (void **)&entry)) { + return &entry->metadata; + } + return NULL; +} + +// Remove a client and its metadata from the hashtable +static void removeClientMetadata(client *c) { + hashtablePop(client_to_keys, c, NULL); +} + +/* ----------------------------- key_to_clients Hashtable Util ------------------------- */ + +/* Hashtable entry: maps a key object to the list of clients blocked on it */ +typedef struct { + robj *key; /* Key object */ + list *clients; /* List of clients blocked on this key */ +} keyToClientsEntry; + +/* Hashtable callbacks */ + +static const void *keyToClientsGetKey(const void *entry) { + return ((keyToClientsEntry *)entry)->key; +} + +static void keyToClientsDestructor(void *entry) { + keyToClientsEntry *e = entry; + decrRefCount(e->key); + listRelease(e->clients); + zfree(entry); +} + +static hashtableType keyToClientsHashtableType = { + .entryGetKey = keyToClientsGetKey, + .hashFunction = dictEncObjHash, + .keyCompare = hashtableEncObjKeyCompare, + .entryDestructor = keyToClientsDestructor, +}; + +/* Utility functions for key_to_clients hashtable */ + +// Get or create the list of clients blocked on a key +static list *addOrFindBlockedClientsListsByKey(robj *key) { + keyToClientsEntry *entry; + if (hashtableFind(key_to_clients, key, (void **)&entry)) { + return entry->clients; + } + + entry = zcalloc(sizeof(keyToClientsEntry)); + entry->key = key; + incrRefCount(key); + entry->clients = listCreate(); + hashtableAdd(key_to_clients, entry); + return entry->clients; +} + +// Get the list of clients blocked on a key, or NULL if none +static list *getBlockedClientsListsByKey(robj *key) { + keyToClientsEntry *entry; + if (hashtableFind(key_to_clients, key, (void **)&entry)) { + return entry->clients; + } + return NULL; +} + +// Remove a key and its client list from the hashtable +static void removeBlockedClientsListsByKey(robj *key) { + hashtablePop(key_to_clients, key, NULL); +} + +/* ----------------------------- util ------------------------- */ + +static void markClientBlocked(client *c) { + serverAssert(c->flag.blocked == 0); + c->flag.blockInuse_blocked = 1; + c->flag.pending_command = 1; +} + +/* + * Initialize metadata for a client and insert it into client_to_keys hashtable. + * + * nKeys specifies the size for the keys array. + * Returns the pointer to the initialized metadata. + */ +static blockInuse_clientMetadata *initClientMetadata(client *c, int nKeys) { + serverAssert(!getClientMetadata(c)); // client must not already exist + serverAssert(nKeys >= 0); + + blockInuse_clientMetadata *metadata = addOrFindClientMetadata(c); + metadata->n_keys = 0; + metadata->keys = NULL; + metadata->blocked_at = server.mstime; + if (nKeys > 0) { + metadata->keys = zmalloc(sizeof(robj *) * nKeys); + } + return metadata; +} + +/* + * Unlink a blocked client from all key_to_clients entries + * and release references in its client metadata. + */ +static void unlinkBlockedClientOnKeys(client *c) { + blockInuse_clientMetadata *metadata = getClientMetadata(c); + if (!metadata) return; + + for (int i = 0; i < metadata->n_keys; ++i) { + robj *key = metadata->keys[i]; + + list *clientList = getBlockedClientsListsByKey(key); + serverAssert(clientList != NULL); + listDelNode(clientList, listSearchKey(clientList, c)); + + if (listLength(clientList) == 0) removeBlockedClientsListsByKey(key); + + decrRefCount(key); + metadata->keys[i] = NULL; + } + metadata->n_keys = 0; + blocked_clients_on_keys--; +} + + +/* + * Remove a specific key from a client's blocked keys array. + */ +static blockInuse_clientMetadata *removeBlockingKeyFromClient(client *c, robj *key) { + blockInuse_clientMetadata *metadata = getClientMetadata(c); + if (metadata == NULL) return NULL; + + sds key_sds = objectGetKey(key); + for (int i = 0; i < metadata->n_keys; ++i) { + sds curr_key = objectGetKey(metadata->keys[i]); + if (sdscmp(curr_key, key_sds) == 0) { + decrRefCount(metadata->keys[i]); + metadata->keys[i] = metadata->keys[metadata->n_keys - 1]; + metadata->keys[metadata->n_keys - 1] = NULL; + metadata->n_keys--; + return metadata; + } + } + serverAssert(false); // key must exist +} + +/* ----------------------------- API implementation ------------------------- */ + +/* Check if client is blocked by blockInuse */ +int blockInuse_clientBlocked(client *c) { + return c->flag.blockInuse_blocked; +} + +/* + * Initialize blockInuse data structures. + */ +void blockInuse_init(void) { + client_to_keys = hashtableCreate(&clientDataHashtableType); + key_to_clients = hashtableCreate(&keyToClientsHashtableType); + blocked_clients_on_keys = 0; +} + +/* + * Release blockInuse data structures. + * Only allowed if no clients are currently blocked. + */ +void blockInuse_release(void) { + serverAssert(blocked_clients_on_keys == 0); + + if (client_to_keys) { + hashtableRelease(client_to_keys); + client_to_keys = NULL; + } + if (key_to_clients) { + hashtableRelease(key_to_clients); + key_to_clients = NULL; + } + blocked_clients_on_keys = 0; +} + +/* Get the current number of clients blocked by blockInuse. */ +int blockInuse_getNumberOfBlockedClients(void) { + return blocked_clients_on_keys; +} + +/* Block a client on a set of keys. */ +void blockInuse_blockClientOnKeys(client *c, int nKeys, robj *keys[]) { + serverAssert(!(blockInuse_clientBlocked(c) || (c)->flag.unblocked)); + serverAssert(nKeys > 0); + serverAssert(!c->flag.replica); + for (int i = 0; i < nKeys; ++i) { + serverAssert(keys[i]->type == OBJ_STRING); + } + + // Initialize client metadata and insert into client_to_keys table + blockInuse_clientMetadata *metadata = initClientMetadata(c, nKeys); + markClientBlocked(c); + + for (int i = 0; i < nKeys; ++i) { + robj *key = keys[i]; + + // Get or create the list of clients blocked on this key + list *blockedClientsList = addOrFindBlockedClientsListsByKey(key); + + // Deduplicate: add client only if it’s not already the last in the list + listNode *last_client = listLast(blockedClientsList); + if (last_client == NULL || last_client->value != c) { + // Add client to the key’s blocked clients list + listAddNodeTail(blockedClientsList, c); + + // Add key to the client’s metadata and increment reference count + incrRefCount(key); + metadata->keys[metadata->n_keys] = key; + metadata->n_keys++; + } + } + + // Disable client’s Read Handler to prevent reading commands while blocked + if (c->conn) { + connSetReadHandler(c->conn, NULL); + } + blocked_clients_on_keys++; +} + +/* + * Unblock all clients blocked on the given key. + * + * - Each client is unblocked only when it has no remaining dependencies on other keys. + * - Clients that become fully unblocked are added to server.unblocked_clients + * and will be resumed later in processUnblockedClients(). + */ +void blockInuse_unblockClientsOnKey(robj *key) { + list *blockedClientsList = getBlockedClientsListsByKey(key); + if (blockedClientsList == NULL) return; + + serverAssert(listLength(blockedClientsList) > 0); + + while (listLength(blockedClientsList) > 0) { + listNode *ln = listFirst(blockedClientsList); + client *c = listNodeValue(ln); + + // Remove client from this key's blocked list + listDelNode(blockedClientsList, ln); + + // Remove this key from the client's blocked key list + blockInuse_clientMetadata *metadata = removeBlockingKeyFromClient(c, key); + + if (metadata->n_keys == 0) { + // Client has no more blocked keys → mark unblocked + serverAssert(c->flag.unblocked == 0); + c->flag.unblocked = 1; + c->flag.blockInuse_blocked = 0; + listAddNodeTail(server.unblocked_clients, c); + + // Remove client metadata from client_to_keys table + removeClientMetadata(c); + blocked_clients_on_keys--; + } + } + + // Remove the key entry from key_to_clients table + removeBlockedClientsListsByKey(key); +} + +/* + * Unblock all clients on all keys. + */ +void blockInuse_unblockClientsOnAllKeys(void) { + hashtableIterator iter; + hashtableInitIterator(&iter, key_to_clients, HASHTABLE_ITER_SAFE); + void *entry; + while (hashtableNext(&iter, &entry)) { + keyToClientsEntry *e = entry; + robj *key = e->key; + incrRefCount(key); + blockInuse_unblockClientsOnKey(key); + decrRefCount(key); + } + hashtableCleanupIterator(&iter); +} + +/* + * Unlink a blocked client from all blockInuse structures, the client must be blocked by blockInuse. + */ +void blockInuse_unlinkClient(client *c) { + serverAssert(blockInuse_clientBlocked(c) && c->flag.unblocked == 0); + + blockInuse_clientMetadata *metadata = getClientMetadata(c); + if (metadata == NULL) return; // Client has no blocking metadata + + // Remove client from all key-to-client lists + unlinkBlockedClientOnKeys(c); + + // Clear the blocked flag and remove client metadata + c->flag.blockInuse_blocked = 0; + removeClientMetadata(c); +} diff --git a/src/blocked_inuse.h b/src/blocked_inuse.h new file mode 100644 index 00000000000..57c4ddb1505 --- /dev/null +++ b/src/blocked_inuse.h @@ -0,0 +1,86 @@ +/* + * Copyright (c) Valkey Contributors + * All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + * + * + * Client blocking mechanism for keys currently in use by other operations. + * + * This module provides a specialized blocking system that prevents concurrent + * access to keys that are actively being modified or processed. Unlike the + * generic blocking operations in blocked.c, this mechanism blocks clients when + * they attempt to access keys that are marked as "in use" by internal + * operations such as bgIteration. + * + * Key features: + * - Blocks clients on multiple keys simultaneously + * - Automatically unblocks clients when all their requested keys become + * available + * - Maintains bidirectional mappings: client->keys and key->clients + * - Integrates with the server's event loop via processUnblockedClients() + * in blocked.c + * + * Workflow: + * 1. blockInuse_blockClientOnKeys() - Block a client on a set of keys + * 2. Keys remain blocked until explicitly unblocked + * 3. blockInuse_unblockClientsOnKey() - Unblock specific key, triggering + * clients resumption + * 4. processUnblockedClients() - Process unblocked clients in beforeSleep() + * + * This is used to ensure data consistency during operations that require + * exclusive access to keys, preventing race conditions and maintaining + * transactional integrity. + * + */ + +#ifndef BLOCKED_INUSE_H__ +#define BLOCKED_INUSE_H__ + +struct robj; // defined in server.h +struct client; // defined in server.h + +/* Check if client is blocked by blockInuse */ +int blockInuse_clientBlocked(client *c); + +/* Initialize blockInuse structures, must be called once during server startup. */ +void blockInuse_init(void); + +/* Free blockInuse data structures, no clients must be blocked by blockInuse. */ +void blockInuse_release(void); + +/* Return the number of clients currently blocked by blockInuse. */ +int blockInuse_getNumberOfBlockedClients(void); + +/* + * Block a client on a set of keys. Duplicate keys are allowed and handled. + * + * To avoid extra copying, this API keeps references to the passed key objects. + */ +void blockInuse_blockClientOnKeys(client *c, int nKeys, robj *keys[]); + +/* + * Unblock clients blocked on the given key. + * + * A client is unblocked only when it has no remaining dependencies on any + * blocked keys. Such clients are added to the server.unblocked_clients list and + * resumed later during processUnblockedClients() in blocked.c. + */ +void blockInuse_unblockClientsOnKey(robj *key); + +/* + * Unblock all clients blocked by blockInuse on all keys. + * + * Clients that become unblocked are added to the server.unblocked_clients + * list and resumed later during processUnblockedClients(). + */ +void blockInuse_unblockClientsOnAllKeys(void); + +/* + * Unlink a client currently blocked by blockInuse. Typically used when + * a client is being freed while still blocked (e.g., client-initiated disconnect). + * + * This function removes the client from all blockInuse data structures. + */ +void blockInuse_unlinkClient(client *c); + +#endif diff --git a/src/networking.c b/src/networking.c index ee523bb61ec..9d54ffc9dcb 100644 --- a/src/networking.c +++ b/src/networking.c @@ -40,6 +40,7 @@ #include "module.h" #include "connection.h" #include "zmalloc.h" +#include "blocked_inuse.h" #include #include #include @@ -1902,7 +1903,9 @@ void unlinkClient(client *c) { waitForClientIO(c); /* If this is marked as current client unset it. */ - if (c->conn && server.current_client == c) server.current_client = NULL; + if (server.current_client == c) server.current_client = NULL; + + if (blockInuse_clientBlocked(c)) blockInuse_unlinkClient(c); /* Certain operations must be done only if the client has an active connection. * If the client was already unlinked or if it's a "fake client" the @@ -1987,6 +1990,9 @@ void unlinkClient(client *c) { /* Clear the tracking status. */ if (c->flag.tracking) disableTracking(c); + + // Client should never in unblocked or blockInuse blocked state. + serverAssert(!(blockInuse_clientBlocked(c) || (c)->flag.unblocked)); } /* Clear the client state to resemble a newly connected client. */ @@ -3803,6 +3809,7 @@ int processPendingCommandAndInputBuffer(client *c) { * But in case of a module blocked client (see RM_Call 'K' flag) we do not reach this code path. * So whenever we change the code here we need to consider if we need this change on module * blocked client as well */ + if (c->flag.close_asap) return C_ERR; if (c->flag.pending_command) { c->flag.pending_command = 0; if (processCommandAndResetClient(c) == C_ERR) { @@ -4261,7 +4268,7 @@ int isClientConnIpV6(client *c) { * readable format, into the sds string 's'. */ sds catClientInfoString(sds s, client *client, int hide_user_data) { if (!server.crashed) waitForClientIO(client); - char flags[17], events[3], capa[9], conninfo[CONN_INFO_LEN], *p; + char flags[18], events[3], capa[9], conninfo[CONN_INFO_LEN], *p; p = flags; if (client->flag.replica) { @@ -4275,6 +4282,7 @@ sds catClientInfoString(sds s, client *client, int hide_user_data) { if (client->flag.pubsub) *p++ = 'P'; if (client->flag.multi) *p++ = 'x'; if (client->flag.blocked) *p++ = 'b'; + if (blockInuse_clientBlocked(client)) *p++ = 'X'; if (client->flag.tracking) *p++ = 't'; if (client->flag.tracking_broken_redir) *p++ = 'R'; if (client->flag.tracking_bcast) *p++ = 'B'; diff --git a/src/server.c b/src/server.c index abe77b1b3b3..2e55e61a74d 100644 --- a/src/server.c +++ b/src/server.c @@ -48,6 +48,7 @@ #include "fmtargs.h" #include "io_threads.h" #include "tls.h" +#include "blocked_inuse.h" #include "sds.h" #include "module.h" #include "scripting_engine.h" @@ -75,6 +76,10 @@ #include #include #include +#include +#ifdef __APPLE__ +#include +#endif #ifdef __linux__ #include @@ -1154,6 +1159,50 @@ void getExpensiveClientsInfo(size_t *in_usage, size_t *out_usage) { *out_usage = o; } +/* + * Check if a TCP client connection has been closed from the remote side. + * + * Returns: + * true if the client has been terminated, + * false if still alive. + */ +static bool clientsCronTcpIsClosing(client *c) { + if (!c->conn) return false; // No connection, cannot check + + // Only TCP or TLS clients are relevant + if (c->conn->type != connectionTypeTcp() && c->conn->type != connectionTypeTls()) return false; + + // Skip if event handlers are installed + if (aeGetFileEvents(server.el, c->conn->fd) != AE_NONE) return false; + +#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) { + sds client_info = catClientInfoString(sdsempty(), c, server.hide_user_data_from_log); + serverLog(LL_VERBOSE, "Client closed connection while blocked %s", client_info); + sdsfree(client_info); + } + + freeClientAsync(c); + return true; // Client has been closed + } + + return false; // Client is still alive +} + /* This function is called by clientsTimeProc() and is used in order to perform * operations on clients that are important to perform constantly. For instance * we use this function in order to disconnect clients after a timeout, including @@ -1208,6 +1257,7 @@ static void clientsCron(int clients_this_cycle) { if (clientsCronResizeQueryBuffer(c)) continue; if (clientsCronResizeOutputBuffer(c, now)) continue; if (clientsCronTrackExpensiveClients(c, curr_peak_mem_usage_slot)) continue; + if (clientsCronTcpIsClosing(c)) continue; /* Iterating all the clients in getMemoryOverheadData() is too slow and * in turn would make the INFO command too slow. So we perform this @@ -3079,6 +3129,7 @@ void initServer(void) { commandlogInit(); latencyMonitorInit(); + blockInuse_init(); initSharedQueryBuf(); /* Initialize ACL default password if it exists */ @@ -4216,6 +4267,8 @@ void unprepareCommand(client *c) { * other operations can be performed by the caller. Otherwise * if C_ERR is returned the client was destroyed (i.e. after QUIT). */ int processCommand(client *c) { + serverAssert(!(blockInuse_clientBlocked(c) || c->flag.unblocked == 1 || c->flag.blocked == 1)); + if (!scriptIsTimedout()) { /* Both EXEC and scripts call call() directly so there should be * no way in_exec or scriptIsRunning() is 1. @@ -4864,6 +4917,8 @@ int finishShutdown(void) { /* Close the listening sockets. Apparently this allows faster restarts. */ closeListeningSockets(1); + blockInuse_release(); + moduleUnloadAllModules(); serverLog(LL_WARNING, "%s is now ready to exit, bye bye...", server.sentinel_mode ? "Sentinel" : "Valkey"); diff --git a/src/server.h b/src/server.h index 9cc46a20795..1b827e2cae9 100644 --- a/src/server.h +++ b/src/server.h @@ -1151,6 +1151,7 @@ typedef struct ClientFlags { uint64_t dirty_cas : 1; /* Watched keys modified. EXEC will fail. */ uint64_t close_after_reply : 1; /* Close after writing entire reply. */ uint64_t unblocked : 1; /* This client was unblocked and is stored in server.unblocked_clients */ + uint64_t blockInuse_blocked : 1; /* This client is blocked by blockInuse */ uint64_t script : 1; /* This is a non connected client used by Lua */ uint64_t asking : 1; /* Client issued the ASKING command */ uint64_t close_asap : 1; /* Close this client ASAP */ diff --git a/src/timeout.c b/src/timeout.c index 8ff0608ae0a..b6522991978 100644 --- a/src/timeout.c +++ b/src/timeout.c @@ -28,6 +28,7 @@ #include "server.h" #include "cluster.h" +#include "blocked_inuse.h" #include @@ -55,10 +56,11 @@ int clientsCronHandleTimeout(client *c, mstime_t now_ms) { if (server.maxidletime && /* This handles the idle clients connection timeout if set. */ - !c->flag.replica && /* No timeout for replicas and monitors */ - !mustObeyClient(c) && /* No timeout for primaries and AOF */ - !c->flag.blocked && /* No timeout for BLPOP */ - !c->flag.pubsub && /* No timeout for Pub/Sub clients */ + !c->flag.replica && /* No timeout for replicas and monitors */ + !mustObeyClient(c) && /* No timeout for primaries and AOF */ + !c->flag.blocked && /* No timeout for BLPOP */ + !c->flag.pubsub && /* No timeout for Pub/Sub clients */ + !blockInuse_clientBlocked(c) && /* No timeout for BlockInuse client */ (now - c->last_interaction > server.maxidletime)) { serverLog(LL_VERBOSE, "Closing idle client"); freeClient(c);