diff options
Diffstat (limited to 'tests')
-rw-r--r-- | tests/modules/blockedclient.c | 275 | ||||
-rw-r--r-- | tests/unit/moduleapi/async_rm_call.tcl | 380 | ||||
-rw-r--r-- | tests/unit/scripting.tcl | 64 |
3 files changed, 689 insertions, 30 deletions
diff --git a/tests/modules/blockedclient.c b/tests/modules/blockedclient.c index f4234b0d3..db3e80b29 100644 --- a/tests/modules/blockedclient.c +++ b/tests/modules/blockedclient.c @@ -219,6 +219,257 @@ int do_rm_call(RedisModuleCtx *ctx, RedisModuleString **argv, int argc){ return REDISMODULE_OK; } +static void rm_call_async_send_reply(RedisModuleCtx *ctx, RedisModuleCallReply *reply) { + RedisModule_ReplyWithCallReply(ctx, reply); + RedisModule_FreeCallReply(reply); +} + +/* Called when the command that was blocked on 'RM_Call' gets unblocked + * and send the reply to the blocked client. */ +static void rm_call_async_on_unblocked(RedisModuleCtx *ctx, RedisModuleCallReply *reply, void *private_data) { + UNUSED(ctx); + RedisModuleBlockedClient *bc = private_data; + RedisModuleCtx *bctx = RedisModule_GetThreadSafeContext(bc); + rm_call_async_send_reply(bctx, reply); + RedisModule_FreeThreadSafeContext(bctx); + RedisModule_UnblockClient(bc, RedisModule_BlockClientGetPrivateData(bc)); +} + +int do_rm_call_async_fire_and_forget(RedisModuleCtx *ctx, RedisModuleString **argv, int argc){ + UNUSED(argv); + UNUSED(argc); + + if(argc < 2){ + return RedisModule_WrongArity(ctx); + } + const char* cmd = RedisModule_StringPtrLen(argv[1], NULL); + + RedisModuleCallReply* rep = RedisModule_Call(ctx, cmd, "!KEv", argv + 2, argc - 2); + + if(RedisModule_CallReplyType(rep) != REDISMODULE_REPLY_PROMISE) { + RedisModule_ReplyWithCallReply(ctx, rep); + } else { + RedisModule_ReplyWithSimpleString(ctx, "Blocked"); + } + RedisModule_FreeCallReply(rep); + + return REDISMODULE_OK; +} + +static void do_rm_call_async_free_pd(RedisModuleCtx * ctx, void *pd) { + UNUSED(ctx); + RedisModule_FreeCallReply(pd); +} + +static void do_rm_call_async_disconnect(RedisModuleCtx *ctx, struct RedisModuleBlockedClient *bc) { + UNUSED(ctx); + RedisModuleCallReply* rep = RedisModule_BlockClientGetPrivateData(bc); + RedisModule_CallReplyPromiseAbort(rep, NULL); + RedisModule_FreeCallReply(rep); + RedisModule_AbortBlock(bc); +} + +/* + * Callback for do_rm_call_async / do_rm_call_async_script_mode + * Gets the command to invoke as the first argument to the command and runs it, + * passing the rest of the arguments to the command invocation. + * If the command got blocked, blocks the client and unblock it when the command gets unblocked, + * this allows check the K (allow blocking) argument to RM_Call. + */ +int do_rm_call_async(RedisModuleCtx *ctx, RedisModuleString **argv, int argc){ + UNUSED(argv); + UNUSED(argc); + + if(argc < 2){ + return RedisModule_WrongArity(ctx); + } + + size_t format_len = 0; + char format[6] = {0}; + + if (!(RedisModule_GetContextFlags(ctx) & REDISMODULE_CTX_FLAGS_DENY_BLOCKING)) { + /* We are allowed to block the client so we can allow RM_Call to also block us */ + format[format_len++] = 'K'; + } + + const char* invoked_cmd = RedisModule_StringPtrLen(argv[0], NULL); + if (strcasecmp(invoked_cmd, "do_rm_call_async_script_mode") == 0) { + format[format_len++] = 'S'; + } + + format[format_len++] = 'E'; + format[format_len++] = 'v'; + if (strcasecmp(invoked_cmd, "do_rm_call_async_no_replicate") != 0) { + /* Notice, without the '!' flag we will have inconsistency between master and replica. + * This is used only to check '!' flag correctness on blocked commands. */ + format[format_len++] = '!'; + } + + const char* cmd = RedisModule_StringPtrLen(argv[1], NULL); + + RedisModuleCallReply* rep = RedisModule_Call(ctx, cmd, format, argv + 2, argc - 2); + + if(RedisModule_CallReplyType(rep) != REDISMODULE_REPLY_PROMISE) { + rm_call_async_send_reply(ctx, rep); + } else { + RedisModuleBlockedClient *bc = RedisModule_BlockClient(ctx, NULL, NULL, do_rm_call_async_free_pd, 0); + RedisModule_SetDisconnectCallback(bc, do_rm_call_async_disconnect); + RedisModule_BlockClientSetPrivateData(bc, rep); + RedisModule_CallReplyPromiseSetUnblockHandler(rep, rm_call_async_on_unblocked, bc); + } + + return REDISMODULE_OK; +} + +/* Private data for wait_and_do_rm_call_async that holds information about: + * 1. the block client, to unblock when done. + * 2. the arguments, contains the command to run using RM_Call */ +typedef struct WaitAndDoRMCallCtx { + RedisModuleBlockedClient *bc; + RedisModuleString **argv; + int argc; +} WaitAndDoRMCallCtx; + +/* + * This callback will be called when the 'wait' command invoke on 'wait_and_do_rm_call_async' will finish. + * This callback will continue the execution flow just like 'do_rm_call_async' command. + */ +static void wait_and_do_rm_call_async_on_unblocked(RedisModuleCtx *ctx, RedisModuleCallReply *reply, void *private_data) { + WaitAndDoRMCallCtx *wctx = private_data; + if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_INTEGER) { + goto done; + } + + if (RedisModule_CallReplyInteger(reply) != 1) { + goto done; + } + + RedisModule_FreeCallReply(reply); + reply = NULL; + + const char* cmd = RedisModule_StringPtrLen(wctx->argv[0], NULL); + reply = RedisModule_Call(ctx, cmd, "!EKv", wctx->argv + 1, wctx->argc - 1); + +done: + if(RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_PROMISE) { + RedisModuleCtx *bctx = RedisModule_GetThreadSafeContext(wctx->bc); + rm_call_async_send_reply(bctx, reply); + RedisModule_FreeThreadSafeContext(bctx); + RedisModule_UnblockClient(wctx->bc, NULL); + } else { + RedisModule_CallReplyPromiseSetUnblockHandler(reply, rm_call_async_on_unblocked, wctx->bc); + RedisModule_FreeCallReply(reply); + } + for (int i = 0 ; i < wctx->argc ; ++i) { + RedisModule_FreeString(NULL, wctx->argv[i]); + } + RedisModule_Free(wctx->argv); + RedisModule_Free(wctx); +} + +/* + * Callback for wait_and_do_rm_call + * Gets the command to invoke as the first argument, runs 'wait' + * command (using the K flag to RM_Call). Once the wait finished, runs the + * command that was given (just like 'do_rm_call_async'). + */ +int wait_and_do_rm_call_async(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + UNUSED(argv); + UNUSED(argc); + + if(argc < 2){ + return RedisModule_WrongArity(ctx); + } + + int flags = RedisModule_GetContextFlags(ctx); + if (flags & REDISMODULE_CTX_FLAGS_DENY_BLOCKING) { + return RedisModule_ReplyWithError(ctx, "Err can not run wait, blocking is not allowed."); + } + + RedisModuleCallReply* rep = RedisModule_Call(ctx, "wait", "!EKcc", "1", "0"); + if(RedisModule_CallReplyType(rep) != REDISMODULE_REPLY_PROMISE) { + rm_call_async_send_reply(ctx, rep); + } else { + RedisModuleBlockedClient *bc = RedisModule_BlockClient(ctx, NULL, NULL, NULL, 0); + WaitAndDoRMCallCtx *wctx = RedisModule_Alloc(sizeof(*wctx)); + *wctx = (WaitAndDoRMCallCtx){ + .bc = bc, + .argv = RedisModule_Alloc((argc - 1) * sizeof(RedisModuleString*)), + .argc = argc - 1, + }; + + for (int i = 1 ; i < argc ; ++i) { + wctx->argv[i - 1] = RedisModule_HoldString(NULL, argv[i]); + } + RedisModule_CallReplyPromiseSetUnblockHandler(rep, wait_and_do_rm_call_async_on_unblocked, wctx); + RedisModule_FreeCallReply(rep); + } + + return REDISMODULE_OK; +} + +static void blpop_and_set_multiple_keys_on_unblocked(RedisModuleCtx *ctx, RedisModuleCallReply *reply, void *private_data) { + /* ignore the reply */ + RedisModule_FreeCallReply(reply); + WaitAndDoRMCallCtx *wctx = private_data; + for (int i = 0 ; i < wctx->argc ; i += 2) { + RedisModuleCallReply* rep = RedisModule_Call(ctx, "set", "!ss", wctx->argv[i], wctx->argv[i + 1]); + RedisModule_FreeCallReply(rep); + } + + RedisModuleCtx *bctx = RedisModule_GetThreadSafeContext(wctx->bc); + RedisModule_ReplyWithSimpleString(bctx, "OK"); + RedisModule_FreeThreadSafeContext(bctx); + RedisModule_UnblockClient(wctx->bc, NULL); + + for (int i = 0 ; i < wctx->argc ; ++i) { + RedisModule_FreeString(NULL, wctx->argv[i]); + } + RedisModule_Free(wctx->argv); + RedisModule_Free(wctx); + +} + +/* + * Performs a blpop command on a given list and when unblocked set multiple string keys. + * This command allows checking that the unblock callback is performed as a unit + * and its effect are replicated to the replica and AOF wrapped with multi exec. + */ +int blpop_and_set_multiple_keys(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + UNUSED(argv); + UNUSED(argc); + + if(argc < 2 || argc % 2 != 0){ + return RedisModule_WrongArity(ctx); + } + + int flags = RedisModule_GetContextFlags(ctx); + if (flags & REDISMODULE_CTX_FLAGS_DENY_BLOCKING) { + return RedisModule_ReplyWithError(ctx, "Err can not run wait, blocking is not allowed."); + } + + RedisModuleCallReply* rep = RedisModule_Call(ctx, "blpop", "!EKsc", argv[1], "0"); + if(RedisModule_CallReplyType(rep) != REDISMODULE_REPLY_PROMISE) { + rm_call_async_send_reply(ctx, rep); + } else { + RedisModuleBlockedClient *bc = RedisModule_BlockClient(ctx, NULL, NULL, NULL, 0); + WaitAndDoRMCallCtx *wctx = RedisModule_Alloc(sizeof(*wctx)); + *wctx = (WaitAndDoRMCallCtx){ + .bc = bc, + .argv = RedisModule_Alloc((argc - 2) * sizeof(RedisModuleString*)), + .argc = argc - 2, + }; + + for (int i = 0 ; i < argc - 2 ; ++i) { + wctx->argv[i] = RedisModule_HoldString(NULL, argv[i + 2]); + } + RedisModule_CallReplyPromiseSetUnblockHandler(rep, blpop_and_set_multiple_keys_on_unblocked, wctx); + RedisModule_FreeCallReply(rep); + } + + return REDISMODULE_OK; +} + /* simulate a blocked client replying to a thread safe context without creating a thread */ int do_fake_bg_true(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { UNUSED(argv); @@ -316,6 +567,30 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) "write", 0, 0, 0) == REDISMODULE_ERR) return REDISMODULE_ERR; + if (RedisModule_CreateCommand(ctx, "do_rm_call_async", do_rm_call_async, + "write", 0, 0, 0) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx, "do_rm_call_async_script_mode", do_rm_call_async, + "write", 0, 0, 0) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx, "do_rm_call_async_no_replicate", do_rm_call_async, + "write", 0, 0, 0) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx, "do_rm_call_fire_and_forget", do_rm_call_async_fire_and_forget, + "write", 0, 0, 0) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx, "wait_and_do_rm_call", wait_and_do_rm_call_async, + "write", 0, 0, 0) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx, "blpop_and_set_multiple_keys", blpop_and_set_multiple_keys, + "write", 0, 0, 0) == REDISMODULE_ERR) + return REDISMODULE_ERR; + if (RedisModule_CreateCommand(ctx, "do_bg_rm_call", do_bg_rm_call, "", 0, 0, 0) == REDISMODULE_ERR) return REDISMODULE_ERR; diff --git a/tests/unit/moduleapi/async_rm_call.tcl b/tests/unit/moduleapi/async_rm_call.tcl new file mode 100644 index 000000000..142b098f0 --- /dev/null +++ b/tests/unit/moduleapi/async_rm_call.tcl @@ -0,0 +1,380 @@ +set testmodule [file normalize tests/modules/blockedclient.so] +set testmodule2 [file normalize tests/modules/postnotifications.so] +set testmodule3 [file normalize tests/modules/blockonkeys.so] + +start_server {tags {"modules"}} { + r module load $testmodule + + test {Locked GIL acquisition from async RM_Call} { + assert_equal {OK} [r do_rm_call_async acquire_gil] + } + + test "Blpop on async RM_Call fire and forget" { + assert_equal {Blocked} [r do_rm_call_fire_and_forget blpop l 0] + r lpush l a + assert_equal {0} [r llen l] + } + + foreach cmd {do_rm_call_async do_rm_call_async_script_mode} { + + test "Blpop on async RM_Call using $cmd" { + set rd [redis_deferring_client] + + $rd $cmd blpop l 0 + wait_for_blocked_clients_count 1 + r lpush l a + assert_equal [$rd read] {l a} + wait_for_blocked_clients_count 0 + } + + test "Brpop on async RM_Call using $cmd" { + set rd [redis_deferring_client] + + $rd $cmd brpop l 0 + wait_for_blocked_clients_count 1 + r lpush l a + assert_equal [$rd read] {l a} + wait_for_blocked_clients_count 0 + } + + test "Brpoplpush on async RM_Call using $cmd" { + set rd [redis_deferring_client] + + $rd $cmd brpoplpush l1 l2 0 + wait_for_blocked_clients_count 1 + r lpush l1 a + assert_equal [$rd read] {a} + wait_for_blocked_clients_count 0 + r lpop l2 + } {a} + + test "Blmove on async RM_Call using $cmd" { + set rd [redis_deferring_client] + + $rd $cmd blmove l1 l2 LEFT LEFT 0 + wait_for_blocked_clients_count 1 + r lpush l1 a + assert_equal [$rd read] {a} + wait_for_blocked_clients_count 0 + r lpop l2 + } {a} + + test "Bzpopmin on async RM_Call using $cmd" { + set rd [redis_deferring_client] + + $rd $cmd bzpopmin s 0 + wait_for_blocked_clients_count 1 + r zadd s 10 foo + assert_equal [$rd read] {s foo 10} + wait_for_blocked_clients_count 0 + } + + test "Bzpopmax on async RM_Call using $cmd" { + set rd [redis_deferring_client] + + $rd $cmd bzpopmax s 0 + wait_for_blocked_clients_count 1 + r zadd s 10 foo + assert_equal [$rd read] {s foo 10} + wait_for_blocked_clients_count 0 + } + } + + test {Nested async RM_Call} { + set rd [redis_deferring_client] + + $rd do_rm_call_async do_rm_call_async do_rm_call_async do_rm_call_async blpop l 0 + wait_for_blocked_clients_count 1 + r lpush l a + assert_equal [$rd read] {l a} + wait_for_blocked_clients_count 0 + } + + test {Test multiple async RM_Call waiting on the same event} { + set rd1 [redis_deferring_client] + set rd2 [redis_deferring_client] + + $rd1 do_rm_call_async do_rm_call_async do_rm_call_async do_rm_call_async blpop l 0 + $rd2 do_rm_call_async do_rm_call_async do_rm_call_async do_rm_call_async blpop l 0 + wait_for_blocked_clients_count 2 + r lpush l element element + assert_equal [$rd1 read] {l element} + assert_equal [$rd2 read] {l element} + wait_for_blocked_clients_count 0 + } + + test {async RM_Call calls RM_Call} { + assert_equal {PONG} [r do_rm_call_async do_rm_call ping] + } + + test {async RM_Call calls background RM_Call calls RM_Call} { + assert_equal {PONG} [r do_rm_call_async do_bg_rm_call do_rm_call ping] + } + + test {async RM_Call calls background RM_Call calls RM_Call calls async RM_Call} { + assert_equal {PONG} [r do_rm_call_async do_bg_rm_call do_rm_call do_rm_call_async ping] + } + + test {async RM_Call inside async RM_Call callback} { + set rd [redis_deferring_client] + $rd wait_and_do_rm_call blpop l 0 + wait_for_blocked_clients_count 1 + + start_server {} { + test "Connect a replica to the master instance" { + r slaveof [srv -1 host] [srv -1 port] + wait_for_condition 50 100 { + [s role] eq {slave} && + [string match {*master_link_status:up*} [r info replication]] + } else { + fail "Can't turn the instance into a replica" + } + } + + assert_equal {1} [r -1 lpush l a] + assert_equal [$rd read] {l a} + } + + wait_for_blocked_clients_count 0 + } + + test {Become replica while having async RM_Call running} { + r flushall + set rd [redis_deferring_client] + $rd do_rm_call_async blpop l 0 + wait_for_blocked_clients_count 1 + + #become a replica of a not existing redis + r replicaof localhost 30000 + + catch {[$rd read]} e + assert_match {UNBLOCKED force unblock from blocking operation*} $e + wait_for_blocked_clients_count 0 + + r replicaof no one + + r lpush l 1 + # make sure the async rm_call was aborted + assert_equal [r llen l] {1} + } + + test {Pipeline with blocking RM_Call} { + r flushall + set rd [redis_deferring_client] + set buf "" + append buf "do_rm_call_async blpop l 0\r\n" + append buf "ping\r\n" + $rd write $buf + $rd flush + wait_for_blocked_clients_count 1 + + # release the blocked client + r lpush l 1 + + assert_equal [$rd read] {l 1} + assert_equal [$rd read] {PONG} + + wait_for_blocked_clients_count 0 + } + + test {blocking RM_Call abort} { + r flushall + set rd [redis_deferring_client] + + $rd client id + set client_id [$rd read] + + $rd do_rm_call_async blpop l 0 + wait_for_blocked_clients_count 1 + + r client kill ID $client_id + assert_error {*error reading reply*} {$rd read} + + wait_for_blocked_clients_count 0 + + r lpush l 1 + # make sure the async rm_call was aborted + assert_equal [r llen l] {1} + } +} + +start_server {tags {"modules"}} { + r module load $testmodule + + test {Test basic replication stream on unblock handler} { + r flushall + set repl [attach_to_replication_stream] + + set rd [redis_deferring_client] + + $rd do_rm_call_async blpop l 0 + wait_for_blocked_clients_count 1 + r lpush l a + assert_equal [$rd read] {l a} + + assert_replication_stream $repl { + {select *} + {lpush l a} + {lpop l} + } + close_replication_stream $repl + + wait_for_blocked_clients_count 0 + } + + test {Test unblock handler are executed as a unit} { + r flushall + set repl [attach_to_replication_stream] + + set rd [redis_deferring_client] + + $rd blpop_and_set_multiple_keys l x 1 y 2 + wait_for_blocked_clients_count 1 + r lpush l a + assert_equal [$rd read] {OK} + + assert_replication_stream $repl { + {select *} + {lpush l a} + {multi} + {lpop l} + {set x 1} + {set y 2} + {exec} + } + close_replication_stream $repl + + wait_for_blocked_clients_count 0 + } + + test {Test no propagation of blocking command} { + r flushall + set repl [attach_to_replication_stream] + + set rd [redis_deferring_client] + + $rd do_rm_call_async_no_replicate blpop l 0 + wait_for_blocked_clients_count 1 + r lpush l a + assert_equal [$rd read] {l a} + + # make sure the lpop are not replicated + r set x 1 + + assert_replication_stream $repl { + {select *} + {lpush l a} + {set x 1} + } + close_replication_stream $repl + + wait_for_blocked_clients_count 0 + } +} + +start_server {tags {"modules"}} { + r module load $testmodule + r module load $testmodule2 + + test {Test unblock handler are executed as a unit with key space notifications} { + r flushall + set repl [attach_to_replication_stream] + + set rd [redis_deferring_client] + + $rd blpop_and_set_multiple_keys l string_foo 1 string_bar 2 + wait_for_blocked_clients_count 1 + r lpush l a + assert_equal [$rd read] {OK} + + assert_replication_stream $repl { + {select *} + {lpush l a} + {multi} + {lpop l} + {set string_foo 1} + {set string_bar 2} + {incr string_changed{string_foo}} + {incr string_changed{string_bar}} + {incr string_total} + {incr string_total} + {exec} + } + close_replication_stream $repl + + wait_for_blocked_clients_count 0 + } + + test {Test unblock handler are executed as a unit with lazy expire} { + r flushall + r DEBUG SET-ACTIVE-EXPIRE 0 + set repl [attach_to_replication_stream] + + set rd [redis_deferring_client] + + $rd blpop_and_set_multiple_keys l string_foo 1 string_bar 2 + wait_for_blocked_clients_count 1 + r lpush l a + assert_equal [$rd read] {OK} + + # set expiration on string_foo + r pexpire string_foo 1 + after 10 + + # now the key should have been expired + $rd blpop_and_set_multiple_keys l string_foo 1 string_bar 2 + wait_for_blocked_clients_count 1 + r lpush l a + assert_equal [$rd read] {OK} + + assert_replication_stream $repl { + {select *} + {lpush l a} + {multi} + {lpop l} + {set string_foo 1} + {set string_bar 2} + {incr string_changed{string_foo}} + {incr string_changed{string_bar}} + {incr string_total} + {incr string_total} + {exec} + {pexpireat string_foo *} + {lpush l a} + {multi} + {lpop l} + {del string_foo} + {set string_foo 1} + {set string_bar 2} + {incr expired} + {incr string_changed{string_foo}} + {incr string_changed{string_bar}} + {incr string_total} + {incr string_total} + {exec} + } + close_replication_stream $repl + r DEBUG SET-ACTIVE-EXPIRE 1 + } + + wait_for_blocked_clients_count 0 +} + +start_server {tags {"modules"}} { + r module load $testmodule + r module load $testmodule3 + + test {Test unblock handler on module blocked on keys} { + set rd [redis_deferring_client] + + r fsl.push l 1 + $rd do_rm_call_async FSL.BPOPGT l 3 0 + wait_for_blocked_clients_count 1 + r fsl.push l 2 + r fsl.push l 3 + r fsl.push l 4 + assert_equal [$rd read] {4} + + wait_for_blocked_clients_count 0 + } +}
\ No newline at end of file diff --git a/tests/unit/scripting.tcl b/tests/unit/scripting.tcl index 02459354a..29193f642 100644 --- a/tests/unit/scripting.tcl +++ b/tests/unit/scripting.tcl @@ -217,41 +217,45 @@ start_server {tags {"scripting"}} { } {*execution time*} } - test {EVAL - Scripts can't run blpop command} { - set e {} - catch {run_script {return redis.pcall('blpop','x',0)} 1 x} e - set e - } {*not allowed*} + test {EVAL - Scripts do not block on blpop command} { + r lpush l 1 + r lpop l + run_script {return redis.pcall('blpop','l',0)} 1 l + } {} - test {EVAL - Scripts can't run brpop command} { - set e {} - catch {run_script {return redis.pcall('brpop','empty_list',0)} 1 empty_list} e - set e - } {*not allowed*} + test {EVAL - Scripts do not block on brpop command} { + r lpush l 1 + r lpop l + run_script {return redis.pcall('brpop','l',0)} 1 l + } {} - test {EVAL - Scripts can't run brpoplpush command} { - set e {} - catch {run_script {return redis.pcall('brpoplpush','empty_list1{t}', 'empty_list2{t}',0)} 2 empty_list1{t} empty_list2{t}} e - set e - } {*not allowed*} + test {EVAL - Scripts do not block on brpoplpush command} { + r lpush empty_list1{t} 1 + r lpop empty_list1{t} + run_script {return redis.pcall('brpoplpush','empty_list1{t}', 'empty_list2{t}',0)} 2 empty_list1{t} empty_list2{t} + } {} - test {EVAL - Scripts can't run blmove command} { - set e {} - catch {run_script {return redis.pcall('blmove','empty_list1{t}', 'empty_list2{t}', 'LEFT', 'LEFT', 0)} 2 empty_list1{t} empty_list2{t}} e - set e - } {*not allowed*} + test {EVAL - Scripts do not block on blmove command} { + r lpush empty_list1{t} 1 + r lpop empty_list1{t} + run_script {return redis.pcall('blmove','empty_list1{t}', 'empty_list2{t}', 'LEFT', 'LEFT', 0)} 2 empty_list1{t} empty_list2{t} + } {} - test {EVAL - Scripts can't run bzpopmin command} { - set e {} - catch {run_script {return redis.pcall('bzpopmin','empty_zset', 0)} 1 empty_zset} e - set e - } {*not allowed*} + test {EVAL - Scripts do not block on bzpopmin command} { + r zadd empty_zset 10 foo + r zmpop 1 empty_zset MIN + run_script {return redis.pcall('bzpopmin','empty_zset', 0)} 1 empty_zset + } {} - test {EVAL - Scripts can't run bzpopmax command} { - set e {} - catch {run_script {return redis.pcall('bzpopmax','empty_zset', 0)} 1 empty_zset} e - set e - } {*not allowed*} + test {EVAL - Scripts do not block on bzpopmax command} { + r zadd empty_zset 10 foo + r zmpop 1 empty_zset MIN + run_script {return redis.pcall('bzpopmax','empty_zset', 0)} 1 empty_zset + } {} + + test {EVAL - Scripts do not block on wait} { + run_script {return redis.pcall('wait','1','0')} 0 + } {0} test {EVAL - Scripts can't run XREAD and XREADGROUP with BLOCK option} { r del s |