reactiveui/Fusillade

[BUG] Refit + Fusillade fails with System.ObjectDisposedException

JeremyBP opened this issue · 3 comments

Describe the bug
Actually the same as #34 witch is closed but not fixed.
Using Refit with Fusillade fails with System.ObjectDisposedException: Cannot access a disposed object. Object name: 'System.Net.Http.HttpConnectionResponseContent'.
The exception is thrown at Refit.RequestBuilderImplementation.d__15`1.MoveNext() in /_/Refit/RequestBuilderImplementation.cs:line 324
It seems that when Fusillade deduplicates requests returning val.Response.ToTask(cancellationToken), Refit fails deserializing HttpContent because it's disposed.

As @clairernovotny on the Refit side with issue #1048 think it looks like a Fusillade bug, I opened the same issue on the Fusillade side.
I don't know if the bug comes from Refit or Fusillade, but I know it throws when using one with each other.

Steps To Reproduce
Create a Refit RestService with any Fusillade's NetCache HttpMessageHandler into the HttpClient, and send the same request at least twice at the same time.
Something dummy like:

try
{
	var reqResService = RestService.For<IReqResService>(new HttpClient(NetCache.UserInitiated)
	{
		BaseAddress = new Uri("https://reqres.in/api")
	});

	var task1 = reqResService.GetUsersAsync();
	var task2 = reqResService.GetUsersAsync();

	var result = await Task.WhenAll(task1, task2);
	var userList = result.FirstOrDefault();
}
catch (Exception e)
{
	throw;
}

with IReqResService:

public interface IReqResService
{
	[Get("/users")]
	Task<UserList> GetUsersAsync();
}

and Models:

public class UserList
{
	[JsonProperty("page")]
	public int Page { get; set; }

	[JsonProperty("per_page")]
	public int PerPage { get; set; }

	[JsonProperty("total")]
	public int Total { get; set; }

	[JsonProperty("total_pages")]
	public int TotalPages { get; set; }

	[JsonProperty("data")]
	public List<User> Data { get; set; }
}

public class User
{
	[JsonProperty("id")]
	public int Id { get; set; }

	[JsonProperty("first_name")]
	public string FirstName { get; set; }

	[JsonProperty("last_name")]
	public string LastName { get; set; }

	[JsonProperty("avatar")]
	public string Avatar { get; set; }

	[JsonProperty("email")]
	public string Email { get; set; }
}

Well interface and models are not the point, it's just to illustrate the bug.
Note that everything succeed when using Fusillade without Refit, I mean using HttpClient directly like:

try
{
	var client = new HttpClient(NetCache.UserInitiated)
	{
		BaseAddress = new Uri("https://reqres.in/api/users")
	};

	var task1 = client.GetAsync("");
	var task2 = client.GetAsync("");

	var responses = await Task.WhenAll(task1, task2);
	var jsonString = await responses.First().Content.ReadAsStringAsync().ConfigureAwait(false);
	var userList = JsonConvert.DeserializeObject<UserList>(jsonString);
}
catch (Exception e)
{
	throw;
}

Note that everything succeed to, when using Refit without Fusillade like:

try
{
	var reqResService = RestService.For<IReqResService>("https://reqres.in/api");

	var task1 = reqResService.GetUsersAsync();
	var task2 = reqResService.GetUsersAsync();

	var result = await Task.WhenAll(task1, task2);
	var userList = result.FirstOrDefault();
}
catch (Exception e)
{
	throw;
}

So it's only when using both of it.

Expected behavior
Should return result

Environment

  • OS: All
  • Device: All
  • Version: All

Looking a little closer, Refit always disposes it's underlying HttpRequestMessage when a concrete type is returned. If Fusillade is caching things, that would break.

Is there any possibility to use refit with fusillade together ?

I'm not using refit, just fusilade and it was causing exceptions to be thrown in Android when using httpclient concurrently:

Object name: 'System.Net.Http.StreamContent'.) ---> System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'System.Net.Http.StreamContent'.
at System.Net.Http.HttpContent.CheckDisposed () <0xa17d609c + 0x00048> in <6ec2f38857a34d7ca25a155c6e4c5a96>:0 
at System.Net.Http.HttpContent.LoadIntoBufferAsync (System.Int64 maxBufferSize, System.Threading.CancellationToken cancellationToken) <0xa17d5404 + 0x0003f> in <6ec2f38857a34d7ca25a155c6e4c5a96>:0 
at System.Net.Http.HttpClient.FinishSendAsyncBuffered (System.Threading.Tasks.Task`1[TResult] sendTask, System.Net.Http.HttpRequestMessage request, System.Threading.CancellationTokenSource cts, System.Boolean disposeCts) <0xa17d2c50 + 0x00628> in <6ec2f38857a34d7ca25a155c6e4c5a96>:0 

Given this and it's issue on iOS #161 I'm thinking about looking at other ways to rate limit the httpclient.