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
-
Extract the attached AndroidHttpUploadCancelRepro.zip.
-
Attach a physical Android device.
-
Run:
-
After the app opens, tap Run upload cancellation repro.
-
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
Android framework version
net10.0-android
Affected platform version
Observed with .NET SDK
10.0.301, Android workload36.1.53Description
Xamarin.Android.Net.AndroidMessageHandlercancels aPUTrequest promptly, but the caller-requested cancellation is reported as a non-cancellation exception, for exampleSystem.IO.IOException/System.Net.WebExceptionwithSocket closed.The same repro using
SocketsHttpHandlerreportsTaskCanceledException/OperationCanceledException.This seems related to #5761, but it is a narrower cancellation-specific case in a different request phase:
WebExceptioninstead ofHttpRequestException.PUTrequest with a small request body where the server accepts the request but delays reading the body and sending response headers.Steps to Reproduce
Extract the attached AndroidHttpUploadCancelRepro.zip.
Attach a physical Android device.
Run:
.\run-repro.ps1After the app opens, tap
Run upload cancellation repro.Capture logcat:
adb logcat -s HttpUploadCancelRepro:DThe repro server delays reading the request body:
The app starts a
PUTrequest with a 64 KiB body and cancels the suppliedCancellationTokenafter about 750 ms.Expected Behavior
Since the supplied
CancellationTokenis cancelled by the caller,SendAsync(..., cancellationToken)should surface anOperationCanceledExceptionsubtype.Actual Behavior
With
AndroidMessageHandler, the request is cancelled promptly, but the observed exception is not anOperationCanceledExceptionsubtype.Observed output from the attached repro:
Did you find any workaround?
A caller can add a broad catch filter such as:
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