Skip to content

Commit 77a65e8

Browse files
authored
support XREAD[GROUP] with BLOCK option in scripts (redis#12596)
In redis#11568 we removed the NOSCRIPT flag from commands and keep the BLOCKING flag. Aiming to allow them in scripts and let them implicitly behave in the non-blocking way. In that sense, the old behavior was to allow LPOP and reject BLPOP, and the new behavior, is to allow BLPOP too, and fail it only in case it ends up blocking. So likewise, so far we allowed XREAD and rejected XREAD BLOCK, and we will now allow that too, and only reject it if it ends up blocking.
1 parent e5ef161 commit 77a65e8

File tree

4 files changed

+26
-14
lines changed

4 files changed

+26
-14
lines changed

src/commands.def

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10861,7 +10861,7 @@ struct COMMAND_STRUCT redisCommandTable[] = {
1086110861
{MAKE_CMD("xlen","Return the number of messages in a stream.","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XLEN_History,0,XLEN_Tips,0,xlenCommand,2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_STREAM,XLEN_Keyspecs,1,NULL,1),.args=XLEN_Args},
1086210862
{MAKE_CMD("xpending","Returns the information and entries from a stream consumer group's pending entries list.","O(N) with N being the number of elements returned, so asking for a small fixed number of entries per call is O(1). O(M), where M is the total number of entries scanned when used with the IDLE filter. When the command returns just the summary and the list of consumers is small, it runs in O(1) time; otherwise, an additional O(N) time for iterating every consumer.","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XPENDING_History,1,XPENDING_Tips,1,xpendingCommand,-3,CMD_READONLY,ACL_CATEGORY_STREAM,XPENDING_Keyspecs,1,NULL,3),.args=XPENDING_Args},
1086310863
{MAKE_CMD("xrange","Returns the messages from a stream within a range of IDs.","O(N) with N being the number of elements being returned. If N is constant (e.g. always asking for the first 10 elements with COUNT), you can consider it O(1).","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XRANGE_History,1,XRANGE_Tips,0,xrangeCommand,-4,CMD_READONLY,ACL_CATEGORY_STREAM,XRANGE_Keyspecs,1,NULL,4),.args=XRANGE_Args},
10864-
{MAKE_CMD("xread","Returns messages from multiple streams with IDs greater than the ones requested. Blocks until a message is available otherwise.",NULL,"5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XREAD_History,0,XREAD_Tips,0,xreadCommand,-4,CMD_BLOCKING|CMD_READONLY|CMD_BLOCKING,ACL_CATEGORY_STREAM,XREAD_Keyspecs,1,xreadGetKeys,3),.args=XREAD_Args},
10864+
{MAKE_CMD("xread","Returns messages from multiple streams with IDs greater than the ones requested. Blocks until a message is available otherwise.",NULL,"5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XREAD_History,0,XREAD_Tips,0,xreadCommand,-4,CMD_BLOCKING|CMD_READONLY,ACL_CATEGORY_STREAM,XREAD_Keyspecs,1,xreadGetKeys,3),.args=XREAD_Args},
1086510865
{MAKE_CMD("xreadgroup","Returns new or historical messages from a stream for a consumer in a group. Blocks until a message is available otherwise.","For each stream mentioned: O(M) with M being the number of elements returned. If M is constant (e.g. always asking for the first 10 elements with COUNT), you can consider it O(1). On the other side when XREADGROUP blocks, XADD will pay the O(N) time in order to serve the N clients blocked on the stream getting new data.","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XREADGROUP_History,0,XREADGROUP_Tips,0,xreadCommand,-7,CMD_BLOCKING|CMD_WRITE,ACL_CATEGORY_STREAM,XREADGROUP_Keyspecs,1,xreadGetKeys,5),.args=XREADGROUP_Args},
1086610866
{MAKE_CMD("xrevrange","Returns the messages from a stream within a range of IDs in reverse order.","O(N) with N being the number of elements returned. If N is constant (e.g. always asking for the first 10 elements with COUNT), you can consider it O(1).","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XREVRANGE_History,1,XREVRANGE_Tips,0,xrevrangeCommand,-4,CMD_READONLY,ACL_CATEGORY_STREAM,XREVRANGE_Keyspecs,1,NULL,4),.args=XREVRANGE_Args},
1086710867
{MAKE_CMD("xsetid","An internal command for replicating stream values.","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XSETID_History,1,XSETID_Tips,0,xsetidCommand,-3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_STREAM,XSETID_Keyspecs,1,NULL,4),.args=XSETID_Args},

src/commands/xread.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@
88
"get_keys_function": "xreadGetKeys",
99
"command_flags": [
1010
"BLOCKING",
11-
"READONLY",
12-
"BLOCKING"
11+
"READONLY"
1312
],
1413
"acl_categories": [
1514
"STREAM"

src/t_stream.c

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2188,14 +2188,6 @@ void xreadCommand(client *c) {
21882188
int moreargs = c->argc-i-1;
21892189
char *o = c->argv[i]->ptr;
21902190
if (!strcasecmp(o,"BLOCK") && moreargs) {
2191-
if (c->flags & CLIENT_SCRIPT) {
2192-
/*
2193-
* Although the CLIENT_DENY_BLOCKING flag should protect from blocking the client
2194-
* on Lua/MULTI/RM_Call we want special treatment for Lua to keep backward compatibility.
2195-
* There is no sense to use BLOCK option within Lua. */
2196-
addReplyErrorFormat(c, "%s command is not allowed with BLOCK option from scripts", (char *)c->argv[0]->ptr);
2197-
return;
2198-
}
21992191
i++;
22002192
if (getTimeoutFromObjectOrReply(c,c->argv[i],&timeout,
22012193
UNIT_MILLISECONDS) != C_OK) return;

tests/unit/scripting.tcl

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -257,17 +257,38 @@ start_server {tags {"scripting"}} {
257257
run_script {return redis.pcall('wait','1','0')} 0
258258
} {0}
259259

260-
test {EVAL - Scripts can't run XREAD and XREADGROUP with BLOCK option} {
260+
test {EVAL - Scripts do not block on XREAD with BLOCK option} {
261261
r del s
262262
r xgroup create s g $ MKSTREAM
263263
set res [run_script {return redis.pcall('xread','STREAMS','s','$')} 1 s]
264264
assert {$res eq {}}
265-
assert_error "*xread command is not allowed with BLOCK option from scripts" {run_script {return redis.pcall('xread','BLOCK',0,'STREAMS','s','$')} 1 s}
265+
run_script {return redis.pcall('xread','BLOCK',0,'STREAMS','s','$')} 1 s
266+
} {}
267+
268+
test {EVAL - Scripts do not block on XREADGROUP with BLOCK option} {
266269
set res [run_script {return redis.pcall('xreadgroup','group','g','c','STREAMS','s','>')} 1 s]
267270
assert {$res eq {}}
268-
assert_error "*xreadgroup command is not allowed with BLOCK option from scripts" {run_script {return redis.pcall('xreadgroup','group','g','c','BLOCK',0,'STREAMS','s','>')} 1 s}
271+
run_script {return redis.pcall('xreadgroup','group','g','c','BLOCK',0,'STREAMS','s','>')} 1 s
272+
} {}
273+
274+
test {EVAL - Scripts do not block on XREAD with BLOCK option -- non empty stream} {
275+
r XADD s * a 1
276+
set res [run_script {return redis.pcall('xread','BLOCK',0,'STREAMS','s','$')} 1 s]
277+
assert {$res eq {}}
278+
279+
set res [run_script {return redis.pcall('xread','BLOCK',0,'STREAMS','s','0-0')} 1 s]
280+
assert {[lrange [lindex $res 0 1 0 1] 0 1] eq {a 1}}
269281
}
270282

283+
test {EVAL - Scripts do not block on XREADGROUP with BLOCK option -- non empty stream} {
284+
r XADD s * b 2
285+
set res [
286+
run_script {return redis.pcall('xreadgroup','group','g','c','BLOCK',0,'STREAMS','s','>')} 1 s
287+
]
288+
assert {[llength [lindex $res 0 1]] == 2}
289+
lindex $res 0 1 0 1
290+
} {a 1}
291+
271292
test {EVAL - Scripts can run non-deterministic commands} {
272293
set e {}
273294
catch {

0 commit comments

Comments
 (0)