[ 
https://issues.apache.org/jira/browse/HDDS-17443?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Gargi Jaiswal reassigned HDDS-17443:
------------------------------------

    Assignee: Gargi Jaiswal

> Domain socket Receiver logs an NPE for every response without a block file 
> and sends no reply to a GetBlock that is not granted access
> --------------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: HDDS-17443
>                 URL: https://issues.apache.org/jira/browse/HDDS-17443
>             Project: Apache Ozone
>          Issue Type: Bug
>    Affects Versions: 2.3.0
>            Reporter: Siyao Meng
>            Assignee: Gargi Jaiswal
>            Priority: Minor
>         Attachments: 
> CR-1-reply-to-getblock-without-block-file-in-domain-socket-receiver.patch, 
> TestBugCR1NullBlockFileClose.java, 
> TestBugMC2GetBlockWithoutFlagNoResponse.java
>
>
> h3. Mechanism
> {{Receiver.ProcessRequestTask.run}} attaches a block file to the response 
> only for a GetBlock that returns SUCCESS, and both places that use the file 
> assume this is the same as "the response has a file":
> * {{Receiver.sendResponse}} closes {{entry.getFile()}} in its {{finally}} 
> block without a null check, and catches only {{IOException}}. For every 
> response without a file (Echo, a GetBlock that does not succeed, the 
> UNSUPPORTED_REQUEST reply) the close throws a {{NullPointerException}} after 
> the response has been flushed. The exception skips 
> {{ContainerMetrics.incContainerLocalOpsLatencies}} and is logged by 
> {{ProcessRequestTask.run}} as "Failed to processRequest" at ERROR with the 
> stack trace.
> * {{KeyValueHandler.handleGetBlock}} opens and parks a block file only when 
> the request sets {{requestShortCircuitAccess}} and the domain socket server 
> is started, and returns SUCCESS with {{shortCircuitAccessGranted=false}} 
> otherwise. {{ProcessRequestTask.run}} fetches the file for every SUCCESS 
> GetBlock, {{Objects.requireNonNull}} throws, the exception is only logged, 
> and no response is sent.
> HDDS-11627 closed the block's stream only when there was one; HDDS-12883 
> brought the close back for the block file without that null check. The 
> non-null requirement on the file dates from HDDS-11627.
> h3. Trigger
> * Logged NPE: every Echo, and every short-circuit GetBlock that does not 
> succeed. The most frequent one is the routine fallback when the local 
> datanode has not applied the PutBlock yet ({{UNKNOWN_BCSID}}, see the comment 
> in {{BlockInputStream.getBlockDataUsingClient}}). Run for {{UNKNOWN_BCSID}} 
> and {{NO_SUCH_BLOCK}}; other error results such as {{CONTAINER_NOT_FOUND}} or 
> a token failure take the same path.
> * No reply: a GetBlock on the domain socket without 
> {{requestShortCircuitAccess}}. The field is optional and defaults to false, 
> so this is a valid request, but the in-tree reader 
> ({{BlockInputStream.getBlockDataUsingSCClient}}) always sets it. By code 
> reading the same happens when the domain socket server is stopped between the 
> block lookup and the {{isStarted()}} check (not run); there {{closeAllPeers}} 
> disconnects the client anyway.
> h3. Impact
> * Every Echo and every failed short-circuit GetBlock adds an ERROR with a 
> stack trace to the datanode log, and {{localLatencyNsEcho}} and 
> {{localLatencyNsGetBlock}} miss those samples ({{numLocalEcho}} and 
> {{numLocalGetBlock}} are correct). The client receives a correct response and 
> falls back to gRPC as designed. Reproduced. No in-tree reader sends Echo on 
> the domain socket (the Echo load generator in freon uses gRPC), so in 
> practice the errors come from failed GetBlocks.
> * A client that sends a GetBlock without the flag gets no reply and waits for 
> its own read timeout. Only that request is affected: the read thread goes 
> back to the pool and the same connection serves the next request normally 
> (reproduced with a single read thread). Other connections and clients are not 
> affected. No in-tree client sends such a request, so today this only matters 
> for other clients of the protocol.
> h3. Reproduction
> [^TestBugCR1NullBlockFileClose.java] and 
> [^TestBugMC2GetBlockWithoutFlagNoResponse.java] 
> ({{hadoop-ozone/integration-test}}) run a real {{XceiverServerDomainSocket}} 
> with {{HddsDispatcher}} and {{KeyValueHandler}} and send requests framed as 
> the short-circuit client frames them. They need the native libhadoop 
> ({{-Phadoop-native-lib}}) and change no source. Both pass on unmodified 
> source, so a passing test means the defect is present.
> * The first sends an Echo, a GetBlock with BCSID 5 against a replica at 0 
> ({{UNKNOWN_BCSID}}) and a GetBlock of a missing block ({{NO_SUCH_BLOCK}}). 
> All three get the right response, and the datanode logs three 
> {{NullPointerException: Cannot invoke "java.io.RandomAccessFile.close()"}} 
> errors. {{numLocalGetBlock}} is 3 with 1 latency sample (the granted control 
> request) and {{numLocalEcho}} is 1 with 0 samples.
> * The second sends a GetBlock with the flag (answered with its descriptor), 
> then the same GetBlock without the flag: no reply within the 3 s read 
> timeout, and the datanode logs {{NullPointerException: Failed to get block 
> file for block}}. A third GetBlock on the same connection is answered 
> normally.
> h3. Patch
> [^CR-1-reply-to-getblock-without-block-file-in-domain-socket-receiver.patch], 
> against 69d77beff60b9106e2517c36a7a1b2cc726af568. It also applies to master 
> at 1acf315d546.
> Two changes in {{Receiver}}: {{sendResponse}} closes the block file only when 
> there is one, and {{ProcessRequestTask.run}} fetches the parked file only 
> when the GetBlock response grants short-circuit access. A SUCCESS GetBlock 
> that grants none is then sent as the handler built it, with its BlockData and 
> without a descriptor, and by code reading the in-tree client takes its 
> existing "Short-circuit access is denied" path (fails the request, disables 
> short-circuit for the configured interval) and reads through gRPC. 
> {{KeyValueHandler.handleGetBlock}} grants access exactly when it parks a 
> file, so the handler is unchanged. The two changes go together: with only the 
> second one, the reply that is now sent would hit the first problem right 
> after it is flushed. The new tests pass the read executor argument of the 
> {{XceiverServerDomainSocket}} constructor like the existing tests; after the 
> change in HDDS-17442, which removes that argument, they drop it as well.
> Covered by {{testResponseWithoutBlockFile}} and 
> {{testGetBlockWithoutShortCircuitAccess}} in the existing 
> {{TestXceiverServerDomainSocket}}. Without the change the first fails because 
> the Receiver logged "Failed to processRequest" for the Echo and the failed 
> GetBlock, and the second fails with {{SocketTimeoutException}}. With it 
> {{TestXceiverServerDomainSocket}}, {{TestXceiverClientManagerSC}}, 
> {{TestLocalChunkInputStream}} and {{TestDomainSocketFactory}} (19 tests) and 
> {{TestKeyValueHandler}} (43 tests) pass, checkstyle is clean, and both 
> reproductions fail because the error is gone and the reply arrives.
> Found by TLA+ model checking and code review of the datanode short-circuit 
> read paths under HDDS-15926, on commit 
> 69d77beff60b9106e2517c36a7a1b2cc726af568. Checked against HDDS issues and 
> apache/ozone pull requests for duplicates before filing. Related but not 
> duplicates: HDDS-12883 (added the close of the block file after it is sent), 
> HDDS-16918 (fallback to gRPC is not logged on the datanode), HDDS-16335 
> (concurrent access on the client side of the domain socket). The attached 
> patch is a proposal for review. Generated with Specula (Claude Opus 5.5).



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