Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions core/src/main/java/io/grpc/internal/ServerCallImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import io.perfmark.Tag;
import io.perfmark.TaskCloseable;
import java.io.InputStream;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;

Expand All @@ -66,6 +67,8 @@ final class ServerCallImpl<ReqT, RespT> extends ServerCall<ReqT, RespT> {
private final DecompressorRegistry decompressorRegistry;
private final CompressorRegistry compressorRegistry;
private CallTracer serverCallTracer;
// The application's close() and the transport's closed() may race; only the first is counted.
private final AtomicBoolean callEndReported = new AtomicBoolean();

// state
private volatile boolean cancelled;
Expand Down Expand Up @@ -229,7 +232,13 @@ private void closeInternal(Status status, Metadata trailers) {

stream.close(status, trailers);
} finally {
serverCallTracer.reportCallEnded(status.isOk());
reportCallEnded(status.isOk());
}
}

private void reportCallEnded(boolean success) {
if (callEndReported.compareAndSet(false, true)) {
serverCallTracer.reportCallEnded(success);
}
}

Expand Down Expand Up @@ -287,7 +296,7 @@ private void handleInternalError(Throwable internalError) {
: Status.INTERNAL.withCause(internalError)
.withDescription("Internal error so cancelling stream.");
stream.cancel(status);
serverCallTracer.reportCallEnded(false); // error so always false
reportCallEnded(false); // error so always false
}

/**
Expand Down Expand Up @@ -415,6 +424,7 @@ private void closedInternal(Status status) {
listener.onComplete();
} else {
call.cancelled = true;
call.reportCallEnded(false);
listener.onCancel();
// The status will not have a cause in all failure scenarios, but we want to make sure
// we always cancel the context with one to keep the context cancelled state consistent.
Expand Down
51 changes: 51 additions & 0 deletions core/src/test/java/io/grpc/internal/ServerCallImplTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,57 @@ public void callTracer_failure() {
callTracer0(Status.UNKNOWN);
}

@Test
public void callTracer_clientCancelled_reportsCallFailed() {
ServerStreamListenerImpl<Long> streamListener =
new ServerCallImpl.ServerStreamListenerImpl<>(call, callListener, context);

streamListener.closed(Status.CANCELLED);

ServerStats.Builder afterBuilder = new ServerStats.Builder();
serverCallTracer.updateBuilder(afterBuilder);
ServerStats after = afterBuilder.build();
assertEquals(1, after.callsStarted);
assertEquals(0, after.callsSucceeded);
assertEquals(1, after.callsFailed);
}

@Test
public void callTracer_clientCancelledThenClosed_reportsCallFailedOnce() {
ServerStreamListenerImpl<Long> streamListener =
new ServerCallImpl.ServerStreamListenerImpl<>(call, callListener, context);

streamListener.closed(Status.CANCELLED);
call.sendHeaders(new Metadata());
call.sendMessage(123L);
call.close(Status.OK, new Metadata());

ServerStats.Builder afterBuilder = new ServerStats.Builder();
serverCallTracer.updateBuilder(afterBuilder);
ServerStats after = afterBuilder.build();
assertEquals(1, after.callsStarted);
assertEquals(0, after.callsSucceeded);
assertEquals(1, after.callsFailed);
}

@Test
public void callTracer_closedThenClientCancelled_reportsCallSucceededOnce() {
ServerStreamListenerImpl<Long> streamListener =
new ServerCallImpl.ServerStreamListenerImpl<>(call, callListener, context);

call.sendHeaders(new Metadata());
call.sendMessage(123L);
call.close(Status.OK, new Metadata());
streamListener.closed(Status.CANCELLED);

ServerStats.Builder afterBuilder = new ServerStats.Builder();
serverCallTracer.updateBuilder(afterBuilder);
ServerStats after = afterBuilder.build();
assertEquals(1, after.callsStarted);
assertEquals(1, after.callsSucceeded);
assertEquals(0, after.callsFailed);
}

private void callTracer0(Status status) {
CallTracer tracer = CallTracer.getDefaultFactory().create();
ServerStats.Builder beforeBuilder = new ServerStats.Builder();
Expand Down
Loading