{
  "id": "modules-blocking-ops",
  "title": "Redis modules and blocking commands",
  "url": "https://un5pn9hmggug.julianrbryant.com/docs/latest/develop/reference/modules/modules-blocking-ops/",
  "summary": "How to implement blocking commands in a Redis module",
  "tags": [
    "docs",
    "develop",
    "stack",
    "oss",
    "rs",
    "rc",
    "oss",
    "kubernetes",
    "clients"
  ],
  "last_updated": "2026-04-01T08:10:08-05:00",
  "page_type": "content",
  "content_hash": "c2ea77453741381c64bed71960bc6fac6698e8bcaa49313688c469725fe88cd3",
  "sections": [
    {
      "id": "content",
      "title": "Content",
      "role": "content",
      "text": "Redis has a few blocking commands among the built-in set of commands.\nOne of the most used is [`BLPOP`]() (or the symmetric [`BRPOP`]()) which blocks\nwaiting for elements arriving in a list.\n\nThe interesting fact about blocking commands is that they do not block\nthe whole server, but just the client calling them. Usually the reason to\nblock is that we expect some external event to happen: this can be\nsome change in the Redis data structures like in the [`BLPOP`]() case, a\nlong computation happening in a thread, to receive some data from the\nnetwork, and so forth.\n\nRedis modules have the ability to implement blocking commands as well,\nthis documentation shows how the API works and describes a few patterns\nthat can be used in order to model blocking commands.\n\n\nHow blocking and resuming works.\n---\n\n_Note: You may want to check the `helloblock.c` example in the Redis source tree\ninside the `src/modules` directory, for a simple to understand example\non how the blocking API is applied._\n\nIn Redis modules, commands are implemented by callback functions that\nare invoked by the Redis core when the specific command is called\nby the user. Normally the callback terminates its execution sending\nsome reply to the client. Using the following function instead, the\nfunction implementing the module command may request that the client\nis put into the blocked state:\n\n    RedisModuleBlockedClient *RedisModule_BlockClient(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(void*), long long timeout_ms);\n\nThe function returns a `RedisModuleBlockedClient` object, which is later\nused in order to unblock the client. The arguments have the following\nmeaning:\n\n* `ctx` is the command execution context as usually in the rest of the API.\n* `reply_callback` is the callback, having the same prototype of a normal command function, that is called when the client is unblocked in order to return a reply to the client.\n* `timeout_callback` is the callback, having the same prototype of a normal command function that is called when the client reached the `ms` timeout.\n* `free_privdata` is the callback that is called in order to free the private data. Private data is a pointer to some data that is passed between the API used to unblock the client, to the callback that will send the reply to the client. We'll see how this mechanism works later in this document.\n* `ms` is the timeout in milliseconds. When the timeout is reached, the timeout callback is called and the client is automatically aborted.\n\nOnce a client is blocked, it can be unblocked with the following API:\n\n    int RedisModule_UnblockClient(RedisModuleBlockedClient *bc, void *privdata);\n\nThe function takes as argument the blocked client object returned by\nthe previous call to `RedisModule_BlockClient()`, and unblock the client.\nImmediately before the client gets unblocked, the `reply_callback` function\nspecified when the client was blocked is called: this function will\nhave access to the `privdata` pointer used here.\n\nIMPORTANT: The above function is thread safe, and can be called from within\na thread doing some work in order to implement the command that blocked\nthe client.\n\nThe `privdata` data will be freed automatically using the `free_privdata`\ncallback when the client is unblocked. This is useful **since the reply\ncallback may never be called** in case the client timeouts or disconnects\nfrom the server, so it's important that it's up to an external function\nto have the responsibility to free the data passed if needed.\n\nTo better understand how the API works, we can imagine writing a command\nthat blocks a client for one second, and then send as reply \"Hello!\".\n\nNote: arity checks and other non important things are not implemented\nint his command, in order to take the example simple.\n\n    int Example_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv,\n                             int argc)\n    {\n        RedisModuleBlockedClient *bc =\n            RedisModule_BlockClient(ctx,reply_func,timeout_func,NULL,0);\n\n        pthread_t tid;\n        pthread_create(&tid,NULL,threadmain,bc);\n\n        return REDISMODULE_OK;\n    }\n\n    void *threadmain(void *arg) {\n        RedisModuleBlockedClient *bc = arg;\n\n        sleep(1); /* Wait one second and unblock. */\n        RedisModule_UnblockClient(bc,NULL);\n    }\n\nThe above command blocks the client ASAP, spawning a thread that will\nwait a second and will unblock the client. Let's check the reply and\ntimeout callbacks, which are in our case very similar, since they\njust reply the client with a different reply type.\n\n    int reply_func(RedisModuleCtx *ctx, RedisModuleString **argv,\n                   int argc)\n    {\n        return RedisModule_ReplyWithSimpleString(ctx,\"Hello!\");\n    }\n\n    int timeout_func(RedisModuleCtx *ctx, RedisModuleString **argv,\n                   int argc)\n    {\n        return RedisModule_ReplyWithNull(ctx);\n    }\n\nThe reply callback just sends the \"Hello!\" string to the client.\nThe important bit here is that the reply callback is called when the\nclient is unblocked from the thread.\n\nThe timeout command returns `NULL`, as it often happens with actual\nRedis blocking commands timing out.\n\nPassing reply data when unblocking\n---\n\nThe above example is simple to understand but lacks an important\nreal world aspect of an actual blocking command implementation: often\nthe reply function will need to know what to reply to the client,\nand this information is often provided as the client is unblocked.\n\nWe could modify the above example so that the thread generates a\nrandom number after waiting one second. You can think at it as an\nactually expansive operation of some kind. Then this random number\ncan be passed to the reply function so that we return it to the command\ncaller. In order to make this working, we modify the functions as follow:\n\n    void *threadmain(void *arg) {\n        RedisModuleBlockedClient *bc = arg;\n\n        sleep(1); /* Wait one second and unblock. */\n\n        long *mynumber = RedisModule_Alloc(sizeof(long));\n        *mynumber = rand();\n        RedisModule_UnblockClient(bc,mynumber);\n    }\n\nAs you can see, now the unblocking call is passing some private data,\nthat is the `mynumber` pointer, to the reply callback. In order to\nobtain this private data, the reply callback will use the following\nfunction:\n\n    void *RedisModule_GetBlockedClientPrivateData(RedisModuleCtx *ctx);\n\nSo our reply callback is modified like that:\n\n    int reply_func(RedisModuleCtx *ctx, RedisModuleString **argv,\n                   int argc)\n    {\n        long *mynumber = RedisModule_GetBlockedClientPrivateData(ctx);\n        /* IMPORTANT: don't free mynumber here, but in the\n         * free privdata callback. */\n        return RedisModule_ReplyWithLongLong(ctx,mynumber);\n    }\n\nNote that we also need to pass a `free_privdata` function when blocking\nthe client with `RedisModule_BlockClient()`, since the allocated\nlong value must be freed. Our callback will look like the following:\n\n    void free_privdata(void *privdata) {\n        RedisModule_Free(privdata);\n    }\n\nNOTE: It is important to stress that the private data is best freed in the\n`free_privdata` callback because the reply function may not be called\nif the client disconnects or timeout.\n\nAlso note that the private data is also accessible from the timeout\ncallback, always using the `GetBlockedClientPrivateData()` API.\n\nAborting the blocking of a client\n---\n\nOne problem that sometimes arises is that we need to allocate resources\nin order to implement the non blocking command. So we block the client,\nthen, for example, try to create a thread, but the thread creation function\nreturns an error. What to do in such a condition in order to recover? We\ndon't want to take the client blocked, nor we want to call `UnblockClient()`\nbecause this will trigger the reply callback to be called.\n\nIn this case the best thing to do is to use the following function:\n\n    int RedisModule_AbortBlock(RedisModuleBlockedClient *bc);\n\nPractically this is how to use it:\n\n    int Example_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv,\n                             int argc)\n    {\n        RedisModuleBlockedClient *bc =\n            RedisModule_BlockClient(ctx,reply_func,timeout_func,NULL,0);\n\n        pthread_t tid;\n        if (pthread_create(&tid,NULL,threadmain,bc) != 0) {\n            RedisModule_AbortBlock(bc);\n            RedisModule_ReplyWithError(ctx,\"Sorry can't create a thread\");\n        }\n\n        return REDISMODULE_OK;\n    }\n\nThe client will be unblocked but the reply callback will not be called.\n\nImplementing the command, reply and timeout callback using a single function\n---\n\nThe following functions can be used in order to implement the reply and\ncallback with the same function that implements the primary command\nfunction:\n\n    int RedisModule_IsBlockedReplyRequest(RedisModuleCtx *ctx);\n    int RedisModule_IsBlockedTimeoutRequest(RedisModuleCtx *ctx);\n\nSo I could rewrite the example command without using a separated\nreply and timeout callback:\n\n    int Example_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv,\n                             int argc)\n    {\n        if (RedisModule_IsBlockedReplyRequest(ctx)) {\n            long *mynumber = RedisModule_GetBlockedClientPrivateData(ctx);\n            return RedisModule_ReplyWithLongLong(ctx,mynumber);\n        } else if (RedisModule_IsBlockedTimeoutRequest) {\n            return RedisModule_ReplyWithNull(ctx);\n        }\n\n        RedisModuleBlockedClient *bc =\n            RedisModule_BlockClient(ctx,reply_func,timeout_func,NULL,0);\n\n        pthread_t tid;\n        if (pthread_create(&tid,NULL,threadmain,bc) != 0) {\n            RedisModule_AbortBlock(bc);\n            RedisModule_ReplyWithError(ctx,\"Sorry can't create a thread\");\n        }\n\n        return REDISMODULE_OK;\n    }\n\nFunctionally is the same but there are people that will prefer the less\nverbose implementation that concentrates most of the command logic in a\nsingle function.\n\nWorking on copies of data inside a thread\n---\n\nAn interesting pattern in order to work with threads implementing the\nslow part of a command, is to work with a copy of the data, so that\nwhile some operation is performed in a key, the user continues to see\nthe old version. However when the thread terminated its work, the\nrepresentations are swapped and the new, processed version, is used.\n\nAn example of this approach is the\n[Neural Redis module](https://github.com/antirez/neural-redis)\nwhere neural networks are trained in different threads while the\nuser can still execute and inspect their older versions.\n\nFuture work\n---\n\nAn API is work in progress right now in order to allow Redis modules APIs\nto be called in a safe way from threads, so that the threaded command\ncan access the data space and do incremental operations.\n\nThere is no ETA for this feature but it may appear in the course of the\nRedis 4.0 release at some point."
    }
  ],
  "examples": []
}
