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

Race condition in option validation for StandardResilienceHandler #4917

Closed
NatMarchand opened this issue Feb 2, 2024 · 3 comments
Closed
Labels
bug This issue describes a behavior which is not expected - a bug.

Comments

@NatMarchand
Copy link

NatMarchand commented Feb 2, 2024

Description

My unit tests randomly crash while starting the host due to a InvalidCastException in option validation for standard resilience handler.

Reproduction Steps

Hard to reproduce as it is a race condition and occurs randomly.

However my tests looks like something like this :

        await new HostBuilder()
            .ConfigureDefaults([])
            .ConfigureServices(services =>
            {
                services.AddHttpClient<FooClient>()
                    .AddStandardResilienceHandler();
                services.AddHttpClient<BarClient>()
                    .AddStandardResilienceHandler();
            })
            .StartAsync();

Expected behavior

Works correclty

Actual behavior

The current exception

fail: Microsoft.Extensions.Hosting.Internal.Host[11]
      Hosting failed to start
      System.InvalidCastException: Unable to cast object of type 'System.TimeSpan' to type 'System.String'.
         at __OptionValidationGeneratedAttributes.<Validators_g>F8B1F3D42962A35D8FF1B1489612AEF6C36F3713335EFF79DB68A25973333C495____SourceGen__RangeAttribute.IsValid(Object value)
         at System.ComponentModel.DataAnnotations.ValidationAttribute.IsValid(Object value, ValidationContext validationContext)
         at System.ComponentModel.DataAnnotations.ValidationAttribute.GetValidationResult(Object value, ValidationContext validationContext)
         at System.ComponentModel.DataAnnotations.Validator.TryValidate(Object value, ValidationContext validationContext, ValidationAttribute attribute, ValidationError& validationError)
         at System.ComponentModel.DataAnnotations.Validator.GetValidationErrors(Object value, ValidationContext validationContext, IEnumerable`1 attributes, Boolean breakOnFirstError)
         at System.ComponentModel.DataAnnotations.Validator.TryValidateValue(Object value, ValidationContext validationContext, ICollection`1 validationResults, IEnumerable`1 validationAttributes)
         at Microsoft.Extensions.Http.Resilience.__HttpRetryStrategyOptionsValidator__.Validate(String name, HttpRetryStrategyOptions options)
         at Microsoft.Extensions.Http.Resilience.Internal.Validators.HttpStandardResilienceOptionsValidator.Validate(String name, HttpStandardResilienceOptions options)
         at Microsoft.Extensions.Options.OptionsFactory`1.Create(String name)
         at Microsoft.Extensions.Options.OptionsMonitor`1.<>c.<Get>b__10_0(String name, IOptionsFactory`1 factory)
         at Microsoft.Extensions.Options.OptionsCache`1.<>c__DisplayClass3_1`1.<GetOrAdd>b__2()
         at System.Lazy`1.ViaFactory(LazyThreadSafetyMode mode)
         at System.Lazy`1.ExecutionAndPublication(LazyHelper executionAndPublication, Boolean useDefaultConstructor)
         at System.Lazy`1.CreateValue()
         at Microsoft.Extensions.Options.OptionsCache`1.GetOrAdd[TArg](String name, Func`3 createOptions, TArg factoryArgument)
         at Microsoft.Extensions.Options.OptionsMonitor`1.Get(String name)
         at Microsoft.Extensions.DependencyInjection.OptionsBuilderExtensions.<>c__DisplayClass0_1`1.<ValidateOnStart>b__1()
         at Microsoft.Extensions.Options.StartupValidator.Validate()
         at Microsoft.Extensions.Hosting.Internal.Host.StartAsync(CancellationToken cancellationToken)

Regression?

No response

Known Workarounds

No response

Configuration

dotnet 8
Microsoft.Extensions.Http.Resilience Version="8.1.0"

Other information

I've been able to debug the exception and walk through the stack.
As you can see we are in the !IsInitialized if branch even though the IsInitialized is true, an other thread must have set it concurrently and then Minimum and Maximum are already Timespan, not strings.
image

@NatMarchand NatMarchand added bug This issue describes a behavior which is not expected - a bug. untriaged labels Feb 2, 2024
@NatMarchand
Copy link
Author

Ah! Just found that it was corrected in the source generator !
dotnet/runtime#97045

@martincostello
Copy link
Member

The package with the fix for the source generator has been released today, 8.0.2, but I think we need a new version of the Microsoft.Extensions.Http.Resilience library built using that version of the source generator to propagate out the fix.

@NatMarchand
Copy link
Author

Looks like 8.2.0 contains the fix. Closing the issue !

@github-actions github-actions bot locked and limited conversation to collaborators Mar 17, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug This issue describes a behavior which is not expected - a bug.
Projects
None yet
Development

No branches or pull requests

2 participants