@@ -198,8 +198,8 @@ typedef struct ValkeyModuleCtx ValkeyModuleCtx;
198198#define VALKEYMODULE_CTX_NEW_CLIENT (1 << 7) /* Free client object when the \
199199 context is destroyed */
200200#define VALKEYMODULE_CTX_CHANNELS_POS_REQUEST (1 << 8)
201- #define VALKEYMODULE_CTX_COMMAND (1 << 9) /* Context created to serve a command from call() or AOF (which calls cmd->proc directly) */
202-
201+ #define VALKEYMODULE_CTX_COMMAND (1 << 9) /* Context created to serve a command from call() or AOF (which calls cmd->proc directly) */
202+ #define VALKEYMODULE_CTX_KEYSPACE_NOTIFICATION (1 << 10) /* Context created a keyspace notification event */
203203
204204/* This represents a key opened with VM_OpenKey(). */
205205struct ValkeyModuleKey {
@@ -7795,6 +7795,8 @@ void unblockClientFromModule(client *c) {
77957795 * in that case the privdata argument is disregarded, because we pass the
77967796 * reply callback the privdata that is set here while blocking.
77977797 *
7798+ * For details on return values and error codes, see the comment block for
7799+ * VM_BlockClient.
77987800 */
77997801ValkeyModuleBlockedClient *moduleBlockClient(ValkeyModuleCtx *ctx,
78007802 ValkeyModuleCmdFunc reply_callback,
@@ -7807,8 +7809,27 @@ ValkeyModuleBlockedClient *moduleBlockClient(ValkeyModuleCtx *ctx,
78077809 void *privdata,
78087810 int flags) {
78097811 client *c = ctx->client;
7812+ if (c->flag.blocked || getClientType(c) != CLIENT_TYPE_NORMAL || c->flag.deny_blocking) {
7813+ /* Early return if duplicate block attempt or client is not normal or
7814+ * client is set to deny blocking. */
7815+ errno = ENOTSUP;
7816+ return NULL;
7817+ }
7818+
7819+ if (ctx->flags & (VALKEYMODULE_CTX_TEMP_CLIENT | VALKEYMODULE_CTX_NEW_CLIENT)) {
7820+ /* Temporary clients can't be blocked */
7821+ errno = EINVAL;
7822+ return NULL;
7823+ }
7824+ int is_keyspace_notification = ctx->flags & (VALKEYMODULE_CTX_KEYSPACE_NOTIFICATION);
78107825 int islua = scriptIsRunning();
78117826 int ismulti = server.in_exec;
7827+ if ((islua || ismulti) && is_keyspace_notification) {
7828+ /* Avoid blocking within transactions when context initiated by
7829+ * keyspace notification. */
7830+ errno = EINVAL;
7831+ return NULL;
7832+ }
78127833 initClientBlockingState(c);
78137834
78147835 c->bstate->module_blocked_handle = zmalloc(sizeof(ValkeyModuleBlockedClient));
@@ -7864,6 +7885,11 @@ ValkeyModuleBlockedClient *moduleBlockClient(ValkeyModuleCtx *ctx,
78647885 c->bstate->timeout = timeout;
78657886 blockClient(c, BLOCKED_MODULE);
78667887 }
7888+ /* Defer response until after being unblocked for a context originated from
7889+ * keyspace notification events */
7890+ if (is_keyspace_notification) {
7891+ initDeferredReplyBuffer(c);
7892+ }
78677893 }
78687894 return bc;
78697895}
@@ -8091,14 +8117,27 @@ int moduleTryServeClientBlockedOnKey(client *c, robj *key) {
80918117 * free_privdata: called in order to free the private data that is passed
80928118 * by ValkeyModule_UnblockClient() call.
80938119 *
8094- * Note: ValkeyModule_UnblockClient should be called for every blocked client,
8095- * even if client was killed, timed-out or disconnected. Failing to do so
8096- * will result in memory leaks.
8120+ * Notes:
8121+ * 1. ValkeyModule_UnblockClient should be called for every blocked client,
8122+ * even if client was killed, timed-out or disconnected. Failing to do so
8123+ * will result in memory leaks.
8124+ * 2. Attempting to block the client on keyspace event notification in versions
8125+ * prior to 8.1.1 leads to a crash.
80978126 *
80988127 * There are some cases where ValkeyModule_BlockClient() cannot be used:
80998128 *
81008129 * 1. If the client is a Lua script.
81018130 * 2. If the client is executing a MULTI block.
8131+ * 3. If the client is a temporary module client.
8132+ * 4. If the client is already blocked.
8133+ *
8134+ * In cases 1 and 2, a call to ValkeyModule_BlockClient() will **not** block the
8135+ * client, but instead produce a specific error reply. Note that if the
8136+ * BlockClient call originated from within a keyspace notification, no error
8137+ * reply is generated but nullptr is returned while the errno is set to EINVAL.
8138+ *
8139+ * In case 3 and 4, a call to ValkeyModule_BlockClient() are no-op, returning
8140+ * nullptr. errno is set to EINVAL for case 3 while ENOTSUP for case 4.
81028141 *
81038142 * In these cases, a call to ValkeyModule_BlockClient() will **not** block the
81048143 * client, but instead produce a specific error reply.
@@ -8290,6 +8329,12 @@ int moduleClientIsBlockedOnKeys(client *c) {
82908329 * needs to be passed to the client, included but not limited some slow
82918330 * to compute reply or some reply obtained via networking.
82928331 *
8332+ * Returns VALKEYMODULE_OK on success. On failure, VALKEYMODULE_ERR is returned
8333+ * and `errno` is set as follows:
8334+ *
8335+ * - EINVAL if bc is NULL.
8336+ * - ENOTSUP if bc contains `blocked on keys` but its timeout callback is NULL.
8337+ *
82938338 * Note 1: this function can be called from threads spawned by the module.
82948339 *
82958340 * Note 2: when we unblock a client that is blocked for keys using the API
@@ -8300,10 +8345,17 @@ int moduleClientIsBlockedOnKeys(client *c) {
83008345 * ValkeyModule_BlockClientOnKeys() is accessible from the timeout
83018346 * callback via VM_GetBlockedClientPrivateData). */
83028347int VM_UnblockClient(ValkeyModuleBlockedClient *bc, void *privdata) {
8348+ if (!bc) {
8349+ errno = EINVAL;
8350+ return VALKEYMODULE_ERR;
8351+ }
83038352 if (bc->blocked_on_keys) {
83048353 /* In theory the user should always pass the timeout handler as an
83058354 * argument, but better to be safe than sorry. */
8306- if (bc->timeout_callback == NULL) return VALKEYMODULE_ERR;
8355+ if (bc->timeout_callback == NULL) {
8356+ errno = ENOTSUP;
8357+ return VALKEYMODULE_ERR;
8358+ }
83078359 if (bc->unblocked) return VALKEYMODULE_OK;
83088360 if (bc->client) moduleBlockedClientTimedOut(bc->client, 1);
83098361 }
@@ -8392,11 +8444,17 @@ void moduleHandleBlockedClients(void) {
83928444 moduleInvokeFreePrivDataCallback(c, bc);
83938445 }
83948446
8395- /* It is possible that this blocked client object accumulated
8396- * replies to send to the client in a thread safe context.
8397- * We need to glue such replies to the client output buffer and
8398- * free the temporary client we just used for the replies. */
8399- if (c) AddReplyFromClient(c, bc->reply_client);
8447+ if (c) {
8448+ /* Replies which were added after the client is blocked by a module
8449+ * are accumulated separately. We need to transmit those replies
8450+ * to the client. */
8451+ commitDeferredReplyBuffer(c, 0);
8452+ /* It is possible that this blocked client object accumulated
8453+ * replies to send to the client in a thread safe context.
8454+ * We need to glue such replies to the client output buffer and
8455+ * free the temporary client we just used for the replies. */
8456+ AddReplyFromClient(c, bc->reply_client);
8457+ }
84008458 moduleReleaseTempClient(bc->reply_client);
84018459 moduleReleaseTempClient(bc->thread_safe_ctx_client);
84028460
@@ -8492,9 +8550,10 @@ void moduleBlockedClientTimedOut(client *c, int from_module) {
84928550
84938551 moduleFreeContext(&ctx);
84948552
8495- if (!from_module)
8553+ if (!from_module) {
84968554 updateStatsOnUnblock(c, bc->background_duration, 0,
84978555 ((server.stat_total_error_replies != prev_error_replies) ? ERROR_COMMAND_FAILED : 0));
8556+ }
84988557
84998558 /* For timeout events, we do not want to call the disconnect callback,
85008559 * because the blocked client will be automatically disconnected in
@@ -8840,12 +8899,16 @@ int VM_NotifyKeyspaceEvent(ValkeyModuleCtx *ctx, int type, const char *event, Va
88408899 return VALKEYMODULE_OK;
88418900}
88428901
8902+ unsigned long moduleNotifyKeyspaceSubscribersCnt(void) {
8903+ return listLength(moduleKeyspaceSubscribers);
8904+ }
8905+
88438906/* Dispatcher for keyspace notifications to module subscriber functions.
88448907 * This gets called only if at least one module requested to be notified on
88458908 * keyspace notifications */
88468909void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid) {
88478910 /* Don't do anything if there aren't any subscribers */
8848- if (listLength(moduleKeyspaceSubscribers ) == 0) return;
8911+ if (moduleNotifyKeyspaceSubscribersCnt( ) == 0) return;
88498912
88508913 /* Ugly hack to handle modules which use write commands from within
88518914 * notify_callback, which they should NOT do!
@@ -8880,8 +8943,14 @@ void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid)
88808943 if ((sub->event_mask & type) &&
88818944 (sub->active == 0 || (sub->module->options & VALKEYMODULE_OPTIONS_ALLOW_NESTED_KEYSPACE_NOTIFICATIONS))) {
88828945 ValkeyModuleCtx ctx;
8883- moduleCreateContext(&ctx, sub->module, VALKEYMODULE_CTX_TEMP_CLIENT);
8946+ if (server.executing_client == NULL) {
8947+ moduleCreateContext(&ctx, sub->module, VALKEYMODULE_CTX_TEMP_CLIENT);
8948+ } else {
8949+ moduleCreateContext(&ctx, sub->module, VALKEYMODULE_CTX_NONE);
8950+ ctx.client = server.executing_client;
8951+ }
88848952 selectDb(ctx.client, dbid);
8953+ ctx.flags |= VALKEYMODULE_CTX_KEYSPACE_NOTIFICATION;
88858954
88868955 /* mark the handler as active to avoid reentrant loops.
88878956 * If the subscriber performs an action triggering itself,
0 commit comments