When the Countly server returns a non-JSON response (e.g., an HTML page from a 502 Bad Gateway or 503 Service Unavailable), Transport.processResponse() throws a JSONException that is not caught by Transport.send(). This uncaught exception prevents Tasks.running from being reset to null, causing Tasks.isRunning() to return true permanently. As a result, DefaultNetworking.check() never submits new send tasks, and the entire networking queue is deadlocked until the SDK is reinitialized.
Root Cause
Transport.processResponse() unconditionally parses the response body as JSON:
// Transport.java — processResponse()
JSONObject json = new JSONObject(response); // throws JSONException on HTML/non-JSON
Transport.send() catches only IOException:
// Transport.java — send()
try {
// ... HTTP request and processResponse() call
} catch (IOException e) {
// handles network errors
}
// JSONException (extends RuntimeException) is NOT caught
The exception propagates up to Tasks, where the executor wrapper has:
// Tasks.java — task execution
running = task.id; // ← set before execution
task.run(); // ← exception thrown here
running = null; // ← NEVER reached (not in a finally block)
Since running is never reset, Tasks.isRunning() returns true forever, and DefaultNetworking.check() skips submitting new tasks.
Steps to Reproduce
- Initialize the Countly SDK
- Queue some events/requests
- Have the Countly server return a non-JSON response (e.g., a reverse proxy returning 502/503 HTML)
- Observe that
isSending() returns true permanently and no further requests are ever sent
Expected Behavior
Transport.send() should catch RuntimeException (or at minimum JSONException) in addition to IOException, treating non-JSON responses as a transient failure with retry.
Tasks should reset running = null in a finally block to ensure the executor never gets permanently stuck regardless of exception type.
When the Countly server returns a non-JSON response (e.g., an HTML page from a 502 Bad Gateway or 503 Service Unavailable),
Transport.processResponse()throws a JSONException that is not caught byTransport.send(). This uncaught exception preventsTasks.runningfrom being reset to null, causingTasks.isRunning()to return true permanently. As a result,DefaultNetworking.check()never submits new send tasks, and the entire networking queue is deadlocked until the SDK is reinitialized.Root Cause
Transport.processResponse()unconditionally parses the response body as JSON:Transport.send()catches only IOException:The exception propagates up to
Tasks, where the executor wrapper has:Since running is never reset,
Tasks.isRunning()returns true forever, andDefaultNetworking.check()skips submitting new tasks.Steps to Reproduce
isSending()returns true permanently and no further requests are ever sentExpected Behavior
Transport.send()should catch RuntimeException (or at minimum JSONException) in addition to IOException, treating non-JSON responses as a transient failure with retry.Tasksshould resetrunning = nullin a finally block to ensure the executor never gets permanently stuck regardless of exception type.