core: delay retriable stream master listener close until all sub streams are closed (#9754)

This helps to prevent retryable stream from using calloptions.executor when it shouldn't, e.g. call is already notified closed. It is done by delaying notifying upper layer (through masterListener).
This commit is contained in:
yifeizhuang 2023-01-12 14:51:13 -08:00 committed by GitHub
parent ce86090322
commit 2b9bd6cdac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 52 additions and 18 deletions

View File

@ -108,7 +108,7 @@ abstract class RetriableStream<ReqT> implements ClientStream {
private final AtomicBoolean noMoreTransparentRetry = new AtomicBoolean();
private final AtomicInteger localOnlyTransparentRetries = new AtomicInteger();
private final AtomicInteger inFlightSubStreams = new AtomicInteger();
private Status savedCancellationReason;
private SavedCloseMasterListenerReason savedCloseMasterListenerReason;
// Used for recording the share of buffer used for the current call out of the channel buffer.
// This field would not be necessary if there is no channel buffer limit.
@ -222,9 +222,10 @@ abstract class RetriableStream<ReqT> implements ClientStream {
}
}
@Nullable // returns null when cancelled
// returns null means we should not create new sub streams, e.g. cancelled or
// other close condition is met for retriableStream.
@Nullable
private Substream createSubstream(int previousAttemptCount, boolean isTransparentRetry) {
// increment only when >= 0, i.e. not cancelled
int inFlight;
do {
inFlight = inFlightSubStreams.get();
@ -506,11 +507,8 @@ abstract class RetriableStream<ReqT> implements ClientStream {
Runnable runnable = commit(noopSubstream);
if (runnable != null) {
savedCancellationReason = reason;
runnable.run();
if (inFlightSubStreams.addAndGet(Integer.MIN_VALUE) == Integer.MIN_VALUE) {
safeCloseMasterListener(reason, RpcProgress.PROCESSED, new Metadata());
}
safeCloseMasterListener(reason, RpcProgress.PROCESSED, new Metadata());
return;
}
@ -816,14 +814,30 @@ abstract class RetriableStream<ReqT> implements ClientStream {
}
private void safeCloseMasterListener(Status status, RpcProgress progress, Metadata metadata) {
listenerSerializeExecutor.execute(
new Runnable() {
@Override
public void run() {
isClosed = true;
masterListener.closed(status, progress, metadata);
}
});
savedCloseMasterListenerReason = new SavedCloseMasterListenerReason(status, progress,
metadata);
if (inFlightSubStreams.addAndGet(Integer.MIN_VALUE) == Integer.MIN_VALUE) {
listenerSerializeExecutor.execute(
new Runnable() {
@Override
public void run() {
isClosed = true;
masterListener.closed(status, progress, metadata);
}
});
}
}
private static final class SavedCloseMasterListenerReason {
private final Status status;
private final RpcProgress progress;
private final Metadata metadata;
SavedCloseMasterListenerReason(Status status, RpcProgress progress, Metadata metadata) {
this.status = status;
this.progress = progress;
this.metadata = metadata;
}
}
private interface BufferEntry {
@ -864,8 +878,17 @@ abstract class RetriableStream<ReqT> implements ClientStream {
}
if (inFlightSubStreams.decrementAndGet() == Integer.MIN_VALUE) {
assert savedCancellationReason != null;
safeCloseMasterListener(savedCancellationReason, RpcProgress.PROCESSED, new Metadata());
assert savedCloseMasterListenerReason != null;
listenerSerializeExecutor.execute(
new Runnable() {
@Override
public void run() {
isClosed = true;
masterListener.closed(savedCloseMasterListenerReason.status,
savedCloseMasterListenerReason.progress,
savedCloseMasterListenerReason.metadata);
}
});
return;
}

View File

@ -2151,6 +2151,10 @@ public class RetriableStreamTest {
assertEquals(Status.CANCELLED.getCode(), statusCaptor.getValue().getCode());
assertEquals(CANCELLED_BECAUSE_COMMITTED, statusCaptor.getValue().getDescription());
inOrder.verify(retriableStreamRecorder).postCommit();
sublistenerCaptor1.getValue().closed(
Status.CANCELLED, PROCESSED, new Metadata());
sublistenerCaptor4.getValue().closed(
Status.CANCELLED, PROCESSED, new Metadata());
inOrder.verify(masterListener).closed(
any(Status.class), any(RpcProgress.class), any(Metadata.class));
inOrder.verifyNoMoreInteractions();
@ -2158,7 +2162,8 @@ public class RetriableStreamTest {
insight = new InsightBuilder();
hedgingStream.appendTimeoutInsight(insight);
assertThat(insight.toString()).isEqualTo(
"[closed=[UNAVAILABLE, INTERNAL], committed=[remote_addr=2.2.2.2:81]]");
"[closed=[UNAVAILABLE, INTERNAL, CANCELLED, CANCELLED], "
+ "committed=[remote_addr=2.2.2.2:81]]");
}
@Test
@ -2425,6 +2430,7 @@ public class RetriableStreamTest {
assertEquals(Status.CANCELLED.getCode(), statusCaptor.getValue().getCode());
assertEquals(CANCELLED_BECAUSE_COMMITTED, statusCaptor.getValue().getDescription());
inOrder.verify(retriableStreamRecorder).postCommit();
sublistenerCaptor3.getValue().closed(Status.CANCELLED, PROCESSED, metadata);
inOrder.verify(masterListener).closed(fatal, PROCESSED, metadata);
inOrder.verifyNoMoreInteractions();
}
@ -2605,6 +2611,8 @@ public class RetriableStreamTest {
assertEquals(Status.CANCELLED.getCode(), statusCaptor.getValue().getCode());
assertEquals(CANCELLED_BECAUSE_COMMITTED, statusCaptor.getValue().getDescription());
verify(retriableStreamRecorder).postCommit();
sublistenerCaptor1.getValue().closed(Status.CANCELLED, PROCESSED, metadata);
sublistenerCaptor4.getValue().closed(Status.CANCELLED, PROCESSED, metadata);
verify(masterListener).closed(status, REFUSED, metadata);
}
@ -2645,6 +2653,9 @@ public class RetriableStreamTest {
assertEquals(Status.CANCELLED.getCode(), statusCaptor.getValue().getCode());
assertEquals(CANCELLED_BECAUSE_COMMITTED, statusCaptor.getValue().getDescription());
verify(retriableStreamRecorder).postCommit();
sublistenerCaptor1.getValue()
.closed(Status.CANCELLED, REFUSED, new Metadata());
//master listener close should wait until all substreams are closed
verify(masterListener).closed(status, REFUSED, metadata);
}