Azure-functions-host: DI injected IServiceProvider is not the currently scoped provider

Created on 14 Jun 2019  路  7Comments  路  Source: Azure/azure-functions-host

I have been trying to work out for a while where the scope is managed in Azure Functions. The Host Runtime creates a Microsoft.Azure.WebJobs.Host.Executors.FunctionInstanceWrapper, Microsoft.Azure.WebJobs.Host which implements the IFunctionInstanceEx interface. There is a property InstanceServices which (lazily) opens a new scope and returns the IServiceProvider. This interface is used in many locations as part of the function activation process, The process can be seen inside the DefaultJobActivator.

An issue I have been grappling with is that an injected instance of the IServiceProvider is the global provider and not the scoped provider attached to the "function instance".

I can't see a way to resolve this except for modifying the factory created by the Microsoft.Extensions.DependencyInjection.ActivatorUtilities to handle a constructor argument of IServiceProvider as a special case, and passing in the one from the function wrapper instead of the one that's provided by the IServiceProvider itself. It seems that creating the scope does not supersede the registered provider inside the child scope.

Repro process

To repro this, create these classes:

```c#
public interface ITestScoped { int Counter { get; set; } }
public class TestScoped : ITestScoped { public int Counter { get; set; } }
public class TestScoped2
{
public TestScoped2( ITestScoped ts ) { Ts = ts; }
public ITestScoped Ts { get; }
}

Register services as follows:

```c#
services.AddScoped<ITestScoped,TestScoped>();
services.AddScoped<TestScoped2>();

In your (instance) functions class, pass in an ITestScoped as a constructor arg as well as a TestScoped2 and a IServiceProvider. Inside the HTTP function use the service provider to get an instance of ITestScoped - if you increment the Counter in the object created through the service provider you'll see none of the other counters are incremented - the objects are not "singleton per function execution" (scoped) properly. If you alter the Counter in either of the objects injected via the constructor args you will see that all the Counter properties are updated together, as all the ITestScoped objects are actually the same object. Scoped properly.

Clearly the issue is that the IServiceProvider passed in to the constructor is not the same as the one used to create the ITestScoped objects that were also passed in.

P2

Most helpful comment

@fabiocav The use-case of function filters was only the event that highlighted this issue for me. The actual problem is that IServiceProvider is not the scoped provider in several cases where one would expect it to be, including DI factories and function execution. This creates a subtle error where scoped services are actually singleton services that never get disposed, which will lead to a number of issues including memory leaks, handle/port exhaustion, and security issues.

All 7 comments

I am having a very similar issue. This is a serious blocking issue for my team. There are numerous reports about bugs surrounding scoped services. Please respond Microsoft.

FWIW, I can demonstrate scoped constructor injection working properly, though I have not tried to force a multi-thread issue. The problem for me is specifically using DI to inject an ISerciceProvider to use in a (known anti-pattern) service locator type model.

I have refactored at the moment to use a dynamic StateBag and factories, so it鈥檚 not a blocker for me, but I can imagine a scenario where it would make life unnecessarily difficult.

Scoped services have an issues when specified using a factory

3399

Scoped services have an issues when specified using a factory

3399

So just adding to my issue - I can confirm this is also a problem - the IServiceProvider supplied as an argument to service registrations with delegates is the root scoped provider, not the current scoped provider. This is a pretty serious issue now.

Adding fuel to the fire, the global filters (an array of registered IFunctionInvocationFilter objects are passed into the FunctionExecutor as a ctor argument. Because the FunctionExecutor is registered as a singleton it means that the filters also have to be singleton, even though each call to this filters is made within the context of a single function execution (i.e. they are logically executed within an invocation scope). Rightly, I would guess, these filters should be registered as Scoped which means removing them from the ctor of FunctionExecutor.

It seems like there hasn't been a lot of thought put into Scoped service scenarios here, unfortunately.

@richardhauer moving this to triaged for assignment, but keeping this scoped to the originally reported issue. Function filters are not currently supported and not tested for those scenarios.

@fabiocav The use-case of function filters was only the event that highlighted this issue for me. The actual problem is that IServiceProvider is not the scoped provider in several cases where one would expect it to be, including DI factories and function execution. This creates a subtle error where scoped services are actually singleton services that never get disposed, which will lead to a number of issues including memory leaks, handle/port exhaustion, and security issues.

The fix for this has shipped

Was this page helpful?
0 / 5 - 0 ratings

Related issues

paulbatum picture paulbatum  路  4Comments

ahmelsayed picture ahmelsayed  路  4Comments

JasonBSteele picture JasonBSteele  路  3Comments

ElvenSpellmaker picture ElvenSpellmaker  路  3Comments

rati3l picture rati3l  路  3Comments