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

Using AddHttpClient<T> creates multiple instances of Scoped services in the same Scope #6432

Closed
madushans opened this issue Jul 30, 2020 · 8 comments

Comments

@madushans
Copy link

madushans commented Jul 30, 2020

Is your question related to a specific version? If so, please specify:

Does not seems to be specific.
Below are the versions I use

Microsoft.Extensions.Http (tested with 3.1.4 and 3.1.6)
Microsoft.NET.Sdk.Functions (tested with 3.0.7 and 3.0.9)
Microsoft.Azure.Functions.Extensions 1.0.0

What language does your question apply to? (e.g. C#, JavaScript, Java, All)

C#

Question

Summary

Basically I have an issue with Scoped services only when using Azure Functions and AddHttpClient<T>.

How to Reproduce

I have the following dependency structure in my functions app.

  • ParentService
    • SharedService
    • DependentService1
      • SharedService
      • MyHttpService
        • HttpClient
    • DependentService2
      • SharedService

All my services are registered as Scoped.
However the SharedService seems to get created twice in the same function execution. Once for the ParentService and first dependent service, and once again for the second dependent service, but only when I use AddHttpClient<T> and only in a Functions app.

I log the HashCode of the SharedService instance injected to each place for debugging.

DI setup

builder.Services.AddHttpClient<MyHttpService>(); // this changes the scoped behavior for stuff below?
builder.Services
           .AddScoped<SharedService>()
           .AddScoped<DependentService1>()
           .AddScoped<DependentService2>()
           .AddScoped<ParentService>();

Services

public class MyHttpService { public MyHttpService(HttpClient httpClient) { } }
public class SharedService { }

public class ParentService
{
    public ParentService( SharedService sharedService, DependentService1 dependentService1, DependentService2 dependentService2)
    {
        Console.WriteLine($"parent got {sharedService.GetHashCode()}");
    }
}

public class DependentService1
{
    public DependentService1(SharedService sharedService, MyHttpService myHttpService)
    {
        Console.WriteLine($"dependent1 got {sharedService.GetHashCode()}");
    }
}

public class DependentService2
{
    public DependentService2(SharedService sharedService)
    {
        Console.WriteLine($"dependent2 got {sharedService.GetHashCode()}");
    }
}

And I have a function as below

public class Function1
{
    private readonly ParentService parentService;

    public Function1(ParentService parentService)
    {
        this.parentService = parentService;
    }
    [FunctionName("Function1")]
    public async Task Run([TimerTrigger("* * * * * *")]TimerInfo myTimer, ILogger log)
    {
          //Debugger.Break(); // or a breakpoint here
    }
}

Now, since I registered ScopedService as Scoped, I expected only one instance but I get the following output.

dependent1 got 12161986
dependent2 got 17153280
parent got 12161986

Parent and Dependent1 always gets the same instance of SharedService, and Dependent2 gets a different instance. If I add a 3rd or 4th dependent service to parent, they all get the same instance injected to Dependent2.

UPDATE2: Only one of the dependents depending on the MyHttpService triggers this issue. If none of the dependent services depend on MyHttpService this behaves expectedly.

This only happens when I use AddHttpClient<T>(). I tried AddHttpClient() (non generic) and also not having it at all, which changes this behavior to reuse my SharedService correctly in both cases.

I currently want to use the AddHttpClient<T>() since I want to customize the HttpClient injected with custom headers .etc. as explained in the guidance here.

It appears this only happens in Azure Functions

I have tried this on an ASP.NET Core WebAPI app (File > New > Project > ASP.NET Core Web Application with defaults) and injecting ParentService to the WeatherForecastController that comes with the VS template which does not have this behavior.

I have also tried this on a simple Console App as follows and it also does not have this behavior.

static void Main(string[] args)
{
    var services = new ServiceCollection();

    services
        .AddHttpClient<MyHttpService>();
    services
        .AddScoped<ParentService>()
        .AddScoped<DependentService1>()
        .AddScoped<DependentService2>()
        .AddScoped<FakeFunction>() // Just a simple class that depend on the ParentService. I assume this is somewhat similar to what is expected to happen in Functions
        .AddScoped<SharedService>();
                
    var serviceProvider = services.BuildServiceProvider();
    var scope = serviceProvider.CreateScope();
    scope.ServiceProvider.GetService<FakeFunction>();
}

Would like to know why this happens and any way to avoid it so the SharedService is shared between parent and its other dependent services.

UPDATE: Dependency Tree was incorrect. Updated. Code samples unchanged.

@ghost ghost assigned kashimiz Jul 30, 2020
@APIWT
Copy link

APIWT commented Jul 31, 2020

I think this is a duplicate of #5098

@madushans
Copy link
Author

Did some more research on this and found out this triggers when AddHttpClient<T>() or AddHttpClient(string name) is called.

However it does not trigger when AddHttpClient() is called. This call returns the IServiceCollection where the two overloads above returns IHttpClientBuilder allowing more configuration.

This basically stops the HttpClient and Message Handlers from being configured as part of DI and potentially allows to run out of sockets in high load scenarios. Also this means other means of extensions that uses IHttpClientBuilder such as Polly extensions cannot be used, unless you do the work yourself for resiliency and MessageHandler/connection pooling .etc.

This can create weird and hard to find bugs since this doesn't trigger any errors or warnings.

@mrpmorris
Copy link

THE FIX IS HERE

#5098 (comment)

Add the following setting
"AzureWebJobsFeatureFlags": "EnableEnhancedScopes"

@v-bbalaiagar v-bbalaiagar self-assigned this May 14, 2021
@v-bbalaiagar
Copy link

Hi @madushans, Apologies for the delayed response, Were you able to find a solution here. Kindly let us know if you could resolve it can we close this issue?

@madushans
Copy link
Author

madushans commented May 16, 2021

The feature switch didn't quite work for me. I may have not had the right version of the host at the time.

I decided to make my own scope dependency wrapping the client and added that instead.


class MyHttpClientProvider{

    public HttpClient Client {get;}

   // create and configure http client on constructor
}

All code depending on http client now depends on above and get the client from the property.

This solved my immediate issue. Waiting for the feature switch work to be rhe default option to remove the above from my code.

Do understand that, that may break existing code of people who inadvertently relied on the "wrong" behavior.

@mrpmorris
Copy link

I doubt anyone relied on it working incorrectly. If they did, there will be a correct way to achieve their weird requirements :)

@sidkri
Copy link
Member

sidkri commented Jul 28, 2021

@fabiocav it looks like the change done for this issue 5098 might resolve this issue - could you review and close the issue if so?

@v-bbalaiagar
Copy link

I checked internally. Closing this issue as this issue is being addressed and worked upon on a similar scenario in #5098, Please find the space for further updates - azure-functions-host/issues/5098

@ghost ghost locked as resolved and limited conversation to collaborators Nov 3, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

6 participants