Skip to content

Conversation

@gjcairo
Copy link
Collaborator

@gjcairo gjcairo commented Nov 29, 2023

Motivation

Clients are the highest level API to make requests to a gRPC server.

Modifications

Add a GRPCClient

Result

We now have a high level client that can be used by the generated stubs to start RPCs to gRPC servers.

/// RPCs are intercepted in the order that interceptors are added. That is, a request sent from the client to
/// the server will first be intercepted by the first added interceptor followed by the second, and so on.
/// For responses from the server, they'll be applied in the opposite order.
public var interceptors: Interceptors {
Copy link
Collaborator

Choose a reason for hiding this comment

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

What happens if I change interceptors while a request is currently making its way through the interceptor pipeline?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You can't modify interceptors once the Client is running (and you cannot execute RPCs unless the Client is running either) so this cannot happen.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Nothing. There's a state check and we grab the array of interceptors before executing the request.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I see but I also find it a bit weird. Like you are calling interceptors = [] after calling run and that call just gets dropped and nothing happens. Why don't we just make them a part of the init and not allow modifying them afterwards?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yeah, I agree, it is a bit weird.

Why don't we just make them a part of the init and not allow modifying them afterwards?

We can do that.

}

/// Underlying storage for the client.
private struct Storage {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is the opposite of what we normally do. We put the storage into the associated values of the State enum. The advantage of that is that once we transition to stopped we free all of the retained storage.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Not always. We tend to do that when the state management and dependencies are complicated. That's not the case here.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Sure but I still think there is benefits to that approach like freeing up the resources as soon as possible. Right now we are going to retain the interceptors for longer than we need.

/// The transport will be closed: this means that it will be given enough time to wait for in-flight RPCs to
/// finish executing, but no new RPCs will be accepted.
/// You can cancel the task executing ``run()`` if you want to immediately stop all work.
public func close() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

IMO close is the wrong name here and we should follow the lifecycle naming of triggerGracefulShutdown

Copy link
Collaborator

Choose a reason for hiding this comment

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

Why do you think it's the wrong name?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Hm if we follow that convention, shouldn't we also rename stopListening() in GPRCServer to triggerGracefulShutdown too?
cc @glbrntt

Copy link
Collaborator

Choose a reason for hiding this comment

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

Why do you think it's the wrong name?

At least coming from a NIO world or an FD world close means "close right now". However, from the documentation here it sounds way more like a graceful shutdown than an immediate close. Furthermore, we are kinda trying to establish the pattern of gracefulShutdown with ServiceLifecycle so I think aligning on a common name is beneficial for the ecosystem.

Copy link
Collaborator

Choose a reason for hiding this comment

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

At least coming from a NIO world or an FD world close means "close right now".

I don't think either of these are correct. In NIO close() just sends a signal down the channel pipeline, a channel handlers can do whatever they want with that signal, including letting in flight requests continue. Closing an FD won't close it right away: the filesystem may need to actually write out data to the underlying storage. The point is that in neither case is close required to take no further action before freeing the underlying resource.

Furthermore, we are kinda trying to establish the pattern of gracefulShutdown with ServiceLifecycle so I think aligning on a common name is beneficial for the ecosystem.

I don't buy into this reasoning at all. Names should be chosen because they are the most appropriate name for the thing they are naming. The graceful shutdown pattern is established for service lifecycle because that's the method name required by its protocol. It is necessarily generic because it can't know what an individual service needs to do to shutdown gracefully. We don't depend on lifecycle here so we don't need to and shouldn't use the generic name.

@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
extension GRPCClient {
/// A collection of interceptors providing cross-cutting functionality to each accepted RPC.
public struct Interceptors: Sendable {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we ever want to actually make this fully generic? So you can have a GRPCCLient with a generic interceptor pipeline which hopefully gets specialised. @glbrntt

Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think the generics carry their weight at the interceptor level tbh. Having a client generic over a transport and a bunch of interceptors hurts ergonomics quite badly.

@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
extension GRPCClient {
/// The execution policy for an RPC.
public enum ExecutionPolicy: Hashable, Sendable {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we use a struct for this instead?

Copy link
Collaborator

Choose a reason for hiding this comment

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

We discussed this one previously (it's just been renamed from elsewhere)

@gjcairo gjcairo marked this pull request as ready for review November 29, 2023 15:05
@gjcairo gjcairo requested a review from glbrntt November 29, 2023 16:46
@glbrntt glbrntt mentioned this pull request Nov 30, 2023
@glbrntt
Copy link
Collaborator

glbrntt commented Nov 30, 2023

Superseded by #1729

@glbrntt glbrntt closed this Nov 30, 2023
@gjcairo gjcairo deleted the client-object branch March 15, 2024 16:08
@gjcairo gjcairo restored the client-object branch March 15, 2024 16:08
@gjcairo gjcairo deleted the client-object branch March 15, 2024 16:08
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