peterxcli commented on code in PR #11143:
URL: https://github.com/apache/ozone/pull/11143#discussion_r3880856866


##########
hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/storage/BlockDataStreamOutput.java:
##########
@@ -541,8 +541,13 @@ public void hsync() throws IOException {
       if (!isClosed()) {
         handleFlush(false);
       }
-    } catch (Exception e) {
-
+    } catch (IOException e) {
+      throw e;
+    } catch (InterruptedException e) {
+      Thread.currentThread().interrupt();
+      handleInterruptedException(e, false);
+    } catch (ExecutionException | RuntimeException e) {
+      handleExecutionException(e);

Review Comment:
   Two things here:
   
   **`catch (IOException e) { throw e; }` is redundant** — `handleFlush` 
declares `IOException`, so it propagates on its own. `close()` twelve lines 
below (`:611`) already has exactly the right shape: only `ExecutionException` 
and `InterruptedException`.
   
   **`| RuntimeException` looks over-broad and unnecessary.** I removed it and 
all 14 tests in `TestBlockDataStreamOutput` still pass, so nothing in this 
class's test coverage needs it. It also funnels programming errors (NPE, 
`Preconditions` failures) through `setIoException`, which poisons the stream's 
`ioException` field so every subsequent call fails with a misleading 
`Unexpected Storage Container Exception: java.lang.NullPointerException`. If a 
specific runtime failure motivated adding it in the "Fix the test failure" 
commit, could you say which one? Otherwise I'd drop it.
   
   ```suggestion
       } catch (ExecutionException e) {
         handleExecutionException(e);
       } catch (InterruptedException e) {
         Thread.currentThread().interrupt();
         handleInterruptedException(e, false);
   ```



##########
hadoop-hdds/client/src/test/java/org/apache/hadoop/hdds/scm/storage/TestBlockDataStreamOutput.java:
##########
@@ -171,10 +171,10 @@ void hsyncPropagatesIOException() throws Exception {
 
     // hsync should propagate the IOException from the failed putBlock
     assertThrows(IOException.class, stream::hsync, "hsync() must propagate 
IOException from failed putBlock");
-    stream.close();
+    stream.cleanup(false);

Review Comment:
   Swapping `close()` for `cleanup(false)` sidesteps a contract worth pinning. 
`close()` after a failed hsync throws, correctly:
   
   ```
   java.io.IOException: Unexpected Storage Container Exception: 
java.util.concurrent.CompletionException: ... java.io.IOException: simulated 
putBlock fail
   ```
   
   That is exactly the "the caller must not think the sync succeeded" property 
this PR is about, so I'd assert it rather than route around it. `close()` still 
releases the client through its own `finally { cleanup(false); }`, so nothing 
leaks. Verified green.
   
   ```suggestion
       assertThrows(IOException.class, stream::close);
   ```



##########
hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/io/KeyDataStreamOutput.java:
##########
@@ -389,6 +389,9 @@ private void handleFlushOrClose(StreamAction op) throws 
IOException {
             try {
               handleStreamAction(entry, op);
             } catch (IOException ioe) {
+              if (op == StreamAction.HSYNC) {
+                throw ioe;
+              }

Review Comment:
   This is my main concern, and it's outside HDDS-15225's stated scope (the 
Jira is scoped to `BlockDataStreamOutput.hsync()`; this hunk arrived only in 
the "Fix the test failure" commit).
   
   **It inverts the Ratis behavior at the key level.** 
`KeyOutputStream.handleFlushOrClose` (`KeyOutputStream.java:585`) routes HSYNC 
failures through `handleException(entry, ioe, false)` and retries exactly like 
writes — no HSYNC special case. The PR description says this "aligns the 
DataStream hsync with the Ratis behavior", which is true of the block-level fix 
but the opposite of true here.
   
   **The recovery it disables genuinely works.** With this hunk reverted, on 
the two-pipeline test added in the same commit, I instrumented the run:
   
   ```
   p2 received=200  putBlocks=1  watches=1
   ```
   
   `handleException` excludes the failed pipeline, allocates a new block, 
replays the 200 buffered bytes (retained in `bufferList` until acked), commits 
them with a successful putBlock + watchForCommit, and only then calls 
`hsyncKey` once. The `hsyncKey` is honest.
   
   **Practical cost:** `CLOSED_CONTAINER_IO` is routine, and 
`containerCloseTriggersRetryOnNewBlock` in this test class asserts the 
DataStream path recovers from it. With this hunk, the same event during 
`hsync()` becomes a hard failure instead of a transparent retry.
   
   **Second-order effect:** because the enclosing `catch (Exception e) { 
markStreamClosed(); throw e; }` at `:400` now fires on the first IOException, 
one failed hsync permanently closes the whole stream. I probed what `close()` 
does next:
   
   ```
   close() after failed hsync returned normally
   commitKey was NEVER called after close()
   ```
   
   `close()` hits `if (closed) return;` and reports success without committing 
the key — so `try { write; hsync } finally { close }` swallows the failure 
entirely.
   
   I'd drop this hunk. All three previously-skipped tests pass without it 
(verified). If the fail-fast semantics are wanted, they seem worth their own 
subtask under HDDS-15169 with the retry-vs-fail-fast tradeoff written down.
   
   ```suggestion
   ```



##########
hadoop-hdds/client/src/test/java/org/apache/hadoop/hdds/scm/storage/TestBlockDataStreamOutput.java:
##########
@@ -187,7 +187,7 @@ void hsyncPropagatesWatchFailure() throws Exception {
 
     // hsync should propagate the watch failure
     assertThrows(IOException.class, stream::hsync, "hsync() must propagate 
IOException from failed watchForCommit");
-    stream.close();
+    stream.cleanup(false);

Review Comment:
   Same as above — asserting that `close()` throws documents the post-failure 
contract, and `close()`'s own `finally` still calls `cleanup(false)`.
   
   ```suggestion
       assertThrows(IOException.class, stream::close);
   ```



##########
hadoop-ozone/client/src/test/java/org/apache/hadoop/ozone/client/io/TestKeyDataStreamOutput.java:
##########
@@ -227,15 +227,14 @@ void hsyncCallsOmHsyncKey() throws Exception {
     }
   }
 
-//  @Test - skipped as it fails now
+  @Test
   void hsyncWithBlockErrorDoesNotCallOmHsync() throws Exception {
-    MockDatanodePipeline pipeline = new MockDatanodePipeline();
+    MockDatanodePipeline pipeline1 = new MockDatanodePipeline(new BlockID(1, 
1));
+    MockDatanodePipeline pipeline2 = new MockDatanodePipeline(new BlockID(2, 
2));
     // First putBlock will fail
-    pipeline.failPutBlockAfter(0, () -> new IOException("putBlock failed"));
-
-    OzoneManagerProtocol omClient = createOmClient(pipeline);
-
-    KeyDataStreamOutput stream = createKeyStream(omClient, pipeline);
+    pipeline1.failPutBlockAfter(0, () -> new IOException("putBlock failed"));
+    OzoneManagerProtocol omClient = createOmClient(pipeline1, pipeline2);
+    KeyDataStreamOutput stream = createKeyStream(omClient, pipeline1, 
pipeline2);

Review Comment:
   This rewrite is what made the `KeyDataStreamOutput` change necessary. The 
original used one pipeline whose putBlock always fails, so retries genuinely 
exhaust and hsync throws. Adding a **healthy** `pipeline2` makes recovery 
possible, which then required the production change to keep `verify(omClient, 
never()).hsyncKey(...)` true.
   
   With the original body restored and only the `BlockDataStreamOutput.hsync()` 
fix applied, all 7 tests in this class pass.
   
   Worth settling explicitly: should the assertion be *"hsync never calls 
hsyncKey after a block error"*, or *"hsync doesn't call hsyncKey until the data 
is durable somewhere"*? The Ratis path implements the second.
   
   Minor, while here: the trailing `stream.close()` at `:246` is dead under the 
current patch (`closed` is already `true` after `markStreamClosed()`), and the 
stream isn't in try-with-resources, so it leaks if an assertion fails.
   
   ```suggestion
       MockDatanodePipeline pipeline = new MockDatanodePipeline();
       // First putBlock will fail
       pipeline.failPutBlockAfter(0, () -> new IOException("putBlock failed"));
   
       OzoneManagerProtocol omClient = createOmClient(pipeline);
   
       KeyDataStreamOutput stream = createKeyStream(omClient, pipeline);
   ```



-- 
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