Azure / Azure/azure-functions-host
HttpClientFactory + DryIoc.ContainerException
- Dominant language
- C#
- Stars
- 2k
- Forks
- 482
- Avg merge
- 2d 10h
- Merged PRs (30d)
- 36
Description
#### Issue Description
We are building RESTFul backend using Azure Functions. We are leveraging HttpClientFactory via DI which seems to work for about an hour and then starts to throw following exception:
> exception:DryIoc.ContainerException: Scope disposed{no name, Parent=disposed{no name}} is disposed and scoped instances are disposed and no longer available.
Side note: our issue resembles the bug reported here #5590.
#### Investigative information
- Timestamp: March 31, 2020
- Function App version: Runtime version is 3.0.13139.0 (~3)
- Function App name: not providing
- Function name(s) (as appropriate): not providing
- Invocation ID: 6a5cacf8-c872-41c4-a940-cadf712e5b5a and 04/01/2020 19:32:28
- Region: west us 2
#### Project Setup:
Nuget packages: note removed few packages that were not relevant
```xml
netcoreapp3.1
v3
```
Startup (DI registration):
```c#
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddHttpClient();
builder.Services.AddTransient();
... other services here ....
}
}
```
API Service:
```c#
public class APIService : IAPIService
{
private readonly IHttpClientFactory _httpClientFactory;
public ApiService(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
public void MakeRequest()
{
var httpClient = _httpClientFactory.CreateClient();
httpClient.PostAsync(...);
}
}
```
Function Call:
```c#
public class MyFunction
{
private readonly IAPIService _apiService;
public MyTrigger(IAPIService apiService)
{
_apiService = apiService;
}
// Runs every hour
[FunctionName(nameof(MyTimerTrigger))]
public async Task MyTimerTrigger([TimerTrigger("0 23 * * * *", RunOnStartup = false)]TimerInfo timerInfo, ILogger logger)
{
logger.LogStartFunction();
await _apiService.Process();
logger.LogExitFunction();
}
```
#### Expected behavior
This timer based function and other RESTFul functions continue to serve requests.
#### Actual behavior
After one hour or so of being operational, all services start returning 500 and following exception is logged in the App Insight:
> Exception: function:MyFunctions.MyEndPoint msg: exception:DryIoc.ContainerException: Scope disposed{no name, Parent=disposed{no name}} is disposed and scoped instances are disposed and no longer available.
at DryIoc.Throw.It(Int32 error, Object arg0, Object arg1, Object arg2, Object arg3) in D:\a\1\s\src\WebJobs.Script.WebHost\DependencyInjection\DryIoc\Container.cs:line 8990
at DryIoc.Scope.TryGet(Object& item, Int32 id) in D:\a\1\s\src\WebJobs.Script.WebHost\DependencyInjection\DryIoc\Container.cs:line 7880
at DryIoc.Container.InstanceFactory.GetAndUnwrapOrDefault(IScope scope, Int32 factoryId) in D:\a\1\s\src\WebJobs.Script.WebHost\DependencyInjection\DryIoc\Container.cs:line 1479
at DryIoc.Container.InstanceFactory.GetInstanceFromScopeChainOrSingletons(IResolverContext r) in D:\a\1\s\src\WebJobs.Script.WebHost\DependencyInjection\DryIoc\Container.cs:line 1468
at DryIoc.Container.DryIoc.IResolver.Resolve(Type serviceType, Object serviceKey, IfUnresolved ifUnresolved, Type requiredServiceType, Request preResolveParent, Object[] args) in D:\a\1\s\src\WebJobs.Script.WebHost\DependencyInjection\DryIoc\Container.cs:line 307
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw(Exception source)
at System.Linq.Expressions.Interpreter.ExceptionHelpers.UnwrapAndRethrow(TargetInvocationException exception)
at System.Linq.Expressions.Interpreter.MethodInfoCallInstruction.Run(InterpretedFrame frame)
at System.Linq.Expressions.Interpreter.Interpreter.Run(InterpretedFrame frame)
at System.Linq.Expressions.Interpreter.LightLambda.Run(Object[] arguments)
at Thunk(Func`2 , IResolverContext )
at DryIoc.Container.ResolveAndCacheDefaultFactoryDelegate(Type serviceType, IfUnresolved ifUnresolved) in D:\a\1\s\src\WebJobs.Script.WebHost\DependencyInjection\DryIoc\Container.cs:line 223
at DryIoc.Container.DryIoc.IResolver.Resolve(Type serviceType, IfUnresolved ifUnresolved) in D:\a\1\s\src\WebJobs.Script.WebHost\DependencyInjection\DryIoc\Container.cs:line 194
at Microsoft.Azure.WebJobs.Script.WebHost.DependencyInjection.ScopedServiceProvider.GetService(Type serviceType) in D:\a\1\s\src\WebJobs.Script.WebHost\DependencyInjection\ScopedServiceProvider.cs:line 25
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
at Microsoft.Extensions.Http.DefaultHttpClientFactory.CreateHandlerEntry(String name)
at Microsoft.Extensions.Http.DefaultHttpClientFactory.<>c__DisplayClass14_0.<.ctor>b__1()
at System.Lazy`1.ViaFactory(LazyThreadSafetyMode mode)
--- End of stack trace from previous location where exception was thrown ---
at System.Lazy`1.CreateValue()
at System.Lazy`1.get_Value()
at Microsoft.Extensions.Http.DefaultHttpClientFactory.CreateHandler(String name)
at Microsoft.Extensions.Http.DefaultHttpClientFactory.CreateClient(String name)
at System.Net.Http.HttpClientFactoryExtensions.CreateClient(IHttpClientFactory factory)
#### Possible workarounds (do not know if following is MS recommended approach)
Since DI is not supported for HttpClient, we will manually create an instance and use that.
```c#
public class APIService : IAPIService
{
private readonly HttpClient _client;
public APIService()
{
_client = new HttpClient();
}
[FunctionName("GetPosts")]
public async Task Get(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "posts")] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
var res = await _client.GetAsync("https://microsoft.com");
await _service.AddResponse(res);
return new OkResult();
}
}
```
Contributor guide
Research direction
Start with the Startup DI registration and APIService/Function call shown in the report, then trace the DefaultHttpClientFactory.CreateHandlerEntry path in the supplied stack trace. Reproduce the hourly failure with the listed Azure Functions and Microsoft.Extensions.Http versions; done means requests continue succeeding without the disposed-scope exception.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- azure, csharp
- Domain
- backend, cloud
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 30/100