Skip to content
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

chore(reply_builder): add dcheck that each command invocation has rep… #2067

Merged
merged 8 commits into from
Oct 26, 2023

Conversation

kostasrim
Copy link
Contributor

Some of the replication tests fail, and I suspect it's our little bug :) I am investigating and will update

@kostasrim kostasrim force-pushed the add_dcheck_for_replies branch from 222bfee to ff10ea7 Compare October 24, 2023 17:47
@@ -134,6 +137,8 @@ class SinkReplyBuilder {

// Similarly to batch mode but is controlled by at operation level.
bool should_aggregate_ : 1;
bool has_replied_ : 1;
bool has_batch_replied_ : 1;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

accidental will remove

@kostasrim kostasrim changed the title chore(reply_builder): add dcheck that each command invocation has rep… (WIP) chore(reply_builder): add dcheck that each command invocation has rep… Oct 24, 2023
@romange
Copy link
Collaborator

romange commented Oct 24, 2023

Some of the tests failing are around memcache flow that have a dedicated reply builder

Comment on lines 1068 to 1077

cntx->reply_builder()->ExpectReply();
try {
cid->Invoke(tail_args, cntx);
} catch (std::exception& e) {
LOG(ERROR) << "Internal error, system probably unstable " << e.what();
return false;
}
DCHECK(cntx->reply_builder()->HasReplied());

Copy link
Contributor

@dranikpg dranikpg Oct 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be my solution suggestion, not sure if its simpler 🙂

#ifdef NDEBUG
CapturingReplyBuilder crb{ReplyMode::ALL};
auto* old = cntx->Inject(&crb);
#endif

*invoke*

#ifdef NDEBUG
auto reply = crb.Take();
DCHECK(reply.index() > 0);
CRB::Apply(std::move(reply), (*cntx).operator->());
cntx->Inject(old);
#endif

Copy link
Contributor

@dranikpg dranikpg Oct 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have some of the mechanics as a convenient RAII in 1924, you could steal it from there if you choose this approach, so all the code doesn't pollute this small function

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would not work with memcached, because the Apply function accepts a RedisReplyBuilder. Moreover, for some reason it made some of the tests stack.

However, I really like this idea, IMO, it's cleaner. What I will try on a seprate PR (because I don't want to block us) is:

  1. Overload Apply to work with MCBuilder (or just make it accept a SinkReplyBuilder).
  2. Figure out what cause the hangs in the tests.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest just doing this for non memcached commands because CRB has no support for storing mc-like replies

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other way round its likely not worth the effort

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest just doing this for non memcached commands because CRB has no support for storing mc-like replies

So just skip for the memcached commands? I can do this by dyn_cast and checking if the reply builder is MCBuilder. I will try this out

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But second thoughts we would also miss the dcheck for memcached commands which I am not really happy about... @romange any opinion on this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dranikpg I applied your comments, I don't want to delay this any longer. Took a little bit more time, because I had to add a few exceptions but it passes locally

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not care about missing memcache - it has a small surface and it does not change a lot.

Copy link
Collaborator

@romange romange Oct 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i saw now the previous commits. I prefer the boolean option

@kostasrim kostasrim changed the title (WIP) chore(reply_builder): add dcheck that each command invocation has rep… chore(reply_builder): add dcheck that each command invocation has rep… Oct 25, 2023
@kostasrim kostasrim self-assigned this Oct 25, 2023
@kostasrim kostasrim requested a review from dranikpg October 25, 2023 16:01
dranikpg
dranikpg previously approved these changes Oct 25, 2023
Copy link
Contributor

@dranikpg dranikpg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just merge it, let's not spend this much time discussing such a small issue I agree

Comment on lines 1055 to 1057
const bool is_repl_conf = cid->name() == "REPLCONF"; \
const bool is_dfly = cid->name() == "DFLY"; \
const bool is_exec = cid->name() == "EXEC"; \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bool ignore = set{"REPLCONF", "DFLY", "EXEC"}.count(cid->name()); 😭

Comment on lines 1061 to 1064
absl::Cleanup clean = [cntx, old]() { \
if (old) \
cntx->Inject(old); \
}; \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what I initially thought was this

struct ReplyGuard {} // do all the stuff in contructor and destructor

Invoke() {
#ifdef NDEBUG
ReplyGuard guard;
#endif
...
}

@@ -1047,6 +1047,39 @@ void Service::DispatchCommand(CmdArgList args, facade::ConnectionContext* cntx)
}
}

#ifndef NDEBUG

#define ENABLE_DCHECK_REPLIED \
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it the only way to do it? by injecting a reply builder and bunch of preprocesor code?

Copy link
Contributor Author

@kostasrim kostasrim Oct 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Injecting the CapturingReplyBuilder is clean enough and I cleaned up the preprocessor code. Do you have any better suggestions?

if (old) {
auto reply = crb.Take();
DCHECK(reply.index() > 0);
CapturingReplyBuilder::Apply(std::move(reply), old);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

much nicer! My question is - could it be that injecting CapturingReplyBuilder would actually change the debug version significantly from the release in terms of I/O pattern?

Copy link
Contributor

@dranikpg dranikpg Oct 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

somewhat, now replies are sent only once the command handler finished executing

Copy link
Contributor Author

@kostasrim kostasrim Oct 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does delay the replies after the invocation of the command. So yes, it does alter the behavior. Originally, I had it implemented differently (see the early commits) but the CapturingReplyBuilder seems a clearer approach.

would actually change the debug version significantly from the release in terms of I/O pattern?

I think it also changes the functionality, as I described above which I agree it is not great but in this context maybe it's ok if you take into account that the release version is also different because of different optimizations.

There is another solution in the previous commits (which can be cleaned up as well) but this also pollutes the RedisReplyBuilder (but it doesn't delay the replies). What's your take?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have not see your solution but is not it just about setting a boolean in a few low-level functions?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i mean in replybuilder ? I am ok with this solution but I do preference with poluting replybuilder if it is simple and efficient.

@kostasrim kostasrim requested review from romange and dranikpg October 26, 2023 07:52
@@ -98,6 +99,14 @@ void SinkReplyBuilder::SendRaw(std::string_view raw) {
Send(&v, 1);
}

void SinkReplyBuilder::ExpectReply() {
has_replied_ = true;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does it mean?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it should be false hold

.contains(cid_name);
const bool should_dcheck = !is_one_of && !is_script;
if (should_dcheck) {
builder->ExpectReply();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not understand this logic.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should have been false. Basically, it sets that we expect the reply on each Invoke which internally sets the has_replied_ to false. If the reply builder is used to reply, then this is set to true, which we DCHECK on destructor

@kostasrim kostasrim requested a review from romange October 26, 2023 10:10
@romange romange merged commit b655fc7 into main Oct 26, 2023
10 checks passed
@romange romange deleted the add_dcheck_for_replies branch October 26, 2023 16:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants