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

Move Endpoint selection to the beginning of client execution #1917

Merged
merged 11 commits into from Jul 25, 2019

Conversation

trustin
Copy link
Member

@trustin trustin commented Jul 19, 2019

Motivation:

ClientRequestContext.endpoint() currently return an Endpoint which
may be 1) a host:port endpoint or 2) a group endpoint. Therefore, a
client decorator does not know which host the request will be sent to
exactly; it is determined after all decorators are evaluated. This makes
it hard for client decorators to do the following:

  • Injecting a header value derived from the target host name.
  • Accounting for the number of requests sent to each host.

Modifications:

  • Split the initialization of DefaultClientRequestContext into two:
    1. constructor and 2) init() method.
  • Determine the target host at the context initialization phase.
  • Make ClientRequestContext.endpoint() nullable, so that the
    decorators can handle the case where endpoint selection has failed.
  • Add ClientRequestContext.endpointSelector().
  • Add test cases for HTTP, gRPC and Thrift.
  • Miscellaneous:
    • Add EndpointGroup.empty()

Result:

  • Client decorators now knows the exact target host of the request.
  • We can make Endpoint always refer to a host:port, which will greatly
    simplify both users' code and ours in the future.
  • We now have a proper place (DefaultClientRequestContext.init()) that
    can be used for EventLoop allocation and connection pooling in the
    future.

It currently has the following issues:

  • When EndpointSelector.select() raises an exception, the construction of ClientRequestContext fails, which means you have no access to its RequestLog. We could make ClientRequestContext available by deferring select() until ClientRequestContext is constructed, but the decorators will still not be able to access to the request.
    • The same problem will arise when we move the connection acquisition logic (connection pooling) to the beginning of request execution. The decorators will never see the connection failure.

@codecov
Copy link

codecov bot commented Jul 19, 2019

Codecov Report

Merging #1917 into master will decrease coverage by 0.01%.
The diff coverage is 77.77%.

Impacted file tree graph

@@             Coverage Diff              @@
##             master    #1917      +/-   ##
============================================
- Coverage     73.28%   73.26%   -0.02%     
- Complexity     8987     9014      +27     
============================================
  Files           794      798       +4     
  Lines         35189    35236      +47     
  Branches       4324     4333       +9     
============================================
+ Hits          25788    25816      +28     
- Misses         7193     7194       +1     
- Partials       2208     2226      +18
Impacted Files Coverage Δ Complexity Δ
.../linecorp/armeria/client/ClientRequestContext.java 94.11% <ø> (ø) 8 <0> (ø) ⬇️
...main/java/com/linecorp/armeria/client/Clients.java 73.01% <ø> (ø) 23 <0> (ø) ⬇️
...va/com/linecorp/armeria/common/RequestContext.java 59.37% <ø> (ø) 23 <0> (ø) ⬇️
.../linecorp/armeria/common/StringValueConverter.java 26.19% <0%> (-2.02%) 9 <0> (ø)
...rp/armeria/client/ClientRequestContextWrapper.java 0% <0%> (ø) 0 <0> (ø) ⬇️
...com/linecorp/armeria/internal/grpc/GrpcStatus.java 66.17% <0%> (-1.48%) 25 <0> (ø)
...a/com/linecorp/armeria/common/HttpHeadersBase.java 73.54% <0%> (ø) 150 <0> (ø) ⬇️
...armeria/common/logback/RequestContextExporter.java 64.57% <0%> (-0.59%) 54 <0> (ø)
...ent/circuitbreaker/KeyedCircuitBreakerMapping.java 84% <0%> (-16%) 5 <0> (ø)
...m/linecorp/armeria/client/grpc/ArmeriaChannel.java 97.5% <100%> (ø) 8 <0> (ø) ⬇️
... and 40 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 3ba74c5...38b328e. Read the comment docs.

@trustin
Copy link
Member Author

trustin commented Jul 22, 2019

@anuraaga @ikhoon @minwoox Thoughts?

@anuraaga
Copy link
Collaborator

Does it work for the failure case to set the Endpoint of the request to a FailedEndpoint or something and continue to execute the request, throwing when about to connect to the FailedEndpoint?

@trustin
Copy link
Member Author

trustin commented Jul 22, 2019

Does it work for the failure case to set the Endpoint of the request to a FailedEndpoint or something and continue to execute the request, throwing when about to connect to the FailedEndpoint?

That sounds good to me. We could also do the same when we move the connection pooling to the beginning. Sounds good?

@anuraaga
Copy link
Collaborator

Yup :)

@trustin trustin force-pushed the select_endpoint_early branch 5 times, most recently from 97d0d24 to a85dad1 Compare July 23, 2019 05:33
@trustin
Copy link
Member Author

trustin commented Jul 23, 2019

I found it's weird to use Endpoint to represent different states that are not very similar to each other - group, host, failure, so I just made ClientRequestContext.endpoint() nullable as advised by @minwoox. What do you think? If that's fine, let me add some test cases and remove WIP.

@trustin
Copy link
Member Author

trustin commented Jul 23, 2019

That being said, I'd eventually like to make Endpoint represent only a host:port, i.e. no group, because EndpointSelector can be used instead. (Not in this pull request but later.)

@anuraaga
Copy link
Collaborator

Yeah that makes sense

@trustin trustin force-pushed the select_endpoint_early branch 4 times, most recently from aab1c37 to 071c4c1 Compare July 24, 2019 03:22
@trustin trustin marked this pull request as ready for review July 24, 2019 03:24
Motivation:

`ClientRequestContext.endpoint()` currently return an `Endpoint` which
may be 1) a host:port endpoint or 2) a group endpoint. Therefore, a
client decorator does not know which host the request will be sent to
exactly; it is determined after all decorators are evaluated. This makes
it hard for client decorators to do the following:

- Injecting a header value derived from the target host name.
- Accounting for the number of requests sent to each host.

Modifications:

- Split the initialization of `DefaultClientRequestContext` into two:
  1) constructor and 2) `init()` method.
- Determine the target host at the context initialization phase.
- Make `ClientRequestContext.endpoint()` nullable, so that the
  decorators can handle the case where endpoint selection has failed.
- Add `ClientRequestContext.endpointSelector()`.
- Add test cases for HTTP, gRPC and Thrift.
- Miscellaneous:
  - Add `EndpointGroup.empty()`.

Result:

- Client decorators now knows the exact target host of the request.
- We can make `Endpoint` always refer to a host:port, which will greatly
  simplify both users' code and ours in the future.
- We now have a proper place (`DefaultClientRequestContext.init()`) that
  can be used for `EventLoop` allocation and connection pooling in the
  future.
Copy link
Member

@minwoox minwoox left a comment

Choose a reason for hiding this comment

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

Great job. 👍

/**
* Creates a new {@link ClientRequestContext} whose properties and {@link Attribute}s are copied from this
* {@link ClientRequestContext}, except having a different {@link Request} and its own {@link RequestLog}.
*/
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for adding missed documentation. 👍

@trustin
Copy link
Member Author

trustin commented Jul 25, 2019

@anuraaga I also hardened the thread safety of ctx.additional{Request,Response}{Headers,Trailers}() using CAS. Let me know if you have a concern about this, although I think it will not be that expensive with the level of contention we expect.

Copy link
Contributor

@ikhoon ikhoon left a comment

Choose a reason for hiding this comment

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

Thank you for awesome job!

@trustin trustin merged commit ef0fec2 into line:master Jul 25, 2019
@trustin trustin deleted the select_endpoint_early branch July 25, 2019 09:08
@trustin trustin added this to the 0.89.0 milestone Jul 26, 2019
fmguerreiro pushed a commit to fmguerreiro/armeria that referenced this pull request Sep 19, 2020
Motivation:

`ClientRequestContext.endpoint()` currently return an `Endpoint` which
may be 1) a host:port endpoint or 2) a group endpoint. Therefore, a
client decorator does not know which host the request will be sent to
exactly; it is determined after all decorators are evaluated. This makes
it hard for client decorators to do the following:

- Injecting a header value derived from the target host name.
- Accounting for the number of requests sent to each host.

Modifications:

- Split the initialization of `DefaultClientRequestContext` into two:
  1) constructor and 2) `init()` method.
- Determine the target host at the context initialization phase.
- Make `ClientRequestContext.endpoint()` nullable, so that the
  decorators can handle the case where endpoint selection has failed.
- Add `ClientRequestContext.endpointSelector()`.
- Add test cases for HTTP, gRPC and Thrift.
- Miscellaneous:
  - Add `EndpointGroup.empty()`.

Result:

- Client decorators now knows the exact target host of the request.
- We can make `Endpoint` always refer to a host:port, which will greatly
  simplify both users' code and ours in the future.
- We now have a proper place (`DefaultClientRequestContext.init()`) that
  can be used for `EventLoop` allocation and connection pooling in the
  future.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants