Andrey Yarovoy created HDDS-17648:
-------------------------------------

             Summary: Key write hangs when its Ratis client is closed while a 
watch-for-commit is still pending
                 Key: HDDS-17648
                 URL: https://issues.apache.org/jira/browse/HDDS-17648
             Project: Apache Ozone
          Issue Type: Improvement
          Components: Ozone Client
            Reporter: Andrey Yarovoy


Since HDDS-17491, `TestKeyInputStream#readAfterReplication(boolean)[2]` 
intermittently fails on master with
`readAfterReplication(boolean) timed out after 5 minutes` (integration 
(client), run 41300247417 on 20da41e535).
Locally it fails in 2 of 6 runs on 20da41e535 and in 0 of 6 runs on the commit 
before it (4ade0f1e9b). The test
thread is stuck writing the test key, not reading it.

**What happens** (from the test log of a failing local run):

1. The write goes to a pipeline that still includes the datanode the previous 
test case stopped, so a
watch-for-commit fails (`ALL_COMMITTED way commit failed`, 
`RaftRetryFailureException`), and
`BlockOutputStream.handleFlush(true)` fails ("Failed to flush").
2. `handleFlush(true)` calls `cleanup(false)` in its `finally` block, which 
releases the Ratis client, while the watch
for the other putBlock of the same key is still being retried by that client.
3. The released client has no references left, and its `XceiverClientManager` 
cache entry has already expired:
`expireAfterAccess` counts from the last cache access, which was the start of 
the write. The periodic
`cleanUp()` added by HDDS-17491 removes the entry, and the client is closed.
4. The pending watch then fails:
```
GrpcClientRpc - client-3F9447C48097: Failed 
RaftClientRequest:client-3F9447C48097->...
org.apache.ratis.protocol.exceptions.AlreadyClosedException: 
client-3F9447C48097 is already CLOSED
XceiverClientRatis - ALL_COMMITTED way commit failed on pipeline ...
AlreadyClosedException: org.apache.ratis.client.impl.RaftClientImpl@144b857d is 
closed
```
5. `XceiverClientRatis.watchForCommit` handles the failure by sending a 
MAJORITY_COMMITTED watch on the same,
now closed, client, from inside its `exceptionally` handler. The future it 
returns is never completed.
6. `BlockOutputStream.waitForAllPendingFlushes()` waits for all flush futures 
with an untimed
`allPendingFlushFutures.get()`, so the write never returns. In the test, JUnit 
interrupts it after 5 minutes;
the write then retries on a new block and succeeds.

Which of the two ways leaves the future incomplete is not confirmed yet: either 
Ratis returns a watch future that
never completes on a closed client, or the call throws inside the 
`exceptionally` handler, where the exception is
swallowed and `replyFuture` is never completed.

**Why it starts with HDDS-17491:** releasing a client while its async work is 
still pending (step 2) predates
HDDS-17491. Before it, an expired entry was only removed during a later cache 
access, which did not happen in this
window in a single-client test, so the client stayed open. HDDS-17491 removes 
expired entries every idle threshold
(10 s by default), so the window is hit regularly. In a busy client, any cache 
access after the threshold could
already trigger the same hang before HDDS-17491.

**Impact:** a key write that hits a failed datanode can hang indefinitely 
instead of retrying on another block.



--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

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

Reply via email to