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

Some cleanup, mostly in the HTTP request representations #211

Merged
merged 3 commits into from
Jan 31, 2024

Conversation

jhump
Copy link
Member

@jhump jhump commented Jan 30, 2024

This PR contains a few changes that try to improve parts of the code.

  1. The first commit is the biggest chunk, which clarifies the representation of HTTP requests. Previously the HTTPRequest class might have a request message. This message was really required for unary operations (and, if absent, would be treated as empty request) and ignored for stream operations. So now the type is split into two: a base HTTPRequest which has no request message, and a UnaryHTTPRequest which has a non-optional message type. Also, since everything in the framework works with Buffer for messages, this changes the type of the message from ByteArray to Buffer.
  2. One of the changes in the second commit was just renaming some variables/parameters in the compression stuff to (IMO) make it a little easier to read. (I was in this part of the code because an early suspicion with one of the streaming errors in the conformance tests was in the gzip implementation, since it seemed to only happen for gzip'ed requests.)
  3. Another small change in the second commit was me trying to make Envelope.pack a little more DRY. Though, looking at it again, it doesn't really reduce the total number of lines of code and is perhaps too Go-like and not very idiomatic for Kotlin... Let me know what you think. I can easily back it out.
  4. The final change (also in the second commit) is really a bug fix. I didn't include it in Fix problems in streams that were identified by conformance tests #210 since it wasn't actually causing any issues in the conformance tests -- at least not yet (we're currently adding some test cases that would tickle it though). When a gRPC operation completes, this was treating missing trailers as a successful RPC, as if the trailers were present and indicated a status of "ok". But that is not correct -- missing trailers in the gRPC protocol means something is definitely wrong (even if it's a unary operation that includes the singular response message). So now the client will consider this case to be an RPC error.

Copy link
Contributor

@pkwarren pkwarren left a comment

Choose a reason for hiding this comment

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

Changes look good.

One reason we might want to keep ByteArray (or switch to okio ByteString) is that it makes requests immutable. By switching to Buffer, it is possible (though unlikely) for someone to mutate it.

val message: Buffer,
// HTTP method to use with the request.
// Almost always POST, but side effect free unary RPCs may be made with GET.
httpMethod: String = HTTPMethod.POST,
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we remove httpMethod from the base HTTPRequest class now since it is only used for unary calls?

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

@@ -46,16 +47,10 @@ internal class GRPCInterceptor(
requestHeaders[GRPC_ACCEPT_ENCODING] = clientConfig.compressionPools()
.map { compressionPool -> compressionPool.name() }
}
val requestMessage = Buffer().use { buffer ->
if (request.message != null) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This is nice that we don't have to do this any more (since unary requests always have to have messages).

@jhump
Copy link
Member Author

jhump commented Jan 30, 2024

One reason we might want to keep ByteArray (or switch to okio ByteString) is that it makes requests immutable.

While that is true, there's no cheap way to convert between a Buffer and a ByteString or ByteArray. Once the data is in a Buffer, it's most efficient to keep it that way:
https://stackoverflow.com/questions/54532432/efficient-okio-source-backed-by-an-already-allocated-bytestring
(Reply is from okio author, Jesse Wilson.)

So by having a different intermediate representation, we have to copy at every step. There's actually a bit more copying we're doing that we could improve/eliminate (such as in the way readStream works in streaming client but also in the way the enveloping works, when it's time to strip the envelope from an uncompressed payload in the stream). And keeping everything a buffer is actually more efficient. In fact, in some ways, keeping it mutable may be desirable -- like the case of stripping the prefix from an envelope, where can just read the 5-byte prefix and then keep the rest of the buffer as the data instead of having to make any copies.

So I guess it's a tradeoff -- if we want something immutable, we'll have to pay for it with copies at each step of the protocol transformation.

Base automatically changed from jh/fix-problems-in-streams to main January 31, 2024 04:14
@jhump jhump merged commit 3204d07 into main Jan 31, 2024
7 checks passed
@jhump jhump deleted the jh/minor-cleanup branch January 31, 2024 12:38
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.

2 participants