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

Azure Function DI Null Exception #1305

Closed
b-twis opened this issue Dec 18, 2019 · 18 comments · Fixed by #1363
Closed

Azure Function DI Null Exception #1305

b-twis opened this issue Dec 18, 2019 · 18 comments · Fixed by #1363
Assignees
Labels
🔍 investigate Indicates that an issue or pull request needs more information.
Milestone

Comments

@b-twis
Copy link

b-twis commented Dec 18, 2019

Describe the bug
Null Exception during Dependency Injection within Azure Functions. Working in 10.0.1 only.

To Reproduce
Using Repository located https://github.com/OneCyrus/GraphQL-AzureFunctions-HotChocolate

Steps to reproduce the behavior:

  1. Download the above sample repository
  2. Query data and get back results - Success
    eg
{
    droid(id:2000) {
        name
    }
}
  1. Update packages to any version after 10.0.1 (both 10.2.0 and 11.0.0-preview.70 have same issue)
  2. Run Query in step 2 - results in a NULL ref exception
{
    "data": {},
    "extensions": {},
    "errors": [
        {
            "message": "Unexpected Execution Error",
            "code": null,
            "path": null,
            "locations": [],
            "exception": {
                "ClassName": "System.NullReferenceException",
                "Message": "Object reference not set to an instance of an object.",
                "Data": null,
                "InnerException": null,
                "HelpURL": null,
                "StackTraceString": "   at lambda_method(Closure , IResolverContext )\r\n   at DryIoc.Factory.<>c__DisplayClass26_0.<ApplyReuse>b__2() in C:\\azure-webjobs-sdk-script\\src\\WebJobs.Script.WebHost\\DependencyInjection\\DryIoc\\Container.cs:line 6596\r\n   at DryIoc.Scope.TryGetOrAdd(ImMap`1 items, Int32 id, CreateScopedValue createValue, Int32 disposalOrder) in C:\\azure-webjobs-sdk-script\\src\\WebJobs.Script.WebHost\\DependencyInjection\\DryIoc\\Container.cs:line 7840\r\n   at DryIoc.Scope.GetOrAdd(Int32 id, CreateScopedValue createValue, Int32 disposalOrder) in C:\\azure-webjobs-sdk-script\\src\\WebJobs.Script.WebHost\\DependencyInjection\\DryIoc\\Container.cs:line 7825\r\n   at DryIoc.Factory.ApplyReuse(Expression serviceExpr, Request request) in C:\\azure-webjobs-sdk-script\\src\\WebJobs.Script.WebHost\\DependencyInjection\\DryIoc\\Container.cs:line 6595\r\n   at DryIoc.Factory.GetExpressionOrDefault(Request request) in C:\\azure-webjobs-sdk-script\\src\\WebJobs.Script.WebHost\\DependencyInjection\\DryIoc\\Container.cs:line 6555\r\n   at DryIoc.Factory.GetDelegateOrDefault(Request request) in C:\\azure-webjobs-sdk-script\\src\\WebJobs.Script.WebHost\\DependencyInjection\\DryIoc\\Container.cs:line 6625\r\n   at DryIoc.Container.ResolveAndCacheDefaultFactoryDelegate(Type serviceType, IfUnresolved ifUnresolved) in C:\\azure-webjobs-sdk-script\\src\\WebJobs.Script.WebHost\\DependencyInjection\\DryIoc\\Container.cs:line 210\r\n   at DryIoc.Container.DryIoc.IResolver.Resolve(Type serviceType, IfUnresolved ifUnresolved) in C:\\azure-webjobs-sdk-script\\src\\WebJobs.Script.WebHost\\DependencyInjection\\DryIoc\\Container.cs:line 195\r\n   at Microsoft.Azure.WebJobs.Script.WebHost.DependencyInjection.JobHostServiceProvider.GetService(Type serviceType, IfUnresolved ifUnresolved) in C:\\azure-webjobs-sdk-script\\src\\WebJobs.Script.WebHost\\DependencyInjection\\JobHostServiceProvider.cs:line 101\r\n   at Microsoft.Azure.WebJobs.Script.WebHost.DependencyInjection.JobHostServiceProvider.GetService(Type serviceType) in C:\\azure-webjobs-sdk-script\\src\\WebJobs.Script.WebHost\\DependencyInjection\\JobHostServiceProvider.cs:line 79\r\n   at lambda_method(Closure , IServiceProvider , QueryDelegate )\r\n   at HotChocolate.Execution.ClassMiddlewareFactory.<>c__DisplayClass0_0`1.<Create>b__1(IServiceProvider s, QueryDelegate n)\r\n   at HotChocolate.Execution.ClassMiddlewareFactory.<>c__DisplayClass2_0`1.<CreateDelegate>b__0(IQueryContext context)\r\n   at HotChocolate.Execution.ExceptionMiddleware.InvokeAsync(IQueryContext context)",
                "RemoteStackTraceString": null,
                "RemoteStackIndex": 0,
                "ExceptionMethod": null,
                "HResult": -2147467261,
                "Source": "Anonymously Hosted DynamicMethods Assembly",
                "WatsonBuckets": null
            },
            "extensions": {
                "message": "Object reference not set to an instance of an object.",
                "stackTrace": "   at lambda_method(Closure , IResolverContext )\r\n   at DryIoc.Factory.<>c__DisplayClass26_0.<ApplyReuse>b__2() in C:\\azure-webjobs-sdk-script\\src\\WebJobs.Script.WebHost\\DependencyInjection\\DryIoc\\Container.cs:line 6596\r\n   at DryIoc.Scope.TryGetOrAdd(ImMap`1 items, Int32 id, CreateScopedValue createValue, Int32 disposalOrder) in C:\\azure-webjobs-sdk-script\\src\\WebJobs.Script.WebHost\\DependencyInjection\\DryIoc\\Container.cs:line 7840\r\n   at DryIoc.Scope.GetOrAdd(Int32 id, CreateScopedValue createValue, Int32 disposalOrder) in C:\\azure-webjobs-sdk-script\\src\\WebJobs.Script.WebHost\\DependencyInjection\\DryIoc\\Container.cs:line 7825\r\n   at DryIoc.Factory.ApplyReuse(Expression serviceExpr, Request request) in C:\\azure-webjobs-sdk-script\\src\\WebJobs.Script.WebHost\\DependencyInjection\\DryIoc\\Container.cs:line 6595\r\n   at DryIoc.Factory.GetExpressionOrDefault(Request request) in C:\\azure-webjobs-sdk-script\\src\\WebJobs.Script.WebHost\\DependencyInjection\\DryIoc\\Container.cs:line 6555\r\n   at DryIoc.Factory.GetDelegateOrDefault(Request request) in C:\\azure-webjobs-sdk-script\\src\\WebJobs.Script.WebHost\\DependencyInjection\\DryIoc\\Container.cs:line 6625\r\n   at DryIoc.Container.ResolveAndCacheDefaultFactoryDelegate(Type serviceType, IfUnresolved ifUnresolved) in C:\\azure-webjobs-sdk-script\\src\\WebJobs.Script.WebHost\\DependencyInjection\\DryIoc\\Container.cs:line 210\r\n   at DryIoc.Container.DryIoc.IResolver.Resolve(Type serviceType, IfUnresolved ifUnresolved) in C:\\azure-webjobs-sdk-script\\src\\WebJobs.Script.WebHost\\DependencyInjection\\DryIoc\\Container.cs:line 195\r\n   at Microsoft.Azure.WebJobs.Script.WebHost.DependencyInjection.JobHostServiceProvider.GetService(Type serviceType, IfUnresolved ifUnresolved) in C:\\azure-webjobs-sdk-script\\src\\WebJobs.Script.WebHost\\DependencyInjection\\JobHostServiceProvider.cs:line 101\r\n   at Microsoft.Azure.WebJobs.Script.WebHost.DependencyInjection.JobHostServiceProvider.GetService(Type serviceType) in C:\\azure-webjobs-sdk-script\\src\\WebJobs.Script.WebHost\\DependencyInjection\\JobHostServiceProvider.cs:line 79\r\n   at lambda_method(Closure , IServiceProvider , QueryDelegate )\r\n   at HotChocolate.Execution.ClassMiddlewareFactory.<>c__DisplayClass0_0`1.<Create>b__1(IServiceProvider s, QueryDelegate n)\r\n   at HotChocolate.Execution.ClassMiddlewareFactory.<>c__DisplayClass2_0`1.<CreateDelegate>b__0(IQueryContext context)\r\n   at HotChocolate.Execution.ExceptionMiddleware.InvokeAsync(IQueryContext context)"
            }
        }
    ],
    "contextData": {}
}

Expected behavior
I am not sure what fundamental changes occur in between 10.0.1 and 10.2.0, but something is affecting the DI stopping it from working in Azure Functions.

Desktop (please complete the following information):

  • OS: Windows 10
  • Visual Studio Community 2019
  • .Net Core 3.0
  • Azure Runtime v3

Additional context
Here are some of the variables I see when the exception is encountered.
image

My guess is there is a change in what this bit of code does which is causing the DI issues

 builder.Services.AddGraphQL(sp => SchemaBuilder.New()
                .AddServices(sp)
                .AddQueryType<QueryType>()
                .AddType<HumanType>()
                .Create());

Any pointers or assistance would be greatly apreciated

Thanks, Basil

@b-twis
Copy link
Author

b-twis commented Dec 18, 2019

#520 links to supplied example repo and suggests that there will be native support. @michaelstaib you mention there will be native support in version 11. How far off is this likely to be? and do you have any working examples that I could use?

@PascalSenn
Copy link
Member

@OneCyrus had similar issue, maybe he knows more

@OneCyrus
Copy link
Contributor

the problem is likely the scoped DI which has a different lifetime in AzF: https://docs.microsoft.com/en-us/azure/azure-functions/functions-dotnet-dependency-injection#service-lifetimes

@OneCyrus
Copy link
Contributor

@b-twis
Copy link
Author

b-twis commented Dec 18, 2019

we discussed this here: https://hotchocolategraphql.slack.com/archives/CD9TNKT8T/p1569335037212400

I dont have access to Slack. Could you provide a quick summary?

Is it an issue with how HC adds the GraphQL services during Configure?

@OneCyrus
Copy link
Contributor

image

@michaelstaib michaelstaib added Hot Chocolate Server 🔍 investigate Indicates that an issue or pull request needs more information. labels Dec 18, 2019
@michaelstaib michaelstaib added this to the 11.0.0 milestone Dec 18, 2019
@OneCyrus
Copy link
Contributor

i just gave this one a try again. and it doesn't look like the azure functions DI behaves incorrectly. the scoped instances are created correctly. there's also a sample on the AzF repo which shows this:

https://github.com/Azure/azure-functions-dotnet-extensions/tree/master/src/samples/DependencyInjection/Scopes
(though i had to update the dependencies to get it up and running)

it looks like there's really an issue where hotchocolate behaves strange in combination with AzF DI.

@PascalSenn
Copy link
Member

PascalSenn commented Dec 28, 2019

Hm... @michaelstaib could this be related to the custom service factory?

Somehow loosing state

@michaelstaib
Copy link
Member

I think the issue is how we build the execution builder. We will fix that in 11 since we will take apart the whole execution. But first I want to focus more on StrawberryShake and Visual Studio integration.

Once Rafi is done with the first public preview of bcp we can start work on this.

@PascalSenn
Copy link
Member

Ok. So @OneCyrus and I were debugging a bit.
The functions really behave strange. We could narrow down the issue and think it is related to the DI more specific to the DI injecting this class. https://github.com/ChilliCream/hotchocolate/blob/master/src/Core/Core/Execution/Middleware/ParseQueryMiddleware.cs

The DI containers fails when it tries to inject IDocumentHashProvider documentHashProvider

This behavior is easily reproducible by registering the IDocumentHashProvider in the function startup:

            builder.Services.AddSingleton<IDocumentHashProvider>((sp) => new MD5DocumentHashProvider());

and then fetch the dependency in function method. It doesn't matter if you do it with ctor injection, over the contextAccessor, or with the HTTPContext in the Request, it all fails :

            var services = req.HttpContext.RequestServices;
            var test = services .GetService<ReviewRepository>(); // works
            var test = services .GetService<IDocumentHashProvider>(); // 💣

And now it becomes really weird.

  1. We registered an instance. As we register a singleton i really expect this instance to be returned.
    In fact, the DI does try to activate the dependency via activator

  2. MD5DocumentHashProvider has two constructors. One without a parameter and one with a parameter. I don't know about the ctor scoring algorithm of this DI implementation. but it chooses the wrong one.
    image

  3. Well.. In the end we don't really need this instance anyway. If the DI injects null this case is handled in the ctor of ParseQueryMiddleware.cs. But the DI Container blows up as soon as it doesnt find a reference.

@michaelstaib
Copy link
Member

so GetService on their container is really GetRequiredService?

@OneCyrus
Copy link
Contributor

it looks like that. GetService stops the execution of the function when it doesn't find something and doesn't just return null. though it's not the implementation of the DI as it's the same Microsoft.Extension.DependencyInjection package which handles this.

@OneCyrus
Copy link
Contributor

though it's probably just because of using the wrong ctor for the HashProvider

@PascalSenn
Copy link
Member

hm.. it's not supposed to be created by the DI. The DI should just resolve it :D

@OneCyrus
Copy link
Contributor

looks like manually registering the HashDocumentProvider after the GraphQL-initialization fixed the issue.

OneCyrus/GraphQL-AzureFunctions-HotChocolate@a37c68a#diff-fbd03c53f6f1dc7701583e41f0cda5c8

the feat_nativeDI branch works now with the latest HC version:
https://github.com/OneCyrus/GraphQL-AzureFunctions-HotChocolate

@b-twis
Copy link
Author

b-twis commented Jan 7, 2020

Thank you. I have been able to get it working using the suggested approach for now.

Keep me posted if it gets fixed in V 11.

Thanks

@OneCyrus
Copy link
Contributor

OneCyrus commented Jan 9, 2020

@michaelstaib
Copy link
Member

This one is now merged and will be part of 10.3.4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🔍 investigate Indicates that an issue or pull request needs more information.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants