Copilot commented on code in PR #10743:
URL: https://github.com/apache/ozone/pull/10743#discussion_r3573449844


##########
hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/transport/server/ratis/ContainerStateMachine.java:
##########
@@ -846,6 +860,222 @@ public CompletableFuture<Message> query(Message request) {
     }
   }
 
+  @Override
+  public void query(Message request, WritableByteChannel stream) {
+    try {
+      metrics.incNumQueryStateMachineOps();
+      final ContainerCommandRequestProto requestProto =
+          message2ContainerCommandRequestProto(request);
+      if (requestProto.getCmdType() == Type.ReadBlock) {
+        streamReadBlock(dispatcher, requestProto, stream);
+      } else {
+        writeAndClose(stream, dispatchCommand(requestProto, null));
+      }
+    } catch (IOException e) {
+      metrics.incNumQueryStateMachineFails();
+      throw new CompletionException(e);
+    }
+  }
+
+  static void streamReadBlock(
+      ContainerDispatcher dispatcher,
+      ContainerCommandRequestProto requestProto,
+      WritableByteChannel stream) throws IOException {
+    final AtomicReference<Throwable> error = new AtomicReference<>();
+    final AtomicBoolean responseSeen = new AtomicBoolean(false);
+    final DataStreamObserver<ReadBlockResponse> observer =
+        new DataStreamObserver<ReadBlockResponse>() {
+          @Override
+          public void onNext(ReadBlockResponse response) {
+            responseSeen.set(true);
+            try {
+              writeReadBlockStreamResponse(stream, response.getResponse(),
+                  response.getData());
+            } catch (IOException e) {
+              error.compareAndSet(null, e);
+              throw new CompletionException(e);
+            } catch (RuntimeException e) {
+              error.compareAndSet(null, e);
+              throw e;
+            }
+          }
+
+          @Override
+          public void onError(Throwable throwable) {
+            error.set(throwable);
+          }
+
+          @Override
+          public void onCompleted() {
+          }
+
+        };
+
+    try (RandomAccessFileChannel blockFile = new RandomAccessFileChannel()) {
+      dispatcher.streamDataReadOnly(requestProto, observer, blockFile, null);
+    } catch (CompletionException e) {
+      if (e.getCause() instanceof IOException) {
+        throw (IOException) e.getCause();
+      }
+      throw new IOException("Failed to stream ReadBlock " + requestProto,
+          e.getCause() != null ? e.getCause() : e);
+    }
+
+    if (error.get() != null) {
+      throw new IOException("Failed to stream ReadBlock " + requestProto,
+          error.get());
+    }
+    if (!responseSeen.get()) {
+      throw new IOException("ReadBlock stream returned no responses: "
+          + requestProto);
+    }
+
+    stream.close();
+  }
+
+  private static void writeAndClose(
+      WritableByteChannel stream, ContainerCommandResponseProto response)
+      throws IOException {
+    try {
+      writeFully(stream, response.toByteString().asReadOnlyByteBuffer());
+    } finally {
+      stream.close();
+    }
+  }
+
+  private static void writeReadBlockStreamResponse(
+      WritableByteChannel stream, ContainerCommandResponseProto response,
+      ByteBuffer data) throws IOException {
+    writeFully(stream, encodeReadBlockStreamResponse(response, data));
+  }

Review Comment:
   writeReadBlockStreamResponse currently allocates a single frame buffer and 
copies the entire data payload into it (frame.put(data)), which defeats the 
goal of streaming/zero-copy and adds extra heap pressure for large reads. Write 
header/metadata and data as separate buffers instead.



##########
hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/transport/server/ratis/ContainerStateMachine.java:
##########
@@ -846,6 +860,222 @@ public CompletableFuture<Message> query(Message request) {
     }
   }
 
+  @Override
+  public void query(Message request, WritableByteChannel stream) {
+    try {
+      metrics.incNumQueryStateMachineOps();
+      final ContainerCommandRequestProto requestProto =
+          message2ContainerCommandRequestProto(request);
+      if (requestProto.getCmdType() == Type.ReadBlock) {
+        streamReadBlock(dispatcher, requestProto, stream);
+      } else {
+        writeAndClose(stream, dispatchCommand(requestProto, null));
+      }
+    } catch (IOException e) {
+      metrics.incNumQueryStateMachineFails();
+      throw new CompletionException(e);
+    }
+  }
+
+  static void streamReadBlock(
+      ContainerDispatcher dispatcher,
+      ContainerCommandRequestProto requestProto,
+      WritableByteChannel stream) throws IOException {
+    final AtomicReference<Throwable> error = new AtomicReference<>();
+    final AtomicBoolean responseSeen = new AtomicBoolean(false);
+    final DataStreamObserver<ReadBlockResponse> observer =
+        new DataStreamObserver<ReadBlockResponse>() {
+          @Override
+          public void onNext(ReadBlockResponse response) {
+            responseSeen.set(true);
+            try {
+              writeReadBlockStreamResponse(stream, response.getResponse(),
+                  response.getData());
+            } catch (IOException e) {
+              error.compareAndSet(null, e);
+              throw new CompletionException(e);
+            } catch (RuntimeException e) {
+              error.compareAndSet(null, e);
+              throw e;
+            }
+          }
+
+          @Override
+          public void onError(Throwable throwable) {
+            error.set(throwable);
+          }
+
+          @Override
+          public void onCompleted() {
+          }
+
+        };
+
+    try (RandomAccessFileChannel blockFile = new RandomAccessFileChannel()) {
+      dispatcher.streamDataReadOnly(requestProto, observer, blockFile, null);
+    } catch (CompletionException e) {
+      if (e.getCause() instanceof IOException) {
+        throw (IOException) e.getCause();
+      }
+      throw new IOException("Failed to stream ReadBlock " + requestProto,
+          e.getCause() != null ? e.getCause() : e);
+    }
+
+    if (error.get() != null) {
+      throw new IOException("Failed to stream ReadBlock " + requestProto,
+          error.get());
+    }
+    if (!responseSeen.get()) {
+      throw new IOException("ReadBlock stream returned no responses: "
+          + requestProto);
+    }
+
+    stream.close();
+  }

Review Comment:
   streamReadBlock closes the WritableByteChannel only on the success path. If 
dispatcher.streamDataReadOnly throws or error/responseSeen checks throw, the 
channel is leaked and the client may hang waiting for EOF.



##########
hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/common/transport/server/ratis/ContainerStateMachine.java:
##########
@@ -846,6 +860,222 @@ public CompletableFuture<Message> query(Message request) {
     }
   }
 
+  @Override
+  public void query(Message request, WritableByteChannel stream) {
+    try {
+      metrics.incNumQueryStateMachineOps();
+      final ContainerCommandRequestProto requestProto =
+          message2ContainerCommandRequestProto(request);
+      if (requestProto.getCmdType() == Type.ReadBlock) {
+        streamReadBlock(dispatcher, requestProto, stream);
+      } else {
+        writeAndClose(stream, dispatchCommand(requestProto, null));
+      }
+    } catch (IOException e) {
+      metrics.incNumQueryStateMachineFails();
+      throw new CompletionException(e);
+    }
+  }
+
+  static void streamReadBlock(
+      ContainerDispatcher dispatcher,
+      ContainerCommandRequestProto requestProto,
+      WritableByteChannel stream) throws IOException {
+    final AtomicReference<Throwable> error = new AtomicReference<>();
+    final AtomicBoolean responseSeen = new AtomicBoolean(false);
+    final DataStreamObserver<ReadBlockResponse> observer =
+        new DataStreamObserver<ReadBlockResponse>() {
+          @Override
+          public void onNext(ReadBlockResponse response) {
+            responseSeen.set(true);
+            try {
+              writeReadBlockStreamResponse(stream, response.getResponse(),
+                  response.getData());
+            } catch (IOException e) {
+              error.compareAndSet(null, e);
+              throw new CompletionException(e);
+            } catch (RuntimeException e) {
+              error.compareAndSet(null, e);
+              throw e;
+            }
+          }
+
+          @Override
+          public void onError(Throwable throwable) {
+            error.set(throwable);
+          }
+
+          @Override
+          public void onCompleted() {
+          }
+
+        };
+
+    try (RandomAccessFileChannel blockFile = new RandomAccessFileChannel()) {
+      dispatcher.streamDataReadOnly(requestProto, observer, blockFile, null);
+    } catch (CompletionException e) {
+      if (e.getCause() instanceof IOException) {
+        throw (IOException) e.getCause();
+      }
+      throw new IOException("Failed to stream ReadBlock " + requestProto,
+          e.getCause() != null ? e.getCause() : e);
+    }
+
+    if (error.get() != null) {
+      throw new IOException("Failed to stream ReadBlock " + requestProto,
+          error.get());
+    }
+    if (!responseSeen.get()) {
+      throw new IOException("ReadBlock stream returned no responses: "
+          + requestProto);
+    }
+
+    stream.close();
+  }
+
+  private static void writeAndClose(
+      WritableByteChannel stream, ContainerCommandResponseProto response)
+      throws IOException {
+    try {
+      writeFully(stream, response.toByteString().asReadOnlyByteBuffer());
+    } finally {
+      stream.close();
+    }
+  }
+
+  private static void writeReadBlockStreamResponse(
+      WritableByteChannel stream, ContainerCommandResponseProto response,
+      ByteBuffer data) throws IOException {
+    writeFully(stream, encodeReadBlockStreamResponse(response, data));
+  }
+
+  private static void writeFully(WritableByteChannel stream, ByteBuffer buffer)
+      throws IOException {
+    while (buffer.hasRemaining()) {
+      final int position = buffer.position();
+      final int remaining = buffer.remaining();
+      final int written = stream.write(buffer);
+      if (written < 0) {
+        throw new IOException("EOF reached while writing ReadBlock stream 
response");
+      }
+      if (written == 0) {
+        throw new IOException("Zero bytes written to ReadBlock stream response 
channel");
+      }

Review Comment:
   writeFully throws when WritableByteChannel.write returns 0, but a zero-byte 
write is permitted (eg non-blocking channels or backpressure). Throwing here 
can cause spurious failures under load; it should retry/yield instead.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to