Skip to content

Commit

Permalink
Nested MULTI or WATCH in MULTI now will abort the transaction (valkey…
Browse files Browse the repository at this point in the history
…-io#723)

Currently, for nested MULTI or executing WATCH in MULTI, we will return
an error but we will not abort the transaction.

```
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> multi
(error) ERR MULTI calls can not be nested
127.0.0.1:6379(TX)> set key value
QUEUED
127.0.0.1:6379(TX)> exec
1) OK

127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> watch key
(error) ERR WATCH inside MULTI is not allowed
127.0.0.1:6379(TX)> set key value
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
```

This is an unexpected behavior that should abort the transaction.
The number of elements returned by EXEC also doesn't match the number
of commands in MULTI.
Add the NO_MULTI flag to them so that they will
be rejected in processCommand and rejectCommand will abort the
transaction.

So there are two visible changes:

- Different words in the error messages. (Command not allowed inside a
transaction)
- Exec returns error.

Signed-off-by: Binbin <[email protected]>
  • Loading branch information
enjoy-binbin authored Jul 3, 2024
1 parent 2d6791b commit 6bf1d02
Show file tree
Hide file tree
Showing 5 changed files with 10 additions and 21 deletions.
4 changes: 2 additions & 2 deletions src/commands.def
Original file line number Diff line number Diff line change
Expand Up @@ -11022,8 +11022,8 @@ struct COMMAND_STRUCT serverCommandTable[] = {
/* transactions */
{MAKE_CMD("discard","Discards a transaction.","O(N), when N is the number of queued commands","2.0.0",CMD_DOC_NONE,NULL,NULL,"transactions",COMMAND_GROUP_TRANSACTIONS,DISCARD_History,0,DISCARD_Tips,0,discardCommand,1,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_FAST|CMD_ALLOW_BUSY,ACL_CATEGORY_TRANSACTION,DISCARD_Keyspecs,0,NULL,0)},
{MAKE_CMD("exec","Executes all commands in a transaction.","Depends on commands in the transaction","1.2.0",CMD_DOC_NONE,NULL,NULL,"transactions",COMMAND_GROUP_TRANSACTIONS,EXEC_History,0,EXEC_Tips,0,execCommand,1,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SKIP_SLOWLOG,ACL_CATEGORY_TRANSACTION,EXEC_Keyspecs,0,NULL,0)},
{MAKE_CMD("multi","Starts a transaction.","O(1)","1.2.0",CMD_DOC_NONE,NULL,NULL,"transactions",COMMAND_GROUP_TRANSACTIONS,MULTI_History,0,MULTI_Tips,0,multiCommand,1,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_FAST|CMD_ALLOW_BUSY,ACL_CATEGORY_TRANSACTION,MULTI_Keyspecs,0,NULL,0)},
{MAKE_CMD("multi","Starts a transaction.","O(1)","1.2.0",CMD_DOC_NONE,NULL,NULL,"transactions",COMMAND_GROUP_TRANSACTIONS,MULTI_History,0,MULTI_Tips,0,multiCommand,1,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_FAST|CMD_NO_MULTI|CMD_ALLOW_BUSY,ACL_CATEGORY_TRANSACTION,MULTI_Keyspecs,0,NULL,0)},
{MAKE_CMD("unwatch","Forgets about watched keys of a transaction.","O(1)","2.2.0",CMD_DOC_NONE,NULL,NULL,"transactions",COMMAND_GROUP_TRANSACTIONS,UNWATCH_History,0,UNWATCH_Tips,0,unwatchCommand,1,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_FAST|CMD_ALLOW_BUSY,ACL_CATEGORY_TRANSACTION,UNWATCH_Keyspecs,0,NULL,0)},
{MAKE_CMD("watch","Monitors changes to keys to determine the execution of a transaction.","O(1) for every key.","2.2.0",CMD_DOC_NONE,NULL,NULL,"transactions",COMMAND_GROUP_TRANSACTIONS,WATCH_History,0,WATCH_Tips,0,watchCommand,-2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_FAST|CMD_ALLOW_BUSY,ACL_CATEGORY_TRANSACTION,WATCH_Keyspecs,1,NULL,1),.args=WATCH_Args},
{MAKE_CMD("watch","Monitors changes to keys to determine the execution of a transaction.","O(1) for every key.","2.2.0",CMD_DOC_NONE,NULL,NULL,"transactions",COMMAND_GROUP_TRANSACTIONS,WATCH_History,0,WATCH_Tips,0,watchCommand,-2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_FAST|CMD_NO_MULTI|CMD_ALLOW_BUSY,ACL_CATEGORY_TRANSACTION,WATCH_Keyspecs,1,NULL,1),.args=WATCH_Args},
{0}
};
1 change: 1 addition & 0 deletions src/commands/multi.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"LOADING",
"STALE",
"FAST",
"NO_MULTI",
"ALLOW_BUSY"
],
"acl_categories": [
Expand Down
1 change: 1 addition & 0 deletions src/commands/watch.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"LOADING",
"STALE",
"FAST",
"NO_MULTI",
"ALLOW_BUSY"
],
"acl_categories": [
Expand Down
9 changes: 0 additions & 9 deletions src/multi.c
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,7 @@ void flagTransaction(client *c) {
}

void multiCommand(client *c) {
if (c->flag.multi) {
addReplyError(c, "MULTI calls can not be nested");
return;
}
c->flag.multi = 1;

addReply(c, shared.ok);
}

Expand Down Expand Up @@ -459,10 +454,6 @@ void touchAllWatchedKeysInDb(serverDb *emptied, serverDb *replaced_with) {
void watchCommand(client *c) {
int j;

if (c->flag.multi) {
addReplyError(c, "WATCH inside MULTI is not allowed");
return;
}
/* No point in watching if the client is already dirty. */
if (c->flag.dirty_cas) {
addReply(c, shared.ok);
Expand Down
16 changes: 6 additions & 10 deletions tests/unit/multi.tcl
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,10 @@ start_server {tags {"multi"}} {
} {QUEUED OK {a b c}}

test {Nested MULTI are not allowed} {
set err {}
r multi
catch {[r multi]} err
r exec
set _ $err
} {*ERR MULTI*}
assert_error "ERR*" {r multi}
assert_error "EXECABORT*" {r exec}
}

test {MULTI where commands alter argc/argv} {
r sadd myset a
Expand All @@ -49,12 +47,10 @@ start_server {tags {"multi"}} {
} {a 0}

test {WATCH inside MULTI is not allowed} {
set err {}
r multi
catch {[r watch x]} err
r exec
set _ $err
} {*ERR WATCH*}
assert_error "ERR*" {r watch}
assert_error "EXECABORT*" {r exec}
}

test {EXEC fails if there are errors while queueing commands #1} {
r del foo1{t} foo2{t}
Expand Down

0 comments on commit 6bf1d02

Please sign in to comment.