Skip to content

Commit 7628b0d

Browse files
committed
xds: treat ext_proc stream OK termination without drain as non-OK status
Update ext_proc client interceptor to treat OK termination without drain as non-OK status regardless of whether messages have started being sent to the ext_proc server, in accordance with the updated gRFC specification. TAG=agy CONV=581f86a6-097d-44a2-9d6e-6f9d2e83cbc0
1 parent 78e1e42 commit 7628b0d

2 files changed

Lines changed: 34 additions & 38 deletions

File tree

‎xds/src/main/java/io/grpc/xds/ExternalProcessorClientInterceptor.java‎

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -669,11 +669,18 @@ public void onError(Throwable t) {
669669

670670
@Override
671671
public void onCompleted() {
672-
if (markExtProcStreamCompleted(extProcStreamState)) {
673-
synchronized (streamLock) {
674-
extProcClientCallRequestObserver = null;
672+
ExtProcStreamState state = extProcStreamState.get();
673+
if (state == ExtProcStreamState.DRAINING || config.getObservabilityMode()) {
674+
if (markExtProcStreamCompleted(extProcStreamState)) {
675+
synchronized (streamLock) {
676+
extProcClientCallRequestObserver = null;
677+
}
678+
handleFailOpen(wrappedListener);
675679
}
676-
handleFailOpen(wrappedListener);
680+
} else if (state == ExtProcStreamState.ACTIVE) {
681+
internalOnError(Status.UNAVAILABLE
682+
.withDescription("External processor stream completed without drain")
683+
.asRuntimeException());
677684
}
678685
}
679686
});
@@ -849,7 +856,11 @@ private void internalOnError(Throwable t) {
849856
if (markExtProcStreamFailed(extProcStreamState)) {
850857
synchronized (streamLock) {
851858
if (extProcClientCallRequestObserver != null) {
852-
extProcClientCallRequestObserver.onError(t);
859+
try {
860+
extProcClientCallRequestObserver.onError(t);
861+
} catch (Throwable ignored) {
862+
// Ignore exception if observer is already closed/completed
863+
}
853864
extProcClientCallRequestObserver = null;
854865
}
855866
}

‎xds/src/test/java/io/grpc/xds/ExternalProcessorClientInterceptorTest.java‎

Lines changed: 18 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -6554,6 +6554,7 @@ public void onNext(ProcessingRequest request) {
65546554
if (request.hasRequestHeaders()) {
65556555
responseObserver.onNext(ProcessingResponse.newBuilder()
65566556
.setRequestHeaders(HeadersResponse.newBuilder().build())
6557+
.setRequestDrain(true)
65576558
.build());
65586559
responseObserver.onCompleted();
65596560
sidecarResponseLatch.countDown();
@@ -9925,9 +9926,10 @@ public void request(int numMessages) {
99259926

99269927
@Test
99279928
@SuppressWarnings("unchecked")
9928-
public void givenExtProcStreamCompleted_whenAppRequestsMessages_thenRequestsForwarded()
9929+
public void givenNoRequestDrain_whenExtProcStreamCompletesNormally_thenTreatsAsNonOkAndCallFails()
99299930
throws Exception {
99309931
ExternalProcessor proto = ExternalProcessor.newBuilder()
9932+
.setFailureModeAllow(false) // Fail closed
99319933
.setGrpcService(GrpcService.newBuilder()
99329934
.setGoogleGrpc(GrpcService.GoogleGrpc.newBuilder()
99339935
.setTargetUri("in-process:///" + extProcServerName)
@@ -9943,9 +9945,9 @@ public void givenExtProcStreamCompleted_whenAppRequestsMessages_thenRequestsForw
99439945
assertThat(configOrError.errorDetail).isNull();
99449946
ExternalProcessorFilterConfig filterConfig = configOrError.config;
99459947

9946-
// External Processor Server
9947-
ExternalProcessorGrpc.ExternalProcessorImplBase extProcImpl;
9948-
extProcImpl = new ExternalProcessorGrpc.ExternalProcessorImplBase() {
9948+
// External Processor Server completes normally without sending request_drain = true
9949+
ExternalProcessorGrpc.ExternalProcessorImplBase extProcImpl =
9950+
new ExternalProcessorGrpc.ExternalProcessorImplBase() {
99499951
@Override
99509952
@SuppressWarnings("unchecked")
99519953
public StreamObserver<ProcessingRequest> process(
@@ -9955,7 +9957,7 @@ public StreamObserver<ProcessingRequest> process(
99559957
@Override
99569958
public void onNext(ProcessingRequest request) {
99579959
if (request.hasRequestHeaders()) {
9958-
// Immediately complete the stream from server side
9960+
// Immediately complete the stream from server side without drain
99599961
responseObserver.onCompleted();
99609962
}
99619963
}
@@ -9991,49 +9993,32 @@ public void onCompleted() {
99919993
}))
99929994
.build());
99939995

9994-
final AtomicInteger dataPlaneRequestCount = new AtomicInteger(0);
99959996
ManagedChannel dataPlaneChannel = grpcCleanup.register(
99969997
InProcessChannelBuilder.forName(dataPlaneServerName)
99979998
.directExecutor()
9998-
.intercept(new ClientInterceptor() {
9999-
@Override
10000-
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
10001-
MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
10002-
return new io.grpc.ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(
10003-
next.newCall(method, callOptions)) {
10004-
@Override
10005-
public void request(int numMessages) {
10006-
dataPlaneRequestCount.addAndGet(numMessages);
10007-
super.request(numMessages);
10008-
}
10009-
};
10010-
}
10011-
})
100129999
.build());
1001310000

10014-
final CountDownLatch readyLatch = new CountDownLatch(1);
10001+
final CountDownLatch callCompletedLatch = new CountDownLatch(1);
10002+
final AtomicReference<Status> closedStatus = new AtomicReference<>();
1001510003
CallOptions callOptions = DEFAULT_CALL_OPTIONS.withExecutor(MoreExecutors.directExecutor());
1001610004
ClientCall<String, String> proxyCall =
1001710005
interceptCall(interceptor, METHOD_SAY_HELLO, callOptions, dataPlaneChannel);
1001810006
proxyCall.start(new ClientCall.Listener<String>() {
1001910007
@Override
10020-
public void onReady() {
10021-
readyLatch.countDown();
10008+
public void onClose(Status status, Metadata trailers) {
10009+
closedStatus.set(status);
10010+
callCompletedLatch.countDown();
1002210011
}
1002310012
}, new Metadata());
1002410013

10025-
// Wait for sidecar stream completion
10026-
assertThat(readyLatch.await(5, TimeUnit.SECONDS)).isTrue();
10027-
assertThat(proxyCall.isReady()).isTrue();
10014+
proxyCall.request(1);
10015+
proxyCall.sendMessage("hello");
10016+
proxyCall.halfClose();
1002810017

10029-
proxyCall.request(7);
10018+
assertThat(callCompletedLatch.await(5, TimeUnit.SECONDS)).isTrue();
10019+
assertThat(closedStatus.get().getCode()).isEqualTo(Status.Code.INTERNAL);
10020+
assertThat(closedStatus.get().getDescription()).contains("External processor stream failed");
1003010021

10031-
// Verify request forwarded immediately
10032-
assertThat(dataPlaneRequestCount.get()).isEqualTo(7);
10033-
// proxyCall.isReady() should remain true as sidecar is gone
10034-
assertThat(proxyCall.isReady()).isTrue();
10035-
10036-
proxyCall.cancel("Cleanup", null);
1003710022
channelManager.close();
1003810023
}
1003910024

0 commit comments

Comments
 (0)