Repository navigation
ObjectDisposedException when retrying POST request using AndroidClientHandler #2901
Description
ObjectDisposedException when retrying POST request using AndroidClientHandler
Steps to Reproduce
- Implement AndroidClientHandler that will retry the same request
public class NativeClientHandler : AndroidClientHandler
{
bool retry = true;
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
HttpResponseMessage response = await BaseSendAsync(request, cancellationToken);
if (retry)
{
retry = !retry;
return await SendAsync(request, cancellationToken);
}
return response;
}
internal Task<HttpResponseMessage> BaseSendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return base.SendAsync(request, cancellationToken);
}
}Notice that same implementation above for HttpClientHandler works just fine
public class ManagedClientHandler : HttpClientHandler
{
bool retry = true;
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
HttpResponseMessage response = await BaseSendAsync(request, cancellationToken);
if (retry)
{
retry = !retry;
return await SendAsync(request, cancellationToken);
}
return response;
}
internal Task<HttpResponseMessage> BaseSendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return base.SendAsync(request, cancellationToken);
}
}- Send POST request that will be re-sent
Use case: server may respond with JSON error so client can challenge for credential; thus, sending the same POST request with authorization header or parameter.
EventHandler clickedHandler = async (a, b) =>
{
var dialog = new Android.App.AlertDialog.Builder(this);
try
{
if (a == button)
{
var client = new System.Net.Http.HttpClient(new NativeClientHandler());
var response = await client.PostAsync(new Uri("http://httpbin.org/post"), new System.Net.Http.FormUrlEncodedContent(new[] { new KeyValuePair<string, string>("f", "json") }));
}
if (a == button1)
{
var client = new System.Net.Http.HttpClient(new ManagedClientHandler());
var response = await client.PostAsync(new Uri("http://httpbin.org/post"), new System.Net.Http.FormUrlEncodedContent(new[] { new KeyValuePair<string, string>("f", "json") }));
dialog.SetMessage(await response.Content.ReadAsStringAsync());
}
dialog.SetTitle("Success!");
}
catch (Exception error)
{
Exception ex = error;
if (ex is AggregateException)
{
ex = ((AggregateException)ex).GetBaseException();
}
dialog.SetTitle(ex.GetType().Name);
dialog.SetMessage(ex.Message);
}
RunOnUiThread(() => dialog.Show());
};
button.Click += clickedHandler;
button1.Click += clickedHandlerExpected Behavior
Following request should have succeeded.
Actual Behavior
Following request fails with ObjectDisposedException "Cannot access a closed Stream"
Version Information
Microsoft Visual Studio Enterprise 2017
Version 15.9.9
VisualStudio.15.Release/15.9.9+28307.518
Microsoft .NET Framework
Version 4.7.03190
Mono Debugging for Visual Studio 4.13.12-pre (9bc9548)
Support for debugging Mono processes with Visual Studio.
VisualStudio.Mac 1.0
Mac Extension for Visual Studio
Xamarin 4.12.3.80 (d15-9@914127c74)
Visual Studio extension to enable development for Xamarin.iOS and Xamarin.Android.
Xamarin Designer 4.16.13 (45a16efd4)
Visual Studio extension to enable Xamarin Designer tools in Visual Studio.
Xamarin Templates 1.1.128 (6f5ebb2)
Templates for building iOS, Android, and Windows apps with Xamarin and Xamarin.Forms.
Xamarin.Android SDK 9.1.7.0 (HEAD/ba9da7a76)
Xamarin.Android Reference Assemblies and MSBuild support.
Xamarin.iOS and Xamarin.Mac SDK 12.2.1.15 (d60abd1)
Xamarin.iOS and Xamarin.Mac Reference Assemblies and MSBuild support.
Activity
IndexOutOfLimit commentedon Jul 8, 2020
Any update on this as I am also facing this issue and it needs to be resolved asap on Prod env.
yma44 commentedon Sep 22, 2020
I also started to have the same issue after upgrading.
from Visual Studio Enterprise 2019 16,2,4 (Xamarin.Andoid 9.4.1.1)
to Visual Studio Enterprise 2019 16,4.12 (Xamarin.Android 10.1.4.0 (d16-4/e44d1ae) )
We are doing the similar logic, when the first request failed we retry sending the same request, and now getting the ObjectDisposedException - cannot access closed stream.
This is the stacktrace.
at System.IO.StreamHelpers.ValidateCopyToArgs (System.IO.Stream source, System.IO.Stream destination, System.Int32 bufferSize) [0x0003a] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/external/corefx/src/Common/src/CoreLib/System/IO/StreamHelpers.CopyValidation.cs:26
at System.IO.MemoryStream.CopyToAsync (System.IO.Stream destination, System.Int32 bufferSize, System.Threading.CancellationToken cancellationToken) [0x00000] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/external/corefx/src/Common/src/CoreLib/System/IO/MemoryStream.cs:531
at Xamarin.Android.Net.AndroidClientHandler.WriteRequestContentToOutput (System.Net.Http.HttpRequestMessage request, Java.Net.HttpURLConnection httpConnection, System.Threading.CancellationToken cancellationToken) [0x00089] in /Users/builder/azdo/_work/204/s/xamarin-android/src/Mono.Android/Xamarin.Android.Net/AndroidClientHandler.cs:352
at Xamarin.Android.Net.AndroidClientHandler.DoProcessRequest (System.Net.Http.HttpRequestMessage request, Java.Net.URL javaUrl, Java.Net.HttpURLConnection httpConnection, System.Threading.CancellationToken cancellationToken, Xamarin.Android.Net.AndroidClientHandler+RequestRedirectionState redirectState) [0x002aa] in /Users/builder/azdo/_work/204/s/xamarin-android/src/Mono.Android/Xamarin.Android.Net/AndroidClientHandler.cs:424
at Xamarin.Android.Net.AndroidClientHandler.SendAsync (System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) [0x0027d] in /Users/builder/azdo/_work/204/s/xamarin-android/src/Mono.Android/Xamarin.Android.Net/AndroidClientHandler.cs:286
It would be great if anyone can tell me when the fix is planned. Thank you!
AdamDiament commentedon Jun 29, 2022
Did anyone get anywhere with this? @jNery-zz ? @yma44 @IndexOutOfLimit
AdamDiament commentedon Jun 30, 2022
Ok I think I have a fix for this:
@jNery-zz ? @yma44 @IndexOutOfLimit
Basically, if it's a retry, clone the HttpRequestMessage before resending it, this will reset the stream position to 0. HttpRequestMessages by design can only be used once.
For your example:
Also as an aside, AndroidClientHandler is now marked as obsolete in the source code with an instruction to use AndroidMessageHandler instead. I've done just that and it works great. It has the same overridable SendAsync method.
dotMorten commentedon Oct 3, 2022
FYI similar thing happens on iOS with NSUrlSessionHandler (no error thrown, but it just posts empty content to the server). However SocketsHttpHandler, HttpClientHandler, WebRequestHandler and other .NET ones are not affected and works fine.
.NET's HttpHandlers seem to disagree. Also if I created them, why can't I decide when to dispose them? It seems more efficient to be able to reuse your http content, than having to recreate it over and over again for repeated requests.
[Mono.Android] Do not dispose request content stream in AndroidMessag…
[Mono.Android] Do not dispose request content stream in AndroidMessag…