core: Don't mark calls as cancelled if they are successfully completed. (#8408)

The semantics around cancel vary slightly between ServerCall and CancellableContext - the context should always be cancelled regardless of the outcome of the call while the ServerCall should only be cancelled on a non-OK status.

This fixes a bug where the ServerCall was always marked cancelled regardless of call status.

Fixes #5882
This commit is contained in:
Terry Wilson 2021-08-20 14:42:01 -07:00 committed by GitHub
parent c54fcba2ee
commit e45aab085c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 15 additions and 3 deletions

View File

@ -279,7 +279,11 @@ final class ServerCallImpl<ReqT, RespT> extends ServerCall<ReqT, RespT> {
new Context.CancellationListener() { new Context.CancellationListener() {
@Override @Override
public void cancelled(Context context) { public void cancelled(Context context) {
ServerStreamListenerImpl.this.call.cancelled = true; // If the context has a cancellation cause then something exceptional happened
// and we should also mark the call as cancelled.
if (context.cancellationCause() != null) {
ServerStreamListenerImpl.this.call.cancelled = true;
}
} }
}, },
MoreExecutors.directExecutor()); MoreExecutors.directExecutor());
@ -355,6 +359,8 @@ final class ServerCallImpl<ReqT, RespT> extends ServerCall<ReqT, RespT> {
} finally { } finally {
// Cancel context after delivering RPC closure notification to allow the application to // Cancel context after delivering RPC closure notification to allow the application to
// clean up and update any state based on whether onComplete or onCancel was called. // clean up and update any state based on whether onComplete or onCancel was called.
// Note that in failure situations JumpToApplicationThreadServerStreamListener has already
// closed the context. In these situations this cancel() call will be a no-op.
context.cancel(null); context.cancel(null);
} }
} }

View File

@ -18,6 +18,7 @@ package io.grpc.internal;
import static com.google.common.base.Charsets.UTF_8; import static com.google.common.base.Charsets.UTF_8;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
@ -378,6 +379,9 @@ public class ServerCallImplTest {
verify(callListener).onComplete(); verify(callListener).onComplete();
assertTrue(context.isCancelled()); assertTrue(context.isCancelled());
assertNull(context.cancellationCause()); assertNull(context.cancellationCause());
// The call considers cancellation to be an exceptional situation so it should
// not be cancelled with an OK status.
assertFalse(call.isCancelled());
} }
@Test @Test

View File

@ -1196,7 +1196,9 @@ public class ServerImplTest {
context, contextCancelled, null); context, contextCancelled, null);
// For close status OK: // For close status OK:
// isCancelled is expected to be true after all pending work is done // The context isCancelled is expected to be true after all pending work is done,
// but for the call it should be false as it gets set cancelled only if the call
// fails with a non-OK status.
assertFalse(callReference.get().isCancelled()); assertFalse(callReference.get().isCancelled());
assertFalse(context.get().isCancelled()); assertFalse(context.get().isCancelled());
streamListener.closed(Status.OK); streamListener.closed(Status.OK);
@ -1204,7 +1206,7 @@ public class ServerImplTest {
assertFalse(context.get().isCancelled()); assertFalse(context.get().isCancelled());
assertEquals(1, executor.runDueTasks()); assertEquals(1, executor.runDueTasks());
assertTrue(callReference.get().isCancelled()); assertFalse(callReference.get().isCancelled());
assertTrue(context.get().isCancelled()); assertTrue(context.get().isCancelled());
assertTrue(contextCancelled.get()); assertTrue(contextCancelled.get());
} }