-
Notifications
You must be signed in to change notification settings - Fork 3.8k
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
core: Do not leak server state when application callbacks throw exceptions #3064
Changes from 1 commit
a1db4c6
d0a2ea3
ab44d1f
027e994
65e4d61
b65e09f
0514d6a
7eb4eab
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 |
---|---|---|
|
@@ -17,16 +17,12 @@ | |
package io.grpc.util.interceptor.server; | ||
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. This needs to move folder/package as well. |
||
|
||
import static com.google.common.collect.Iterables.getOnlyElement; | ||
import static org.mockito.Matchers.any; | ||
import static org.mockito.Matchers.same; | ||
import static org.mockito.Mockito.doThrow; | ||
import static org.mockito.Mockito.times; | ||
import static org.mockito.Mockito.verify; | ||
|
||
import io.grpc.Metadata; | ||
import io.grpc.MethodDescriptor; | ||
import io.grpc.MethodDescriptor.Marshaller; | ||
import io.grpc.MethodDescriptor.MethodType; | ||
import io.grpc.ServerCall; | ||
import io.grpc.ServerCallHandler; | ||
import io.grpc.ServerInterceptors; | ||
|
@@ -36,12 +32,13 @@ | |
import io.grpc.Status; | ||
import io.grpc.StatusRuntimeException; | ||
import io.grpc.testing.NoopServerCall; | ||
import io.grpc.testing.TestMethodDescriptors; | ||
import io.grpc.util.TransmitStatusRuntimeExceptionInterceptor; | ||
import java.util.Arrays; | ||
import org.junit.Before; | ||
import org.junit.Test; | ||
import org.junit.runner.RunWith; | ||
import org.junit.runners.JUnit4; | ||
import org.mockito.Mock; | ||
import org.mockito.Mockito; | ||
import org.mockito.MockitoAnnotations; | ||
|
||
|
@@ -51,42 +48,27 @@ | |
*/ | ||
@RunWith(JUnit4.class) | ||
public class UtilServerInterceptorsTest { | ||
@Mock | ||
private Marshaller<String> requestMarshaller; | ||
|
||
@Mock | ||
private Marshaller<Integer> responseMarshaller; | ||
private MethodDescriptor<String, Integer> flowMethod = TestMethodDescriptors.noopMethod(); | ||
private ServerCall<String, Integer> call = Mockito.spy(new NoopServerCall<String, Integer>()); | ||
private final Metadata headers = new Metadata(); | ||
|
||
@Mock | ||
private ServerServiceDefinition serviceDefinition; | ||
private ServerCallHandler<String, Integer> handler; | ||
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. Make a concrete implementation instead of a mock? 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. Most of these fields can turn into concrete implementations without too much boiler plate, so changing those as well. |
||
|
||
@Mock | ||
private ServerCall.Listener<String> listener; | ||
|
||
private MethodDescriptor<String, Integer> flowMethod; | ||
|
||
private ServerCall<String, Integer> call = new NoopServerCall<String, Integer>(); | ||
|
||
private ServerServiceDefinition serviceDefinition; | ||
|
||
private final Metadata headers = new Metadata(); | ||
|
||
/** Set up for test. */ | ||
@Before | ||
public void setUp() { | ||
MockitoAnnotations.initMocks(this); | ||
flowMethod = MethodDescriptor.<String, Integer>newBuilder() | ||
.setType(MethodType.UNKNOWN) | ||
.setFullMethodName("basic/flow") | ||
.setRequestMarshaller(requestMarshaller) | ||
.setResponseMarshaller(responseMarshaller) | ||
.build(); | ||
|
||
Mockito.when(handler.startCall( | ||
Mockito.<ServerCall<String, Integer>>any(), Mockito.<Metadata>any())) | ||
.thenReturn(listener); | ||
|
||
serviceDefinition = ServerServiceDefinition.builder(new ServiceDescriptor("basic", flowMethod)) | ||
handler = new ServerCallHandler<String, Integer>() { | ||
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. This initialization can be done at declaration time, and then serviceDefinition can also be initialized when declared. That would leave just initMocks in setup. |
||
@Override | ||
public ServerCall.Listener<String> startCall( | ||
ServerCall<String, Integer> call, Metadata headers) { | ||
return listener; | ||
} | ||
}; | ||
serviceDefinition = ServerServiceDefinition.builder( | ||
new ServiceDescriptor("service_foo", flowMethod)) | ||
.addMethod(flowMethod, handler).build(); | ||
} | ||
|
||
|
@@ -103,18 +85,38 @@ private static ServerMethodDefinition<String, Integer> getSoleMethod( | |
public void statusRuntimeExceptionTransmitter() { | ||
final Status expectedStatus = Status.UNAVAILABLE; | ||
final Metadata expectedMetadata = new Metadata(); | ||
call = Mockito.spy(call); | ||
final StatusRuntimeException exception = | ||
new StatusRuntimeException(expectedStatus, expectedMetadata); | ||
doThrow(exception).when(listener).onMessage(any(String.class)); | ||
doThrow(exception).when(listener).onCancel(); | ||
doThrow(exception).when(listener).onComplete(); | ||
doThrow(exception).when(listener).onHalfClose(); | ||
doThrow(exception).when(listener).onReady(); | ||
listener = new ServerCall.Listener<String>() { | ||
@Override | ||
public void onMessage(String message) { | ||
throw exception; | ||
} | ||
|
||
@Override | ||
public void onHalfClose() { | ||
throw exception; | ||
} | ||
|
||
@Override | ||
public void onCancel() { | ||
throw exception; | ||
} | ||
|
||
@Override | ||
public void onComplete() { | ||
throw exception; | ||
} | ||
|
||
@Override | ||
public void onReady() { | ||
throw exception; | ||
} | ||
}; | ||
|
||
ServerServiceDefinition intercepted = ServerInterceptors.intercept( | ||
serviceDefinition, | ||
Arrays.asList(StatusRuntimeExceptionTransmitter.instance())); | ||
Arrays.asList(TransmitStatusRuntimeExceptionInterceptor.instance())); | ||
// The interceptor should have handled the error by directly closing the ServerCall | ||
// and the exception should not propagate to the method's caller | ||
getSoleMethod(intercepted).getServerCallHandler().startCall(call, headers).onMessage("hello"); | ||
|
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.
getTrailers may return null, but close() requires non-null Metadata