Skip to content

AndroidMessageHandler maps cancelled PUT request to IOException/WebException instead of OperationCanceledException #11624

@tipa

Description

@tipa

Android framework version

net10.0-android

Affected platform version

Observed with .NET SDK 10.0.301, Android workload 36.1.53

Description

Xamarin.Android.Net.AndroidMessageHandler cancels a PUT request promptly, but the caller-requested cancellation is reported as a non-cancellation exception, for example System.IO.IOException / System.Net.WebException with Socket closed.

The same repro using SocketsHttpHandler reports TaskCanceledException / OperationCanceledException.

This seems related to #5761, but it is a narrower cancellation-specific case in a different request phase:

Steps to Reproduce

  1. Extract the attached AndroidHttpUploadCancelRepro.zip.

  2. Attach a physical Android device.

  3. Run:

    .\run-repro.ps1
  4. After the app opens, tap Run upload cancellation repro.

  5. Capture logcat:

    adb logcat -s HttpUploadCancelRepro:D

The repro server delays reading the request body:

http://127.0.0.1:5092/upload?readDelayMs=10000

The app starts a PUT request with a 64 KiB body and cancels the supplied CancellationToken after about 750 ms.

Expected Behavior

Since the supplied CancellationToken is cancelled by the caller, SendAsync(..., cancellationToken) should surface an OperationCanceledException subtype.

Actual Behavior

With AndroidMessageHandler, the request is cancelled promptly, but the observed exception is not an OperationCanceledException subtype.

Observed output from the attached repro:

Expected for caller cancellation: OperationCanceledException

BUG  Default ctor
     actual: System.Net.WebException
     cancellation exception: NO
     token canceled: YES (+19 ms after token)
     message: Socket closed
     inner: Java.Net.SocketException: Socket closed
     handler: HttpClientHandler -> AndroidMessageHandler

BUG  AndroidMessageHandler
     actual: System.Net.WebException
     cancellation exception: NO
     token canceled: YES (+6 ms after token)
     message: Socket closed
     inner: Java.Net.SocketException: Socket closed
     handler: AndroidMessageHandler

OK   SocketsHttpHandler
     actual: System.Threading.Tasks.TaskCanceledException
     cancellation exception: YES
     token canceled: YES (+77 ms after token)
     message: The operation was canceled.
     inner: System.Threading.Tasks.TaskCanceledException: The operation was canceled.
     handler: SocketsHttpHandler

Did you find any workaround?

A caller can add a broad catch filter such as:

catch (Exception ex) when (cancellationToken.IsCancellationRequested)

but this is only a workaround. It treats all exceptions after cancellation as cancellation and can hide unrelated failures that race with token cancellation.

Relevant log output

Metadata

Metadata

Assignees

No one assigned

    Labels

    Area: App RuntimeIssues in `libmonodroid.so`.needs-triageIssues that need to be assigned.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions