Skip to content

Conversation

@shubham1g5
Copy link
Contributor

@shubham1g5 shubham1g5 commented Oct 9, 2025

Product Description

Network Interface changes to facilitate more specific error handling

Special deploy instructions

  • This PR can be deployed after merge with no further considerations.

Rollback instructions

  • This PR can be reverted after deploy with no further considerations.

Review

  • The set of people pinged as reviewers is appropriate for the level of risk of the change.

Duplicate PR

Automatically duplicate this PR as defined in contributing.md.

Summary by CodeRabbit

  • New Features

    • Enhanced network error handling to read detailed server error information, enabling clearer error messages and diagnostics during failures.
  • Bug Fixes

    • Fixed incomplete processing of client (4xx) error responses, reducing failed syncs and downloads.
    • Prevented crashes when servers return error bodies by safely accessing error streams, improving overall app stability and reliability during connectivity issues.

@coderabbitai
Copy link

coderabbitai bot commented Oct 9, 2025

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

The HttpResponseProcessor interface now requires a ResponseStreamAccessor when handling client errors. ResponseStreamAccessor adds getErrorResponseStream(). ModernHttpRequester implements and exposes error stream access, and passes the accessor into processClientError during client-error responses.

Changes

Cohort / File(s) Summary
Interfaces: HTTP processing
src/main/java/org/commcare/core/interfaces/HttpResponseProcessor.java
Updated method signature: processClientError now requires (int responseCode, ResponseStreamAccessor streamAccessor).
Interfaces: Stream access
src/main/java/org/commcare/core/interfaces/ResponseStreamAccessor.java
Added method: InputStream getErrorResponseStream().
Network requester updates
src/main/java/org/commcare/core/network/ModernHttpRequester.java
Added support to retrieve error response stream: new public @Nullable InputStream getErrorResponseStream(Response<ResponseBody> response) and implementation in anonymous ResponseStreamAccessor. Updated error handling to call processClientError(responseCode, streamAccessor) and expose errorBody().byteStream() when present.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Caller
  participant ModernHttpRequester as ModernHttpRequester
  participant Server
  participant Processor as HttpResponseProcessor
  participant Accessor as ResponseStreamAccessor

  Caller->>ModernHttpRequester: makeRequestAndProcess(...)
  ModernHttpRequester->>Server: HTTP request
  Server-->>ModernHttpRequester: HTTP response

  rect rgba(200,240,255,0.3)
    note over ModernHttpRequester: Build ResponseStreamAccessor<br/>(getResponseStream, getErrorResponseStream)
    ModernHttpRequester->>Accessor: create anonymous accessor
  end

  alt 2xx Success
    ModernHttpRequester->>Processor: processSuccess(streamAccessor)
  else 4xx Client Error
    ModernHttpRequester->>Processor: processClientError(code, streamAccessor)
    note over Accessor: getErrorResponseStream()<br/>reads error body stream
  else 5xx/Other
    ModernHttpRequester->>Processor: processServerFailure(code)
  end

  Processor-->>Caller: processing result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

I nibbled the wires, then hopped to the stream,
Found error brooks bubbling where failures gleam.
With a twitch of an ear, I pass along clues—
Client-code crumbs and response-body news.
Now when bytes go astray, I’m keen to confess:
“Sip the error river; decode the mess.” 🐇💻

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly conveys the primary purpose of the changeset—enabling enhanced error response logging for the mobile client—which aligns with the modifications to the network interface and response stream accessor. It is specific enough for reviewers to understand the main intent without listing implementation details.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2361d31 and 89841dc.

📒 Files selected for processing (3)
  • src/main/java/org/commcare/core/interfaces/HttpResponseProcessor.java (1 hunks)
  • src/main/java/org/commcare/core/interfaces/ResponseStreamAccessor.java (1 hunks)
  • src/main/java/org/commcare/core/network/ModernHttpRequester.java (4 hunks)

public interface ResponseStreamAccessor {
InputStream getResponseStream() throws IOException;

InputStream getErrorResponseStream();
Copy link

@coderabbitai coderabbitai bot Oct 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Align error stream accessor contract with I/O realities

Error bodies are optional and may require disk/network I/O to materialize. Without @Nullable and a checked exception, implementors must either fabricate values or silently swallow failures, which breaks parity with getResponseStream(). Please strengthen the contract so callers can both detect “no error body” and surface I/O issues.

 import java.io.IOException;
 import java.io.InputStream;
+import javax.annotation.Nullable;

 public interface ResponseStreamAccessor {
     InputStream getResponseStream() throws IOException;

-    InputStream getErrorResponseStream();
+    @Nullable
+    InputStream getErrorResponseStream() throws IOException;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
InputStream getErrorResponseStream();
import java.io.IOException;
import java.io.InputStream;
import javax.annotation.Nullable;
public interface ResponseStreamAccessor {
InputStream getResponseStream() throws IOException;
@Nullable
InputStream getErrorResponseStream() throws IOException;
}
🤖 Prompt for AI Agents
In src/main/java/org/commcare/core/interfaces/ResponseStreamAccessor.java around
line 9, the getErrorResponseStream method lacks nullable and checked-I/O
semantics; update its signature to mirror getResponseStream by annotating the
return as @Nullable and declaring it throws IOException (and add the necessary
imports), so implementors can return null for absent error bodies and surface
I/O failures instead of fabricating values or swallowing exceptions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch the commit details to see what changes were made
gh api repos/dimagi/commcare-core/commits/36d172699825c3d048ca6609a0cb7b55686cb456 --jq '.commit.message, .files[].patch'

Length of output: 2254


@shubham1g5, I see you've taken a different approach that centralizes error handling! 👍

Instead of propagating IOException through the interface, you're:

  1. ✅ Added @Nullable to getErrorResponseStream() (as suggested)
  2. ✅ Handling exceptions at the call site in ModernHttpRequester.processResponse() with a try-catch-finally block
  3. ✅ Managing stream lifecycle (opening and closing) in the caller
  4. ✅ Simplified HttpResponseProcessor.processClientError() to take InputStream directly instead of ResponseStreamAccessor

This design centralizes error handling and resource management, which is a valid alternative to what I originally suggested. The core concern—handling absent error bodies and I/O failures gracefully—is now addressed.


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

}
} else if (responseCode >= 400 && responseCode < 500) {
responseProcessor.processClientError(responseCode);
responseProcessor.processClientError(responseCode, streamAccessor);
Copy link

@coderabbitai coderabbitai bot Oct 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Ensure error streams are closed after client handling

We now expose an InputStream for error bodies, but unlike the success path we never close it. That leaves file descriptors (or cached files) dangling if the processor only reads part of the stream or forgets to close it. Wrap the accessor so we can close the cached stream after processClientError returns.

-        } else if (responseCode >= 400 && responseCode < 500) {
-            responseProcessor.processClientError(responseCode, streamAccessor);
+        } else if (responseCode >= 400 && responseCode < 500) {
+            final InputStream[] errorStreamHolder = new InputStream[1];
+            ResponseStreamAccessor closingAccessor = new ResponseStreamAccessor() {
+                @Override
+                public InputStream getResponseStream() throws IOException {
+                    return streamAccessor.getResponseStream();
+                }
+
+                @Nullable
+                @Override
+                public InputStream getErrorResponseStream() throws IOException {
+                    if (errorStreamHolder[0] == null) {
+                        errorStreamHolder[0] = streamAccessor.getErrorResponseStream();
+                    }
+                    return errorStreamHolder[0];
+                }
+
+                @Override
+                public String getApiVersion() {
+                    return streamAccessor.getApiVersion();
+                }
+            };
+
+            try {
+                responseProcessor.processClientError(responseCode, closingAccessor);
+            } finally {
+                StreamsUtil.closeStream(errorStreamHolder[0]);
+            }
🤖 Prompt for AI Agents
In src/main/java/org/commcare/core/network/ModernHttpRequester.java around line
184, the error-path exposes an InputStream via the provided stream accessor but
never closes it; wrap the accessor usage so the cached/error InputStream is
closed after responseProcessor.processClientError returns. Concretely, obtain
the InputStream from the accessor (or create a small wrapper accessor), call
processClientError inside a try block, and in a finally block close the
retrieved/cached stream (or call the accessor's close method) to ensure file
descriptors/cached files are released while preserving the existing contract
with responseProcessor.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch the commit details using GitHub CLI
gh api repos/dimagi/commcare-core/commits/36d172699825c3d048ca6609a0cb7b55686cb456 --jq '.commit.message, .files[].patch' 

# Get the full diff for this commit
gh api repos/dimagi/commcare-core/commits/36d172699825c3d048ca6609a0cb7b55686cb456 --jq '.files[] | select(.filename | contains("ModernHttpRequester.java")) | .patch'

Length of output: 3770


@shubham1g5, perfect! The fix looks great. You've taken a cleaner approach than my suggestion by:

  1. Obtaining the stream in the parent caller (processResponse) rather than wrapping the accessor
  2. Closing it in a finally block to guarantee cleanup, even if processClientError throws an exception
  3. Simplifying the API by passing the InputStream directly instead of the ResponseStreamAccessor
  4. Adding proper exception handling with Logger.exception to catch any errors during processing

This approach is now consistent with how the success path handles streams (both obtain the stream, process it, and close it in a finally block). The resource management is solid and the code is easier to understand.

🐰✨
^_^
(•‿•)

🎯 <
Nice fix!


🧬 Code Graph Analysis Results

<src/main/java/org/commcare/util/NetworkStatus.java> (lines 16-30)

public class NetworkStatus {
    public static boolean isCaptivePortal() {
        String captivePortalURL = "http://www.commcarehq.org/serverup.txt";
        CommCareNetworkService commCareNetworkService =
                CommCareNetworkServiceGenerator.createNoAuthCommCareNetworkService();
        try {
            Response<ResponseBody> response =
                    commCareNetworkService.makeGetRequest(captivePortalURL, new HashMap<>()).execute();
            return response.code() == 200 && !"success".equals(response.body().string());
        } catch (IOException e) {
            Logger.log(LogTypes.TYPE_WARNING_NETWORK, "Detecting captive portal failed with exception" + e.getMessage());
            return false;
        }
    }
}

If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

@shubham1g5 shubham1g5 marked this pull request as ready for review October 13, 2025 10:29
Copy link
Contributor

@OrangeAndGreen OrangeAndGreen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Glad to see this improvement!

try {
errorStream = streamAccessor.getErrorResponseStream();
responseProcessor.processClientError(responseCode, errorStream);
} catch (Exception e) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious about the generic Exception catch here, is there something more specific that we're expecting?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, I just took a fail safe approach for anything related to error handling as it's not critical for us to know about these failures to justify crashing and also a catch here not have more side-effects than showing a non-specific error.

@shubham1g5 shubham1g5 merged commit d14fc42 into commcare_2.60 Oct 16, 2025
2 checks passed
@shubham1g5 shubham1g5 deleted the errorLogging branch October 16, 2025 14:27
@coderabbitai coderabbitai bot mentioned this pull request Oct 20, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants