Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] Refit + Fusillade fails with System.ObjectDisposedException #148

Open
JeremyBP opened this issue Feb 2, 2021 · 3 comments
Open

[BUG] Refit + Fusillade fails with System.ObjectDisposedException #148

JeremyBP opened this issue Feb 2, 2021 · 3 comments
Labels

Comments

@JeremyBP
Copy link

JeremyBP commented Feb 2, 2021

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
@clairernovotny
Copy link
Member

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.

@mikart143
Copy link

Is there any possibility to use refit with fusillade together ?

@pellet
Copy link

pellet commented Sep 1, 2021

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants