Skip to content

Commit 3daabc8

Browse files
authored
HDDS-15013. gRPC channel holding objects on completed request in ReadBlock (#10071)
1 parent e407268 commit 3daabc8

2 files changed

Lines changed: 98 additions & 6 deletions

File tree

hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/storage/StreamBlockInputStream.java

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
import org.apache.ratis.protocol.exceptions.TimeoutIOException;
5555
import org.apache.ratis.thirdparty.com.google.protobuf.ByteString;
5656
import org.apache.ratis.thirdparty.io.grpc.StatusRuntimeException;
57+
import org.apache.ratis.thirdparty.io.grpc.stub.ClientCallStreamObserver;
5758
import org.apache.ratis.util.Preconditions;
5859
import org.slf4j.Logger;
5960
import org.slf4j.LoggerFactory;
@@ -65,6 +66,7 @@
6566
public class StreamBlockInputStream extends BlockExtendedInputStream {
6667
private static final Logger LOG = LoggerFactory.getLogger(StreamBlockInputStream.class);
6768
private static final int EOF = -1;
69+
private static final String STREAM_CLOSE_REASON = "StreamBlockInputStream closed";
6870
private static final AtomicInteger STREAM_ID = new AtomicInteger(0);
6971
private static final AtomicInteger READER_ID = new AtomicInteger(0);
7072

@@ -225,12 +227,36 @@ public synchronized void unbuffer() {
225227
}
226228

227229
private synchronized void closeStream() {
228-
if (streamingReader != null) {
229-
LOG.debug("Closing {}", streamingReader);
230-
streamingReader.onCompleted();
231-
streamingReader = null;
230+
if (streamingReader == null) {
231+
buffer = null;
232+
return;
232233
}
234+
235+
final StreamingReader reader = streamingReader;
236+
streamingReader = null;
233237
buffer = null;
238+
239+
if (LOG.isDebugEnabled()) {
240+
LOG.debug("Closing {}", reader);
241+
}
242+
243+
reader.onCompleted();
244+
245+
final StreamingReadResponse response = reader.getResponse();
246+
if (response != null) {
247+
final ClientCallStreamObserver<ContainerProtos.ContainerCommandRequestProto> requestObserver =
248+
response.getRequestObserver();
249+
try {
250+
requestObserver.onCompleted();
251+
} catch (RuntimeException e) {
252+
LOG.warn("Failed to close gRPC request stream for {}", reader, e);
253+
try {
254+
requestObserver.cancel(STREAM_CLOSE_REASON, e);
255+
} catch (RuntimeException cancelEx) {
256+
LOG.warn("Failed to cancel gRPC request stream for {}", reader, cancelEx);
257+
}
258+
}
259+
}
234260
}
235261

236262
protected synchronized void checkOpen() throws IOException {

hadoop-hdds/client/src/test/java/org/apache/hadoop/hdds/scm/storage/TestStreamBlockInputStream.java

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@
1919

2020
import static org.junit.jupiter.api.Assertions.assertEquals;
2121
import static org.mockito.ArgumentMatchers.any;
22+
import static org.mockito.ArgumentMatchers.eq;
2223
import static org.mockito.Mockito.doAnswer;
2324
import static org.mockito.Mockito.doNothing;
25+
import static org.mockito.Mockito.doThrow;
2426
import static org.mockito.Mockito.mock;
2527
import static org.mockito.Mockito.never;
2628
import static org.mockito.Mockito.times;
@@ -50,6 +52,7 @@
5052
import org.apache.hadoop.hdds.security.token.OzoneBlockTokenIdentifier;
5153
import org.apache.hadoop.security.token.Token;
5254
import org.apache.ratis.thirdparty.com.google.protobuf.ByteString;
55+
import org.apache.ratis.thirdparty.io.grpc.stub.ClientCallStreamObserver;
5356
import org.junit.jupiter.api.Test;
5457

5558
/**
@@ -97,7 +100,8 @@ public void testReleasesStreamPermitAtBlockEof() throws Exception {
97100
byte[] data = new byte[] {1, 2, 3, 4};
98101
long length = data.length;
99102
Pipeline pipeline = mockStandalonePipeline();
100-
XceiverClientGrpc xceiverClient = mockStreamingReadClient(data);
103+
ClientCallStreamObserver<ContainerCommandRequestProto> requestObserver = mock(ClientCallStreamObserver.class);
104+
XceiverClientGrpc xceiverClient = mockStreamingReadClient(data, requestObserver);
101105
XceiverClientFactory xceiverClientFactory = mock(XceiverClientFactory.class);
102106
when(xceiverClientFactory.acquireClientForReadData(any(Pipeline.class)))
103107
.thenReturn(xceiverClient);
@@ -115,13 +119,73 @@ public void testReleasesStreamPermitAtBlockEof() throws Exception {
115119
assertEquals(data[(int) length - 1] & 0xFF, last);
116120
assertEquals(length, sbis.getPos());
117121
verify(xceiverClient, times(1)).completeStreamRead();
122+
verify(requestObserver, times(1)).onCompleted();
118123

119124
// Subsequent reads should return EOF and must not trigger duplicate permit release.
120125
assertEquals(-1, sbis.read());
121126
assertEquals(-1, sbis.read());
122127
}
123128

124129
verify(xceiverClient, times(1)).completeStreamRead();
130+
verify(requestObserver, times(1)).onCompleted();
131+
verify(requestObserver, never()).cancel(any(), any());
132+
}
133+
134+
@Test
135+
public void testCancelsRequestStreamWhenOnCompletedThrows() throws Exception {
136+
OzoneClientConfig clientConfig = newStreamReadConfig();
137+
BlockID blockID = new BlockID(1L, 3L);
138+
byte[] data = new byte[] {1, 2, 3, 4};
139+
Pipeline pipeline = mockStandalonePipeline();
140+
ClientCallStreamObserver<ContainerCommandRequestProto> requestObserver = mock(ClientCallStreamObserver.class);
141+
RuntimeException closeFailure = new RuntimeException("close failed");
142+
doThrow(closeFailure).when(requestObserver).onCompleted();
143+
144+
XceiverClientGrpc xceiverClient = mockStreamingReadClient(data, requestObserver);
145+
XceiverClientFactory xceiverClientFactory = mock(XceiverClientFactory.class);
146+
when(xceiverClientFactory.acquireClientForReadData(any(Pipeline.class))).thenReturn(xceiverClient);
147+
148+
try (StreamBlockInputStream sbis = new StreamBlockInputStream(
149+
blockID, data.length, pipeline, null, xceiverClientFactory, NO_REFRESH, clientConfig)) {
150+
ByteBuffer all = ByteBuffer.allocate(data.length);
151+
assertEquals(data.length, sbis.read(all));
152+
assertEquals(data.length, sbis.getPos());
153+
assertEquals(-1, sbis.read());
154+
}
155+
156+
verify(requestObserver, times(1)).onCompleted();
157+
verify(requestObserver, times(1)).cancel(eq("StreamBlockInputStream closed"), eq(closeFailure));
158+
verify(xceiverClient, times(1)).completeStreamRead();
159+
}
160+
161+
@Test
162+
public void testCloseDoesNotFailWhenOnCompletedAndCancelThrow() throws Exception {
163+
OzoneClientConfig clientConfig = newStreamReadConfig();
164+
BlockID blockID = new BlockID(1L, 4L);
165+
byte[] data = new byte[] {1, 2, 3, 4};
166+
Pipeline pipeline = mockStandalonePipeline();
167+
ClientCallStreamObserver<ContainerCommandRequestProto> requestObserver = mock(ClientCallStreamObserver.class);
168+
RuntimeException closeFailure = new RuntimeException("close failed");
169+
RuntimeException cancelFailure = new RuntimeException("cancel failed");
170+
doThrow(closeFailure).when(requestObserver).onCompleted();
171+
doThrow(cancelFailure).when(requestObserver)
172+
.cancel(eq("StreamBlockInputStream closed"), eq(closeFailure));
173+
174+
XceiverClientGrpc xceiverClient = mockStreamingReadClient(data, requestObserver);
175+
XceiverClientFactory xceiverClientFactory = mock(XceiverClientFactory.class);
176+
when(xceiverClientFactory.acquireClientForReadData(any(Pipeline.class))).thenReturn(xceiverClient);
177+
178+
try (StreamBlockInputStream sbis = new StreamBlockInputStream(
179+
blockID, data.length, pipeline, null, xceiverClientFactory, NO_REFRESH, clientConfig)) {
180+
ByteBuffer all = ByteBuffer.allocate(data.length);
181+
assertEquals(data.length, sbis.read(all));
182+
assertEquals(data.length, sbis.getPos());
183+
assertEquals(-1, sbis.read());
184+
}
185+
186+
verify(requestObserver, times(1)).onCompleted();
187+
verify(requestObserver, times(1)).cancel(eq("StreamBlockInputStream closed"), eq(closeFailure));
188+
verify(xceiverClient, times(1)).completeStreamRead();
125189
}
126190

127191
private OzoneClientConfig newStreamReadConfig() {
@@ -149,10 +213,12 @@ private Pipeline mockStandalonePipeline() throws Exception {
149213
return pipeline;
150214
}
151215

152-
private XceiverClientGrpc mockStreamingReadClient(byte[] data) throws Exception {
216+
private XceiverClientGrpc mockStreamingReadClient(byte[] data,
217+
ClientCallStreamObserver<ContainerCommandRequestProto> requestObserver) throws Exception {
153218
XceiverClientGrpc xceiverClient = mock(XceiverClientGrpc.class);
154219
StreamingReadResponse streamingReadResponse = mock(StreamingReadResponse.class);
155220
ReadBlockResponseProto readBlock = buildReadBlockResponse(data);
221+
when(streamingReadResponse.getRequestObserver()).thenReturn(requestObserver);
156222

157223
doNothing().when(xceiverClient)
158224
.streamRead(any(ContainerCommandRequestProto.class),

0 commit comments

Comments
 (0)