-
Notifications
You must be signed in to change notification settings - Fork 3.9k
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
api,core: Add ServerInterceptor2 interface #7746
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sending the review comments I have. I should have sent them out last week, but forgot to. They are incomplete, and even includes a note for myself, but probably are helpful.
@@ -24,11 +27,13 @@ | |||
public final class ServerMethodDefinition<ReqT, RespT> { | |||
private final MethodDescriptor<ReqT, RespT> method; | |||
private final ServerCallHandler<ReqT, RespT> handler; | |||
private final List<ServerStreamTracer.Factory> streamTracerFactories; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class must not be mutable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
@@ -654,6 +685,23 @@ public void cancelled(Context context) { | |||
} | |||
} | |||
|
|||
/** Wrapper to convert ServerInterceptor to ServerInterceptor2. */ | |||
private static final class ServerInterceptorConverter implements ServerInterceptor2 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be provided as a method on ServerInterceptors
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
/** | ||
* Adds a {@link ServerInterceptor2} that is run for all services on the server. Interceptors | ||
* added through this method always run before per-service interceptors added through {@link | ||
* ServerInterceptors} and any interceptors added through {@link #intercept(ServerInterceptor)}. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we want to be able to mix ServerInterceptor and ServerInterceptor2 together, such that they are run in the order added, not in two stages. I imagine that would be implemented by converting ServerInterceptors to ServerInterceptor2 when being added.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is much clearer. Done.
@@ -554,13 +558,28 @@ private void runInternal() { | |||
context.cancel(null); | |||
return; | |||
} | |||
listener = startCall(stream, methodName, method, headers, context, statsTraceCtx, tag); | |||
ServerMethodDefinition<?, ?> interceptedDef = getInterceptedMethodDef(method); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since interceptors can now do non-trivial processing ahead-of-time for the method, I think we'll try to do this per-RPC only for the fallbackRegistry
case. We can pre-apply the interceptors to the services in registry
. Although there may be complications, like with reflection. Not essential today, but at least put a TODO.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added TODO
ServerCallHandler<ReqT, RespT> handler = methodDef.getServerCallHandler(); | ||
for (ServerInterceptor interceptor : interceptors) { | ||
handler = InternalServerInterceptors.interceptCallHandler(interceptor, handler); | ||
Context previous = context.attach(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note to self: Context here and in JumpToApplicationThreadServerStreamListener seems should see some simplification. Why is it doing this?
StreamTracer[] tracerArr = new StreamTracer[factories.size()]; | ||
for (int i = 0; i < tracerArr.length; i++) { | ||
ServerStreamTracer tracer = factories.get(i).newServerStreamTracer(fullMethodName, headers); | ||
context = tracer.filterContext(context).withCancellation(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, no. Every CancellableContext must be canceled at some point in the future. It's not clear to me why we'd even need to involve this code with CancellabelContext.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't need to be CancellableContext
here. As far as the ServerStreamTracer#filterContext
it should just be a Context
. This is just a result of bad 'type matching' on my part: by the time this method is invoked from ServerImpl
, we already have a CancellableContext
- I think it's necessary very early in the call handling process for e.g. RPC deadlines - and I was just blindly maintaining that type here.
But I'm not sure I fully understand the problem that you are describing: looking at what withCancellation
actually does, it's obvious that it's heavier weight than we actually need but I understood your comment to mean this is actually incorrect due to a mishandling of the context's lifetime. However, since we begin with a CancellableContext
from ServerImpl
(created and managed by the existing code prior to this PR, so I don't think I've broken anything there, probably :)) and according to the CancellableContext
javadoc it is "A context which inherits cancellation from its parent but which can also be independently cancelled and which will propagate cancellation to its descendants" shouldn't this actually work as-is? Since the parent CancellableContext
from ServerImpl.ServerTransportListenerImpl#createContext
will eventually be cancelled and that cancellation propagated to the descendants created here?
I'm worried that I'm misunderstanding something fundamental here, but the basic constraint I was operating under was that, when this method is invoked from ServerImpl.ServerTransportListenerImpl#streamCreatedInternal
, we have a CancellableContext
and want to get a CancellableContext
back. The current code is just a ham-handed way of maintaining that type, but is there a reason that we don't want a CancellableContext
at this point at all and the "conversion" from Context
to CancellableContext
currently done in ServerImpl.ServerTransportListenerImpl#createContext
should be deferred until after these interceptor-provided tracers have been invoked?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was talking about "To avoid leaking memory, every CancellableContext must have a defined lifetime, after which it is guaranteed to be cancelled." Yes, if the parent is cancellable you are guaranteed this one will have a lifetime, although you aren't guaranteed the lifetime is appropriate (the lifetime could be the entire process lifetime in some Context cases). I think the only reason you know the lifetime is appropriate here is because you say "statsTraceCtx.serverFilterContext()" is expected to be called first, at which point the context should already be scoped to the call.
But there's costs to cancellable contexts, and it is really weird that they don't manage their own lifetime, and it's weird they are created in a loop (instead of only once at the end). I think this code should just be dealing with normal contexts; normal context input and normal context output.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the review! I addressed most of the comments, and left a long-winded follow-up query in regards to properly handling the CancellableContext
issue
StreamTracer[] tracerArr = new StreamTracer[factories.size()]; | ||
for (int i = 0; i < tracerArr.length; i++) { | ||
ServerStreamTracer tracer = factories.get(i).newServerStreamTracer(fullMethodName, headers); | ||
context = tracer.filterContext(context).withCancellation(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't need to be CancellableContext
here. As far as the ServerStreamTracer#filterContext
it should just be a Context
. This is just a result of bad 'type matching' on my part: by the time this method is invoked from ServerImpl
, we already have a CancellableContext
- I think it's necessary very early in the call handling process for e.g. RPC deadlines - and I was just blindly maintaining that type here.
But I'm not sure I fully understand the problem that you are describing: looking at what withCancellation
actually does, it's obvious that it's heavier weight than we actually need but I understood your comment to mean this is actually incorrect due to a mishandling of the context's lifetime. However, since we begin with a CancellableContext
from ServerImpl
(created and managed by the existing code prior to this PR, so I don't think I've broken anything there, probably :)) and according to the CancellableContext
javadoc it is "A context which inherits cancellation from its parent but which can also be independently cancelled and which will propagate cancellation to its descendants" shouldn't this actually work as-is? Since the parent CancellableContext
from ServerImpl.ServerTransportListenerImpl#createContext
will eventually be cancelled and that cancellation propagated to the descendants created here?
I'm worried that I'm misunderstanding something fundamental here, but the basic constraint I was operating under was that, when this method is invoked from ServerImpl.ServerTransportListenerImpl#streamCreatedInternal
, we have a CancellableContext
and want to get a CancellableContext
back. The current code is just a ham-handed way of maintaining that type, but is there a reason that we don't want a CancellableContext
at this point at all and the "conversion" from Context
to CancellableContext
currently done in ServerImpl.ServerTransportListenerImpl#createContext
should be deferred until after these interceptor-provided tracers have been invoked?
@@ -554,13 +558,28 @@ private void runInternal() { | |||
context.cancel(null); | |||
return; | |||
} | |||
listener = startCall(stream, methodName, method, headers, context, statsTraceCtx, tag); | |||
ServerMethodDefinition<?, ?> interceptedDef = getInterceptedMethodDef(method); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added TODO
/** | ||
* Adds a {@link ServerInterceptor2} that is run for all services on the server. Interceptors | ||
* added through this method always run before per-service interceptors added through {@link | ||
* ServerInterceptors} and any interceptors added through {@link #intercept(ServerInterceptor)}. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is much clearer. Done.
@@ -24,11 +27,13 @@ | |||
public final class ServerMethodDefinition<ReqT, RespT> { | |||
private final MethodDescriptor<ReqT, RespT> method; | |||
private final ServerCallHandler<ReqT, RespT> handler; | |||
private final List<ServerStreamTracer.Factory> streamTracerFactories; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
@@ -654,6 +685,23 @@ public void cancelled(Context context) { | |||
} | |||
} | |||
|
|||
/** Wrapper to convert ServerInterceptor to ServerInterceptor2. */ | |||
private static final class ServerInterceptorConverter implements ServerInterceptor2 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
Also, I know we discussed a better name than |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, apparently I've have some pending comments sitting here for a long time. I'm really surprised github didn't GC them.
this.method = method; | ||
this.handler = handler; | ||
this.tracerFactories = Collections.unmodifiableList(tracerFactories); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not immutable. You need to make a copy.
List<ServerStreamTracer.Factory> l = new ArrayList<>();
def = def.withStreamTracerFactories(l);
l.add(foo);
*/ | ||
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/????") | ||
public ServerMethodDefinition<ReqT, RespT> withStreamTracerFactories( | ||
List<ServerStreamTracer.Factory> tracerFactories) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an annoying API to use, as it requires getting the current list, copying it, and adding your own factory to the list, all before calling this API. How about a withAdditionalStreamTracerFactory(ServerStreamTracer.Factory)
which adds to the list for you. (withStreamTracerFactory
may be an okay name, but that's more for an API review meeting discussion.)
* @since 1.36.0 | ||
*/ | ||
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/????") | ||
public List<ServerStreamTracer.Factory> getStreamTracerFactories() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There may be value in this being a Collection. List is fine for now, but it should be discussed in the API review meeting.
ServerInterceptor2
allows rewriting theServerMethodDefinition
. This accommodates, among other things, setting stream tracers on theServerMethodDefinition
as part of an interceptor, rather than requiring stream tracer factories to be added directly to the server builder prior to construction.Since some aspects of the stats trace context are currently created when the stream begins and before method handler lookup has occurred, which means any stream tracers attached via a
ServerInterceptor2
must be invoked later, after method lookup. And one of the abilities of a server stream tracer is filtering the context in which the call will take place. This PR rewires things inServerImpl
to accomodate a (partially) delayed creation of the stats trace context itself and the context in which the call operates.Notes for review:
StatsTraceContext
class already had several client-specific and server-specific methods. This PR adds more. It is probably worthwhile splitting the class into a ClientStatsTraceContext and ServerStatsTraceContext.ServerBuilder#intercept(io.grpc.ServerInterceptor2)
is confusing when discussing "order" in relation to the existingServerInterceptor
, as theServerInterceptor2
API is actually invoked prior to the call starting, so outside of theServerInteceptor#startCall
ordering entirely - butServerInterceptor2
can still rewrite theServerCallHandler
to mimic the operation of aServerInterceptor
.