New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Don't write replies if close the client ASAP #7202
Conversation
Good fix @ShooterIT, thanks! However for the second part, that is, removing it from the pending writes list, what about that instead?
|
Hi @antirez there must be some data in output buffer if the client output buffer size is more than My test is as follows, I set small size of "normal" in "client-output-buffer-limit", and I generated a big list that has 8717 items. The connection was closed if I executed
redis log
I think it is unnecessary to send the incomplete reply to client, and we need to call |
I've rebased this PR and run the tests. i see it fails in the second test in @madolson looking at that test, i suppose it used to work due to implicit reconnect of the tests, is that intended:
@ShooterIT can you look into it and check how it affected that test? |
Firstly, I'm sorry i didn't read Salvatore's suggestion carefully. I think i should merge his suggestion, because there are also some other clients set CLIENT_CLOSE_ASAP flag For test error, I don't know much actually, maybe you are right i think |
But I think we should also remove the client from the pending writes list, because redis also will write replies to client when enable threaded I/O if only change the following.
|
Maybe we also check in
|
060987f
to
e8fc322
Compare
hi @oranagra As follow, this change will make tests passed
|
Please also have a look if you have time @madolson |
I think the problem is that for acls (and module acls) we should probably use the close after reply flag instead of the close async. The motivation here is that there were completed commands in the output we should return to the calling clients. So I think the test caught a good thing, I can make that fix. |
The test specifically is relying on that behavior, so I agree that we could try to catch the exception, but I think it's probably the wrong long term thing to do. |
@madolson so the next step is to keep the test as is, and change acl.c and module.c to use |
I think that is not enough, we can't write any thing clients if we set |
@ShooterIT you mean that |
@oranagra Yes.
If we the user we delete also is the user of current client, we kill current client async(L1) but we also wish we can write reply to it(L2). So I think it is not enough If we set CLIENT_CLOSE_AFTER_REPLY flag because we can't write any later. For tests in module api, @madolson has been said,
|
@ShooterIT if these are the only two problems (problematic corner cases), maybe we should just reorder things in these? The benefit of this PR is an immediate release of a possibly (probably) huge output buffer rather. |
@oranagra Yes, My motivation to do this PR is losing control of output buffer size. In my case, our operation engineers found used memory of redis was more than 15GB even more but max memory we set only is 10G. By For problems, maybe there are not only two, I do a try, just draft, maybe @madolson has a better idea.
|
@ShooterIT the solution you suggested last seems good. I'm also ok with the alternative of not adding a new flag, and instead change these two cases to reply first and act later: difdiff --git a/src/acl.c b/src/acl.c
index 3194feb5b..bd0affa31 100644
--- a/src/acl.c
+++ b/src/acl.c
@@ -278,7 +278,7 @@ void ACLFreeUserAndKillClients(user *u) {
* it in non authenticated mode. */
c->user = DefaultUser;
c->authenticated = 0;
- freeClientAsync(c);
+ c->flags |= CLIENT_CLOSE_AFTER_REPLY;
}
}
ACLFreeUser(u);
@@ -1673,6 +1673,16 @@ void aclCommand(client *c) {
}
}
+ /* we need to count and reply before deleting, since we may be deleting the current user */
+ for (int j = 2; j < c->argc; j++) {
+ sds username = c->argv[j]->ptr;
+ if (raxFind(Users,(unsigned char*)username,
+ sdslen(username)))
+ {
+ deleted++;
+ }
+ }
+ addReplyLongLong(c,deleted);
for (int j = 2; j < c->argc; j++) {
sds username = c->argv[j]->ptr;
user *u;
@@ -1681,10 +1691,8 @@ void aclCommand(client *c) {
(void**)&u))
{
ACLFreeUserAndKillClients(u);
- deleted++;
}
}
- addReplyLongLong(c,deleted);
} else if (!strcasecmp(sub,"getuser") && c->argc == 3) {
user *u = ACLGetUserByName(c->argv[2]->ptr,sdslen(c->argv[2]->ptr));
if (u == NULL) {
diff --git a/tests/modules/auth.c b/tests/modules/auth.c
index b13c10385..f769db876 100644
--- a/tests/modules/auth.c
+++ b/tests/modules/auth.c
@@ -15,6 +15,8 @@ int Auth_CreateModuleUser(RedisModuleCtx *ctx, RedisModuleString **argv, int arg
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
+ /* reply before closing since we might be deleting the current user. */
+ RedisModule_ReplyWithSimpleString(ctx, "OK");
if (global) {
RedisModule_FreeModuleUser(global);
}
@@ -24,7 +26,7 @@ int Auth_CreateModuleUser(RedisModuleCtx *ctx, RedisModuleString **argv, int arg
RedisModule_SetModuleUserACL(global, "allkeys");
RedisModule_SetModuleUserACL(global, "on");
- return RedisModule_ReplyWithSimpleString(ctx, "OK");
+ return REDISMODULE_OK;
}
int Auth_AuthModuleUser(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { i've tested both approaches (my diff above and what you posted in your last message) and they seem to work.
What do you think? |
e8fc322
to
93e4475
Compare
@oranagra I'm ok to use CLIENT_CLOSE_AFTER_COMMAND, actually i also wanted to use this name(maybe little long, but it is much clear).
Just like i said above, so we asked yangbodong22011 to add more test cases #7765. So I rebase to newest unstable branch, it failed on |
ok, so there are more places that first close a client and then reply to it. and it's good that we have coverage for some. and please add tests for:
sounds like a plan? |
Yes. Let me do it ASAP, this PR took too long time. |
yes. i really want to fix this (for fear of forgetting). |
93e4475
to
a8b77f3
Compare
Yes, too many conversations. I add two tests. Maybe i missed somethings before. Actually, these two problems influence each other :( |
@oranagra Because of code execution order, before this, we may firstly write to some replies to clients just when trigger writable event, but in fact, we may can’t send all replies to clients since OS socket buffer is limited. But this unexpected behavior makes some commands work well, for ACL subcommands, if the client deletes the using user, we need to send reply to client and close it, but before, we close the client firstly and write the reply to reply buffer secondly. We shouldn't do this despite it works well in most cases. We add a flag 'CLIENT_CLOSE_AFTER_COMMAND' to mark clients, this flag means we will close the client after executing commands and send all entire replies, so that we can write replies to reply buffer during executing commands, send replies to clients, and close them later. We also fix some implicit problems. If client output buffer limit is enforced in 'multi/exec', all commands will be executed completely in redis and clients will not read any reply instead of partial replies. Even more, if the client executes 'ACL deluser' the using user in 'multi/exec', it will not read the replies after 'ACL deluser' just like before executing 'client kill' itself in 'multi/exec'. |
Thanks for the summary (and all the work), will use that message when i squash merge it. |
Before this commit, we would have continued to add replies to the reply buffer even if client output buffer limit is reached, so the used memory would keep increasing over the configured limit. What's more, we shouldn’t write any reply to the client if it is set 'CLIENT_CLOSE_ASAP' flag because that doesn't conform to its definition and we will close all clients flagged with 'CLIENT_CLOSE_ASAP' in ‘beforeSleep’. Because of code execution order, before this, we may firstly write to part of the replies to the socket before disconnecting it, but in fact, we may can’t send the full replies to clients since OS socket buffer is limited. But this unexpected behavior makes some commands work well, for instance ACL DELUSER, if the client deletes the current user, we need to send reply to client and close the connection, but before, we close the client firstly and write the reply to reply buffer. secondly, we shouldn't do this despite the fact it works well in most cases. We add a flag 'CLIENT_CLOSE_AFTER_COMMAND' to mark clients, this flag means we will close the client after executing commands and send all entire replies, so that we can write replies to reply buffer during executing commands, send replies to clients, and close them later. We also fix some implicit problems. If client output buffer limit is enforced in 'multi/exec', all commands will be executed completely in redis and clients will not read any reply instead of partial replies. Even more, if the client executes 'ACL deluser' the using user in 'multi/exec', it will not read the replies after 'ACL deluser' just like before executing 'client kill' itself in 'multi/exec'. We added some tests for output buffer limit breach during multi-exec and using a pipeline of many small commands rather than one with big response. Co-authored-by: Oran Agra <oran@redislabs.com> (cherry picked from commit 57709c4)
Before this commit, we would have continued to add replies to the reply buffer even if client output buffer limit is reached, so the used memory would keep increasing over the configured limit. What's more, we shouldn’t write any reply to the client if it is set 'CLIENT_CLOSE_ASAP' flag because that doesn't conform to its definition and we will close all clients flagged with 'CLIENT_CLOSE_ASAP' in ‘beforeSleep’. Because of code execution order, before this, we may firstly write to part of the replies to the socket before disconnecting it, but in fact, we may can’t send the full replies to clients since OS socket buffer is limited. But this unexpected behavior makes some commands work well, for instance ACL DELUSER, if the client deletes the current user, we need to send reply to client and close the connection, but before, we close the client firstly and write the reply to reply buffer. secondly, we shouldn't do this despite the fact it works well in most cases. We add a flag 'CLIENT_CLOSE_AFTER_COMMAND' to mark clients, this flag means we will close the client after executing commands and send all entire replies, so that we can write replies to reply buffer during executing commands, send replies to clients, and close them later. We also fix some implicit problems. If client output buffer limit is enforced in 'multi/exec', all commands will be executed completely in redis and clients will not read any reply instead of partial replies. Even more, if the client executes 'ACL deluser' the using user in 'multi/exec', it will not read the replies after 'ACL deluser' just like before executing 'client kill' itself in 'multi/exec'. We added some tests for output buffer limit breach during multi-exec and using a pipeline of many small commands rather than one with big response. Co-authored-by: Oran Agra <oran@redislabs.com> (cherry picked from commit 57709c4)
Before this commit, we would have continued to add replies to the reply buffer even if client output buffer limit is reached, so the used memory would keep increasing over the configured limit. What's more, we shouldn’t write any reply to the client if it is set 'CLIENT_CLOSE_ASAP' flag because that doesn't conform to its definition and we will close all clients flagged with 'CLIENT_CLOSE_ASAP' in ‘beforeSleep’. Because of code execution order, before this, we may firstly write to part of the replies to the socket before disconnecting it, but in fact, we may can’t send the full replies to clients since OS socket buffer is limited. But this unexpected behavior makes some commands work well, for instance ACL DELUSER, if the client deletes the current user, we need to send reply to client and close the connection, but before, we close the client firstly and write the reply to reply buffer. secondly, we shouldn't do this despite the fact it works well in most cases. We add a flag 'CLIENT_CLOSE_AFTER_COMMAND' to mark clients, this flag means we will close the client after executing commands and send all entire replies, so that we can write replies to reply buffer during executing commands, send replies to clients, and close them later. We also fix some implicit problems. If client output buffer limit is enforced in 'multi/exec', all commands will be executed completely in redis and clients will not read any reply instead of partial replies. Even more, if the client executes 'ACL deluser' the using user in 'multi/exec', it will not read the replies after 'ACL deluser' just like before executing 'client kill' itself in 'multi/exec'. We added some tests for output buffer limit breach during multi-exec and using a pipeline of many small commands rather than one with big response. Co-authored-by: Oran Agra <oran@redislabs.com> (cherry picked from commit 57709c4)
Before this commit, we would have continued to add replies to the reply buffer even if client output buffer limit is reached, so the used memory would keep increasing over the configured limit. What's more, we shouldn’t write any reply to the client if it is set 'CLIENT_CLOSE_ASAP' flag because that doesn't conform to its definition and we will close all clients flagged with 'CLIENT_CLOSE_ASAP' in ‘beforeSleep’. Because of code execution order, before this, we may firstly write to part of the replies to the socket before disconnecting it, but in fact, we may can’t send the full replies to clients since OS socket buffer is limited. But this unexpected behavior makes some commands work well, for instance ACL DELUSER, if the client deletes the current user, we need to send reply to client and close the connection, but before, we close the client firstly and write the reply to reply buffer. secondly, we shouldn't do this despite the fact it works well in most cases. We add a flag 'CLIENT_CLOSE_AFTER_COMMAND' to mark clients, this flag means we will close the client after executing commands and send all entire replies, so that we can write replies to reply buffer during executing commands, send replies to clients, and close them later. We also fix some implicit problems. If client output buffer limit is enforced in 'multi/exec', all commands will be executed completely in redis and clients will not read any reply instead of partial replies. Even more, if the client executes 'ACL deluser' the using user in 'multi/exec', it will not read the replies after 'ACL deluser' just like before executing 'client kill' itself in 'multi/exec'. We added some tests for output buffer limit breach during multi-exec and using a pipeline of many small commands rather than one with big response. Co-authored-by: Oran Agra <oran@redislabs.com>
Before this commit, we would have continued to add replies to the reply buffer even if client output buffer limit is reached, so the used memory would keep increasing over the configured limit. What's more, we shouldn’t write any reply to the client if it is set 'CLIENT_CLOSE_ASAP' flag because that doesn't conform to its definition and we will close all clients flagged with 'CLIENT_CLOSE_ASAP' in ‘beforeSleep’. Because of code execution order, before this, we may firstly write to part of the replies to the socket before disconnecting it, but in fact, we may can’t send the full replies to clients since OS socket buffer is limited. But this unexpected behavior makes some commands work well, for instance ACL DELUSER, if the client deletes the current user, we need to send reply to client and close the connection, but before, we close the client firstly and write the reply to reply buffer. secondly, we shouldn't do this despite the fact it works well in most cases. We add a flag 'CLIENT_CLOSE_AFTER_COMMAND' to mark clients, this flag means we will close the client after executing commands and send all entire replies, so that we can write replies to reply buffer during executing commands, send replies to clients, and close them later. We also fix some implicit problems. If client output buffer limit is enforced in 'multi/exec', all commands will be executed completely in redis and clients will not read any reply instead of partial replies. Even more, if the client executes 'ACL deluser' the using user in 'multi/exec', it will not read the replies after 'ACL deluser' just like before executing 'client kill' itself in 'multi/exec'. We added some tests for output buffer limit breach during multi-exec and using a pipeline of many small commands rather than one with big response. Co-authored-by: Oran Agra <oran@redislabs.com> (cherry picked from commit 57709c4)
Module blocked clients cache the response in a temporary client, the reply list in this client would be affected by the recent fix in redis#7202, but when the reply is later copied into the real client, it would have bypassed all the checks for output buffer limit, which would have resulted in both: responding with a partial response to the client, and also not disconnecting it at all.
Module blocked clients cache the response in a temporary client, the reply list in this client would be affected by the recent fix in #7202, but when the reply is later copied into the real client, it would have bypassed all the checks for output buffer limit, which would have resulted in both: responding with a partial response to the client, and also not disconnecting it at all.
Module blocked clients cache the response in a temporary client, the reply list in this client would be affected by the recent fix in redis#7202, but when the reply is later copied into the real client, it would have bypassed all the checks for output buffer limit, which would have resulted in both: responding with a partial response to the client, and also not disconnecting it at all. (cherry picked from commit 48efc25)
Module blocked clients cache the response in a temporary client, the reply list in this client would be affected by the recent fix in #7202, but when the reply is later copied into the real client, it would have bypassed all the checks for output buffer limit, which would have resulted in both: responding with a partial response to the client, and also not disconnecting it at all. (cherry picked from commit 48efc25)
Module blocked clients cache the response in a temporary client, the reply list in this client would be affected by the recent fix in redis#7202, but when the reply is later copied into the real client, it would have bypassed all the checks for output buffer limit, which would have resulted in both: responding with a partial response to the client, and also not disconnecting it at all.
I config set small memory sizes for "normal" of
client-output-buffer-limit
to limit the memory clients used, but I actually found the memory kept increasing when users executedsmembers
command on a big key.I also could read some replies exactly when I send redis command by “telnet” even if the client output buffer size is more than the part of
client-output-buffer-limit
I set.I consider we shouldn't write replies to the client output buffer if we want to close it asap, that will not use unnecessary memory. And we also should remove the client from the list of pending writes, so that we won't
write(2)
replies to the client.