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

Create a custom thrift protocol to ensure client/server compatibility #5398

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

DomGarguilo
Copy link
Member

Addresses #5390

@DomGarguilo DomGarguilo added this to the 4.0.0 milestone Mar 11, 2025
@DomGarguilo DomGarguilo requested a review from ctubbsii March 11, 2025 20:06
@DomGarguilo DomGarguilo self-assigned this Mar 11, 2025
@DomGarguilo
Copy link
Member Author

Created this in draft since, for now, it only addresses the task outlined in the ticket:

create a custom protocol with the versioning information stored in a header and checks on the server side to validate it

I'm not sure if any of the other proposed tasks should be added here or as follow-on PRs.

public static class AccumuloProtocol extends TCompactProtocol {

private static final int MAGIC_NUMBER = 0x41434355; // "ACCU" in ASCII
private static final byte PROTOCOL_VERSION = 1;
Copy link
Contributor

Choose a reason for hiding this comment

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

Should probably add a comment to AccumuloDataVersion.CURRENT_VERSION that references this and mentions that when changing CURRENT_VERSION may also need to change this.

Copy link
Member

Choose a reason for hiding this comment

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

I was wondering about that... whether the protocol version should be independent, so we can version the serialization format of the protocol header.

We will probably want to include the AccumuloDataVersion.CURRENT_VERSION in the protocol header directly, though. We could also include the actual Accumulo release version in here, too (major.minor.patch; e.g. 4.0.0). I think to verify, we'd probably want to ensure the same major and same minor version, if we want to be even more restrictive than the data version. We really shouldn't have Accumulo servers or clients/servers talking across major.minor versions (different patch releases are okay, and is important for rolling upgrades).

Copy link
Member Author

Choose a reason for hiding this comment

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

I left it separate for now just in case we wanted to have multiple protocol versions able to talk to each other but am not sure about that.

The data version and protocol version definitely seem related but I'm not sure how closely tied we want to have those versions.

try {
this.writeHeader();
} catch (TException e) {
if (scope != null) {
Copy link
Contributor

Choose a reason for hiding this comment

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

span has a recordException message, but not sure if that should be used here. I have seen it but have not looked into it in detail. Might be worth looking into.

}
}

super.writeMessageBegin(message);
Copy link
Contributor

Choose a reason for hiding this comment

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

Seems like this should be inside the try block above. Thinking if this message throws exception that writeMessageEnd will never be called. If it should be in the try block then maybe the existing code in 2.1 has a bug.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm not sure I understand. Putting super.writeMessageBegin(message); in the try-catch would close the span and scope if an exception is thrown from it but I'm not sure that it would make writeMessageEnd() in either case, right? Maybe im missing something here.

Copy link
Contributor

@keith-turner keith-turner Mar 12, 2025

Choose a reason for hiding this comment

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

If super.writeMessageBegin(message) throws an exception, then we can probably assume that writeMessageEnd() will never be called. So if this happens will nothing close the span and scope?

Copy link
Member

@ctubbsii ctubbsii left a comment

Choose a reason for hiding this comment

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

I'm not sure if we actually need to distinguish between client and server for the spans.

Comment on lines +121 to +124
if (this.isClient && scope != null) {
scope.close();
span.end();
}
Copy link
Member

Choose a reason for hiding this comment

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

I don't think we care if it's a client. If a scope was opened, we'd want to close it.


@Override
public void writeMessageBegin(TMessage message) throws TException {
if (this.isClient) {
Copy link
Member

Choose a reason for hiding this comment

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

The code this replaces used the same protocol for both clients and servers. I'm not 100% sure when writeMessageBegin gets called, but I think it's probably fine to do it on every message, and not discriminate based on client and server.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yea I am kinda unsure about this point (not sure whats correct/better).

If I understand things correctly here, the thrift messages flow in both directions between "client" and "server" over the same connection. So as long as we validate compatibility when the client sends a message to the server, than we can avoid doing the extra work of adding (and therefore validating) the headers when the server responds to the client.

I think things would work just fine if we did not distinguish between client and server in this context, we would just be doing more work than necessary which might be unoptimal (which seems like it matters in this context).

I'm also thinking though there might be the benefit of always writing and validating these header just so we are certain that there are no edge cases or scenarios that I'm not thinking about where a server responds to a client over a connection that has not been validated yet. I don't know if this would ever happen but it seems like a trade off between absolute certainty that we have valid comms both ways and optimizing the amount of work done (again might be worth the optimization in this case, not sure).

Definitely would appreciate feedback on this line of thinking and corrections regarding any incorrect assumptions ive made above.

Copy link
Contributor

Choose a reason for hiding this comment

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

If I understand things correctly here, the thrift messages flow in both directions between "client" and "server" over the same connection. So as long as we validate compatibility when the client sends a message to the server, than we can avoid doing the extra work of adding (and therefore validating) the headers when the server responds to the client.

That sounds good to me. It seems like the changes in the code to support this are minimal, do you know if you have caught everything in the code for covering this functionality?

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