-
Notifications
You must be signed in to change notification settings - Fork 4k
core: Delayed deserialization for unary/server-streaming calls #13004
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -34,6 +34,7 @@ | |
| import io.grpc.CompressorRegistry; | ||
| import io.grpc.Context; | ||
| import io.grpc.DecompressorRegistry; | ||
| import io.grpc.Detachable; | ||
| import io.grpc.InternalDecompressorRegistry; | ||
| import io.grpc.InternalStatus; | ||
| import io.grpc.Metadata; | ||
|
|
@@ -45,6 +46,9 @@ | |
| import io.perfmark.PerfMark; | ||
| import io.perfmark.Tag; | ||
| import io.perfmark.TaskCloseable; | ||
| import java.io.ByteArrayInputStream; | ||
| import java.io.ByteArrayOutputStream; | ||
| import java.io.IOException; | ||
| import java.io.InputStream; | ||
| import java.util.logging.Level; | ||
| import java.util.logging.Logger; | ||
|
|
@@ -288,6 +292,7 @@ static final class ServerStreamListenerImpl<ReqT> implements ServerStreamListene | |
| private final ServerCallImpl<ReqT, ?> call; | ||
| private final ServerCall.Listener<ReqT> listener; | ||
| private final Context.CancellableContext context; | ||
| private InputStream delayedMessage; | ||
|
|
||
| public ServerStreamListenerImpl( | ||
| ServerCallImpl<ReqT, ?> call, ServerCall.Listener<ReqT> listener, | ||
|
|
@@ -320,6 +325,20 @@ public void messagesAvailable(MessageProducer producer) { | |
| } | ||
| } | ||
|
|
||
| private static InputStream bufferMessage(InputStream is) throws IOException { | ||
| if (is instanceof Detachable) { | ||
| return ((Detachable) is).detach(); | ||
| } | ||
| // Fallback: copy to byte array | ||
| ByteArrayOutputStream baos = new ByteArrayOutputStream(); | ||
| byte[] buffer = new byte[4096]; | ||
| int bytesRead; | ||
| while ((bytesRead = is.read(buffer)) != -1) { | ||
| baos.write(buffer, 0, bytesRead); | ||
| } | ||
| return new ByteArrayInputStream(baos.toByteArray()); | ||
| } | ||
|
|
||
| @SuppressWarnings("Finally") // The code avoids suppressing the exception thrown from try | ||
| private void messagesAvailableInternal(final MessageProducer producer) { | ||
| if (call.cancelled) { | ||
|
|
@@ -330,13 +349,31 @@ private void messagesAvailableInternal(final MessageProducer producer) { | |
| InputStream message; | ||
| try { | ||
| while ((message = producer.next()) != null) { | ||
| try { | ||
| listener.onMessage(call.method.parseRequest(message)); | ||
| } catch (Throwable t) { | ||
| GrpcUtil.closeQuietly(message); | ||
| throw t; | ||
| if (call.method.getType().clientSendsOneMessage()) { | ||
| if (delayedMessage != null) { | ||
| GrpcUtil.closeQuietly(message); | ||
| call.stream.cancel(Status.INTERNAL.withDescription("Too many requests")); | ||
| GrpcUtil.closeQuietly(delayedMessage); | ||
| delayedMessage = null; | ||
| closedInternal(Status.INTERNAL.withDescription("Too many requests")); | ||
| return; | ||
| } | ||
| try { | ||
| delayedMessage = bufferMessage(message); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are we making a copy here when we could just "not call close()" on the original message?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That would work for the |
||
| } catch (Throwable t) { | ||
| GrpcUtil.closeQuietly(message); | ||
| throw t; | ||
| } | ||
| message.close(); | ||
| } else { | ||
| try { | ||
| listener.onMessage(call.method.parseRequest(message)); | ||
| } catch (Throwable t) { | ||
| GrpcUtil.closeQuietly(message); | ||
| throw t; | ||
| } | ||
| message.close(); | ||
| } | ||
| message.close(); | ||
| } | ||
| } catch (Throwable t) { | ||
| GrpcUtil.closeQuietly(producer); | ||
|
|
@@ -353,6 +390,19 @@ public void halfClosed() { | |
| return; | ||
| } | ||
|
|
||
| if (delayedMessage != null) { | ||
| InputStream message = delayedMessage; | ||
| delayedMessage = null; | ||
| try { | ||
| listener.onMessage(call.method.parseRequest(message)); | ||
| } catch (Throwable t) { | ||
| GrpcUtil.closeQuietly(message); | ||
| Throwables.throwIfUnchecked(t); | ||
| throw new RuntimeException(t); | ||
| } | ||
| GrpcUtil.closeQuietly(message); | ||
| } | ||
|
|
||
| listener.onHalfClose(); | ||
| } | ||
| } | ||
|
|
@@ -366,6 +416,10 @@ public void closed(Status status) { | |
| } | ||
|
|
||
| private void closedInternal(Status status) { | ||
| if (delayedMessage != null) { | ||
| GrpcUtil.closeQuietly(delayedMessage); | ||
| delayedMessage = null; | ||
| } | ||
| Throwable cancelCause = null; | ||
| try { | ||
| if (status.isOk()) { | ||
|
|
||
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 puts the call back into a normal state, so if other events happen after this one (e.g., message, or half close), that could end up propagating to the application before the cancel is processed. I don't know the easiest way to handle that though; obviously we could set some more state/booleans. It is probably worth looking into the exception handling in the executor see what would happen if we throw here.
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.
If an exception is thrown from
messagesAvailableInternal, the catch block in the wrapped code submitted to the call executor catches it and callsinternalClose(t)eventually leading to an asynchronous callback from the transport. It still does not handle the race you mentioned. Instead I'm now invokingclosedInternalsynchronously when the error is detected. This synchronously setscall.cancelled = trueand cancels the context before returning from the executor task.