-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Microsoft.Extensions.Http.Resilience - Cannot access a disposed object. - Integrationtest with WebApplicationFactory #97037
Comments
Thanks for your report. I think we might be dealing with multiple different issues, given the different symptoms. Regarding the stack trace that ends with an InvalidCastException, that one is very puzzling. I just ran a test that exercises this code, and it never gets to a point where it is casting a TimeSpan into a string. I tried both a valid timespan and an invalid timespan to make sure I try out both success and failure validation paths. This code path should not be subject to non-deterministic behavior, either it works or it doesn't work. So I don't know what's going on here. Are you running the .NET 8 runtime with .NET 8 versions of all extensions? The only explanation I have is that you're somehow using an older version of the RangeAttribute type which didn't support the semantics this code is depending on. |
I set it up with Refit so maybe related to that? I use .net 8 final version. The tests run with xUnit latest version as well. The strange thing is that it feels like something is not disposing but some parts is. Then it try to get something from the IServiceCollection that is disposed, like the standard settings the recilienece library seams to set up. The Options of default values? And in that case if IServcieCollection somehow gets disposed but some other stuffs are not it try to inject the default settings and get wrong values that are not valid? Its so hard to debug because it happens when two test or more run in within the same class. So I can never get this problem when run each test one by one manually. But I do not get why IServiceCollection is disposed any way, the exception start when it try to create and use the client. If I remove the .AddStandardResilienceHandler(); from the code the error gets away. It would be easier If I could get this to happen all the time :) I'm using .net 8 I have not test it without Refit and had no time test it more so I just added a if statement so it does not add that line for my tests :( I shall see if I have more time to do even more tests. After 1 day testing with different disposing, order of registrations, change my test to use fixture, no fixture etc without any progress I searched here and saw the issue about shared settings and thought maybe this can be related and a know issue. |
It doesn't feel as though Refit is to blame here. There could definitely be a race here due to some global state being attached somewhere. I can see where the multiple disposal issue could surface in that context. But the InvalidCastException appears highly mysterious. If there's any way you could catch it in the debugger when this happens and look around to see how we got there, maybe we can figure out what's happening in that particular case. And who knows, maybe that will shed some light on the multiple disposal issue as well. |
Sadly I have limited skills here in deep debugging when it comes to catching it from a random situation. All I can get is more info about the whole error thought, not sure if it gives more info.
And this is the code that set it up in startup.cs
And my factory I created that I used with my Fixture setup or I have also tried to just use it in a constructor of my unit test.
I tried it without the IAsyncDisposable and just IDispose as well. I'm reusing this factory for different tests that exist in different classes. I have this action so I can easy |
Would your code be available publicly somewhere? I'd love to get it and try this locally. Thanks. |
I have a strong suspicion that this is not related to resilience code, but rather something around options or options validation. To get resilience out of the picture try to do the following: var builder = builder.Services.AddRefitClient<TApiInterface>()
.ConfigureHttpClient(c =>
{
c.BaseAddress = new Uri(configuration.GetValue<string>(endpointConfigKey)!);
c.Timeout = TimeSpan.FromSeconds(35);
})
.AddHttpMessageHandler<AuthHeaderHandler>();
builder.AddStandardResilienceHandler();
// This is the key to named options used inside AddStandardResilienceHandler
var optionsName = builder.Name + "-standard";
// Now resolve the options same way the resilience is doing it
var options = services.BuildServiceProvider()
.GetRequiredService<IOptionsMonitor<HttpStandardResilienceOptions>>()
.Get(optionsName); I suspect that the call to retrieve the options will fail. |
Sadly I do not :/ the code is located in a private repository at a company, so it's not public :( I need to talk with them to see what I can do public or not in that case |
When I added:
|
Here is what I can share The fixture:
My factory code:
The Lock part is my last experiment, so In general I do not use that, but it does not matter, same result any way. One of 2 test. I have two classes for 2 different feautres. Boot have 2 test with same pattern.
I'm sorry I cannot share a project for you. If this does not help. I will see if I have time tomorrow to create a whole new project that I can share. if not, then I turn it off, go back to old polly for a short time period, and see if this is something others will report. |
This is very strange error:
Probably some host setup issue? I am afraid that at this point I am not able to help further, until you provide some repro code. |
I understand that and can see what I can do. I saw there are some new updates to Polly, resilience lib from 8 to a 8.0.1 or something. I will test that out as soon to see if the problem persists, or might be fixed in those versions. |
I'm getting the What info would you need to diagnose this? Would a complete solution that shows the problem help? |
I've just started noticing this error locally when trying to migrate to the use of The code I'm reproducing this with locally (but not in CI) is here: martincostello/alexa-london-travel@288ef84 Wasn't there a code change in this area since 8.0.0 for an issue to do with native AoT? The stack trace is ringing a bell, but that might just be a coincidence. This is the issue I'm thinking of: #94799, which was marked as fixed for 8.0 by #94857. Stack trace for a failing test:
|
The decompiled code for that method from Microsoft.Extensions.Http.Resilience 8.1.0 is this according to ILSpy is below. Looks like it contains two casts to a string. ____SourceGen__RangeAttribute.IsValid()public override bool IsValid(object? value)
{
if (!Initialized)
{
if (Minimum == null || Maximum == null)
{
throw new InvalidOperationException("The minimum and maximum values must be set to valid values.");
}
if (NeedToConvertMinMax)
{
CultureInfo formatProvider = (ParseLimitsInInvariantCulture ? CultureInfo.InvariantCulture : CultureInfo.CurrentCulture);
if (OperandType == typeof(TimeSpan))
{
if (!TimeSpan.TryParse((string)Minimum, formatProvider, out var result) || !TimeSpan.TryParse((string)Maximum, formatProvider, out var result2))
{
throw new InvalidOperationException("The minimum and maximum values must be set to valid values.");
}
Minimum = result;
Maximum = result2;
}
else
{
Minimum = ConvertValue(Minimum, formatProvider) ?? throw new InvalidOperationException("The minimum and maximum values must be set to valid values.");
Maximum = ConvertValue(Maximum, formatProvider) ?? throw new InvalidOperationException("The minimum and maximum values must be set to valid values.");
}
}
int num = ((IComparable)Minimum).CompareTo((IComparable)Maximum);
if (num > 0)
{
throw new InvalidOperationException("The maximum value '{Maximum}' must be greater than or equal to the minimum value '{Minimum}'.");
}
if (num == 0 && (MinimumIsExclusive || MaximumIsExclusive))
{
throw new InvalidOperationException("Cannot use exclusive bounds when the maximum value is equal to the minimum value.");
}
Initialized = true;
}
if ((value == null || (value is string text && text.Length == 0)) ? true : false)
{
return true;
}
CultureInfo formatProvider2 = (ConvertValueInInvariantCulture ? CultureInfo.InvariantCulture : CultureInfo.CurrentCulture);
object obj;
if (OperandType == typeof(TimeSpan))
{
if (value is TimeSpan)
{
obj = value;
}
else
{
if (!(value is string))
{
DefaultInterpolatedStringHandler defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(121, 1);
defaultInterpolatedStringHandler.AppendLiteral("A value type ");
defaultInterpolatedStringHandler.AppendFormatted(value!.GetType());
defaultInterpolatedStringHandler.AppendLiteral(" that is not a TimeSpan or a string has been given. This might indicate a problem with the source generator.");
throw new InvalidOperationException(defaultInterpolatedStringHandler.ToStringAndClear());
}
if (!TimeSpan.TryParse((string)value, formatProvider2, out var result3))
{
return false;
}
obj = result3;
}
}
else
{
try
{
obj = ConvertValue(value, formatProvider2);
}
catch (Exception ex) when (((ex is FormatException || ex is InvalidCastException || ex is NotSupportedException) ? 1 : 0) != 0)
{
return false;
}
}
IComparable comparable = (IComparable)Minimum;
IComparable comparable2 = (IComparable)Maximum;
if (MinimumIsExclusive ? (comparable.CompareTo(obj) < 0) : (comparable.CompareTo(obj) <= 0))
{
if (!MaximumIsExclusive)
{
return comparable2.CompareTo(obj) >= 0;
}
return comparable2.CompareTo(obj) > 0;
}
return false;
} |
Looking at the code, there might be a race condition where the casts |
One final data point, if I change my xunit configuration to have |
|
@tarekgh I think the InvalidCastException part of this problem has to do with the config validation code gen and how we're handling some racy initialization in the attribute. Can you take a look please? |
OK, so the problem is this:
|
Tagging subscribers to this area: @dotnet/area-system-componentmodel-dataannotations Issue DetailsProblem with .AddStandardResilienceHandler(); when used in integration test with custom web factory and when there is more than one test. I have a service that uses HttpClients to 3rd party endpoints. On this one, I use the.AddStandardResilienceHandler(); All works fine, but after some lib updates, I start getting random exceptions from my tests. The tests randomly give me:
And:
It does not happen all the time but mostly every time. I have tried fixture, using the same client that's created on all test methods but it seems like a disposal is happening.
If I remove the .AddStandardResilienceHandler(); from my setup of the HTTP client all works fine. I also tried to remove the added services and override them in
But that will not work because it's still have added the resilience stuff before I reach this area. So it seams like: Not sure if there is som clean-up, or dispose issues with the resilience setup? I have followed the basic guidelines to setup integration tests in .net Have I missed something? Or is this a known bug?
|
I moved the issue to the runtime repo. I'll take a look. |
@martincostello is it possible you can share the source generated file from your repro? |
I'm not at a computer until tomorrow now, but I had to use ILSpy to get the code because it was compiled into the Resilience library, rather than being generated into my own project. |
Tagging subscribers to this area: @dotnet/area-extensions-options Issue DetailsProblem with .AddStandardResilienceHandler(); when used in integration test with custom web factory and when there is more than one test. I have a service that uses HttpClients to 3rd party endpoints. On this one, I use the.AddStandardResilienceHandler(); All works fine, but after some lib updates, I start getting random exceptions from my tests. The tests randomly give me:
And:
It does not happen all the time but mostly every time. I have tried fixture, using the same client that's created on all test methods but it seems like a disposal is happening.
If I remove the .AddStandardResilienceHandler(); from my setup of the HTTP client all works fine. I also tried to remove the added services and override them in
But that will not work because it's still have added the resilience stuff before I reach this area. So it seams like: Not sure if there is som clean-up, or dispose issues with the resilience setup? I have followed the basic guidelines to setup integration tests in .net Have I missed something? Or is this a known bug?
|
never mind. I have created an isolated simple repro and I can see the issue now. |
@tarekgh PR is in mailstone 9 - can we hope to get in in 8.0.2? |
I intend to port the fix to version 8.0 after merging it into the main branch. This will likely be included in 8.0.3, as the cutoff for 8.0.2 has already passed. |
I have submitted the fix to main and 8.0 servicing branches. Hopefully, this will be included in the February 8.0 servicing release. |
Problem with .AddStandardResilienceHandler(); when used in integration test with custom web factory and when there is more than one test.
I have a service that uses HttpClients to 3rd party endpoints. On this one, I use the.AddStandardResilienceHandler();
I set this up in my startup.cs then I have an integration test where I use the WebApplicationFactory
All works fine, but after some lib updates, I start getting random exceptions from my tests.
If I run one test at a time with no problem, it seems to be shared resource issues when they run in parallel.
The tests randomly give me:
And:
It does not happen all the time but mostly every time.
I have tried fixture, using the same client that's created on all test methods but it seems like a disposal is happening.
But next time there is some expectation that the resilience services shall exist somehow. Not all services are not resettled.
Some singleton was added once I guess.
If I remove the .AddStandardResilienceHandler(); from my setup of the HTTP client all works fine.
I also tried to remove the added services and override them in
But that will not work because it's still have added the resilience stuff before I reach this area.
So it seams like:
First it creates my client with resilience, then I ran the fist FACT test. Then a second one.
I have 2 tests one for success and one for failure. If I remove one of the test all works fine. But as soon I share the client in many test this happens.
Not sure if there is som clean-up, or dispose issues with the resilience setup? I have followed the basic guidelines to setup integration tests in .net
Have I missed something? Or is this a known bug?
I can see there is a bug reported where it seams to be a shared setting for all services atm so we can't override settings on different once. Maybe that's related here?
The text was updated successfully, but these errors were encountered: