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

Refactor the async server call context #1407

Merged
merged 1 commit into from
May 17, 2022

Conversation

glbrntt
Copy link
Collaborator

@glbrntt glbrntt commented May 16, 2022

Motivation:

The async call context provided to async server methods provides an API
to access request headers and the logger as well as modify the response
headers, trailers and user info.

This is currently implemented as a class with its mutable state
protected by a lock. This works fine but is racy: the response headers
can be updated after writing the first message and still be sent before
that message (as writing the message requires executing onto the event
loop).

Modifications:

  • Turn the context into a struct and break it into request and
    response components.
  • Request components are immutable.
  • Response components are mutable via async calls which execute onto
    the underlying event loop.
  • This introduces allocations as the work is submitted onto the
    event loop, however setting headers is not a frequent operation and is
    worth the trade off for a less surprising API.
  • Trap when headers/trailers are set after they have been sent
    (this is an assertion failre).

Result:

  • Async server context has a less surprising API
  • Setting headers/trailers is not racy

Motivation:

The async call context provided to async server methods provides an API
to access request headers and the logger as well as modify the response
headers, trailers and user info.

This is currently implemented as a class with its mutable state
protected by a lock. This works fine but is racy: the response headers
can be updated after writing the first message and still be sent before
that message (as writing the message requires executing onto the event
loop).

Modifications:

- Turn the context into a `struct` and break it into request and
  response components.
- Request components are immutable (or, more correctly, mutating them
  has no impact on the underlying RPC).
- Response components are mutable via `async` calls which execute onto
  the underlying event loop.
- This introduces allocations as the work is submitted onto the
  event loop, however setting headers is not a frequent operation and is
  worth the trade off for a less surprising API.
- Trap when headers/trailers are set after they have been sent
  (this is an assertion failre).

Result:

- Async server context has a less surprising API
- Setting headers/trailers is not racy
@glbrntt glbrntt requested a review from FranzBusch May 16, 2022 13:56
Comment on lines -233 to -234
func testResponseHeadersDroppedIfSetAfterFirstResponse() async throws {
throw XCTSkip("Setting metadata is racy. This test will not reliably pass until that is fixed.")
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This was removed because it no longer applies: we now trap (on debug builds) if headers are set after writing the first response

Comment on lines +73 to +78
/// The request headers received from the client at the start of the RPC.
public var headers: HPACKHeaders

/// A logger.
public var logger: Logger

Copy link
Collaborator

Choose a reason for hiding this comment

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

Should we expose the setters publicly here as well?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

There's no harm in having them: the caller would have to take a copy of Request to mutate these anyway.

Comment on lines +94 to +103
public func setHeaders(_ headers: HPACKHeaders) async throws {
try await self.contextProvider.setResponseHeaders(headers)
}

/// Set the metadata to return at the end of the RPC.
///
/// If this is required it must be updated before returning from the handler.
public func setTrailers(_ trailers: HPACKHeaders) async throws {
try await self.contextProvider.setResponseTrailers(trailers)
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

The new API only allows to set headers/trailers. Should we expose APIs that allow to mutate the currently set headers?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good question. I thought about this a bit and I don't think we should provide it.

They are empty by default and the caller knows what the value is: it can only have been set by them so I'm not sure it's actually useful to have (any update could be achieved by just setting the value a bit later).

@glbrntt glbrntt requested a review from FranzBusch May 17, 2022 09:45
Copy link
Collaborator

@FranzBusch FranzBusch left a comment

Choose a reason for hiding this comment

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

LGTM. Nice work!

@glbrntt glbrntt merged commit 981e6cd into grpc:1.7.1-async-await May 17, 2022
@glbrntt glbrntt deleted the gb-set-response-headers branch May 17, 2022 09:57
@glbrntt glbrntt added the 🆕 semver/minor Adds new public API. label Jun 13, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🆕 semver/minor Adds new public API.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants