Azure / Azure/azure-functions-host
Dependency resolution issues in extension
- Dominant language
- C#
- Stars
- 2k
- Forks
- 482
- Avg merge
- 2d 12h
- Merged PRs (30d)
- 38
Description
Okay, after spending some time on some other stuff, I've come back around to this. I've spent all day narrowing down what my problem is. I've affectionately called it "scope creep", because the DI framework doesn't appear to be honouring the scope (affection is really not what I'm feeling in regards to these DI issues).
It all comes down to this specific registration using `Microsoft.Extensions.Http (2.2.0)`:
```
builder.Services.AddHttpClient();
```
I've repro'd it at: https://github.com/t-l-k/azure-functions-di-bug.git
I've added the prerelease CLI tools `Azure.Functions.Cli.min.win-x64.2.7.1513.zip` into the repo and they're expanded in a `Directory.Build.props` Unzip build step (requires `> MSBuild 15.8`), the project references the contained `func.exe` as the debug executable. I've done this because the [Artifact drop](https://azfunc.visualstudio.com/Azure%20Functions/_build/results?buildId=1550) is no longer availables. perhaps @fabiocav we could have another build (retained)? https://github.com/Azure/azure-functions-core-tools/commit/7c2ae304b41dcfb7bcee14d9cbbf12c73f78ca0a -
You have to run it in this version, because without the `2.0.12620.0` extension, it doesn't run at all (everything is a singleton chaos).
When running, the bug manifests in the class `MediatrWarmupExtension` (which was a behaviour I was exploring to workaround the issue), line https://github.com/t-l-k/azure-functions-di-bug/blob/23919854b3791311ae48bcc67cc4f3f5a183386d/Extensions/MediatrWarmupExtension.cs#L40
```
var handler1 = scopedProvider.GetRequiredService>();
var handler2 = scopedProvider.GetRequiredService>();
// ^^ The bug manifests at this point ^^ handler1 and handler2 dependencies should be the same.
```
The trace level output of the application displays this as part of an extension initialisation:
```
Azure Functions Core Tools (2.7.1513 Commit hash: 7c2ae304b41dcfb7bcee14d9cbbf12c73f78ca0a)
Function Runtime Version: 2.0.12620.0
Can't determine project language from files. Please use one of [--csharp, --javascript, --typescript, --java, --python, --powershell]
[10/09/2019 15:58:47] Building host: startup suppressed:False, configuration suppressed: False
[10/09/2019 15:58:48] [0] graphObjectA 15948253 (from 66477871 (DependencyGraphAlpha)) 4
[10/09/2019 15:58:48] [0] graphObjectB 53858013 (from 66477871 (DependencyGraphAlpha)) 4
[10/09/2019 15:58:48] [0] graphObjectA 8857874 (from 51797632 (DependencyGraphBravo)) 4
[10/09/2019 15:58:48] [0] graphObjectB 14008467 (from 51797632 (DependencyGraphBravo)) 4
[10/09/2019 15:58:51] [1] graphObjectA 8857874 (from 29236951 (DependencyGraphAlpha)) 4
[10/09/2019 15:58:51] [1] graphObjectB 14008467 (from 29236951 (DependencyGraphAlpha)) 4
[10/09/2019 15:58:51] Warmed up IRequestHandler for Func.Canary.Application.ScopeCreepCommand
Hosting environment: Production
Content root path: D:\source\repos\t-l-k\Func.Canary\bin\Debug\netcoreapp2.2
Now listening on: http://0.0.0.0:7071
Application started. Press Ctrl+C to shut down.
```
Commenting out the `AddHttpClient` registration behaves thus:
```
Azure Functions Core Tools (2.7.1513 Commit hash: 7c2ae304b41dcfb7bcee14d9cbbf12c73f78ca0a)
Function Runtime Version: 2.0.12620.0
Can't determine project language from files. Please use one of [--csharp, --javascript, --typescript, --java, --python, --powershell]
[10/09/2019 15:59:49] Building host: startup suppressed:False, configuration suppressed: False
[10/09/2019 15:59:50] [0] graphObjectA 50788593 (from 59927501 (DependencyGraphAlpha)) 4
[10/09/2019 15:59:50] [0] graphObjectB 50517987 (from 59927501 (DependencyGraphAlpha)) 4
[10/09/2019 15:59:50] [0] graphObjectA 50788593 (from 54244775 (DependencyGraphBravo)) 4
[10/09/2019 15:59:50] [0] graphObjectB 50517987 (from 54244775 (DependencyGraphBravo)) 4
[10/09/2019 15:59:52] Warmed up IRequestHandler for Func.Canary.Application.ScopeCreepCommand
Hosting environment: Production
Content root path: D:\source\repos\t-l-k\Func.Canary\bin\Debug\netcoreapp2.2
Now listening on: http://0.0.0.0:7071
Application started. Press Ctrl+C to shut down.
```
Note the absence of `graphObjectA` and `graphObjectB` preceded by `[1]`. With the bug active, a second service `DependencyGraphAlpha` is constructed, **in the same scope**, for the 2nd transient handler resolution. The other parameter, `DependencyGraphBravo`, is reused **correctly** across both instances.
Here are all the registrations in the example:
```
services.AddMediatR(typeof(ScopeCreepCommandHandler));
services.AddScoped();
services.AddScoped();
services.AddScoped();
services.AddScoped();
services.AddScoped(sp => sp.GetRequiredService());
```
_I did try experimenting with service provider sub-scopes, but doing so appeared to offend the Function's host runtime. I wish to point out, that whilst I have materialised this in an **extension**, the exact same behaviour was being exhibited when resolving a `HttpClient` reference within a durable function activity call._
_My temporary workaround is to just resolve all of my `HttpClient` consumers in an initialisation step, for the time being, but obviously it's not ideal I don't know where else this bug may materialise._
Please fix! :cry:
_Originally posted by @t-l-k in https://github.com/Azure/azure-functions-host/issues/3399#issuecomment-530010253_
Contributor guide
Assessment
This issue has not been assessed yet.