diff --git a/src/Microsoft.Azure.WebJobs.Host/Executors/ActivatorInstanceFactory.cs b/src/Microsoft.Azure.WebJobs.Host/Executors/ActivatorInstanceFactory.cs index 77975fe963..1941b340e8 100644 --- a/src/Microsoft.Azure.WebJobs.Host/Executors/ActivatorInstanceFactory.cs +++ b/src/Microsoft.Azure.WebJobs.Host/Executors/ActivatorInstanceFactory.cs @@ -5,23 +5,25 @@ namespace Microsoft.Azure.WebJobs.Host.Executors { - internal class ActivatorInstanceFactory : IFactory + internal class ActivatorInstanceFactory : IJobInstanceFactory { - private readonly IJobActivator _activator; + private readonly Func _createInstance; public ActivatorInstanceFactory(IJobActivator activator) { if (activator == null) { - throw new ArgumentNullException("activator"); + throw new ArgumentNullException(nameof(activator)); } - _activator = activator; + _createInstance = activator is IJobActivatorEx activatorEx + ? new Func(i => activatorEx.CreateInstance(i)) + : new Func(i => activator.CreateInstance()); } - public TReflected Create() + public T Create(IFunctionInstanceEx functionInstance) { - return _activator.CreateInstance(); + return _createInstance(functionInstance); } } } diff --git a/src/Microsoft.Azure.WebJobs.Host/Executors/DefaultJobActivator.cs b/src/Microsoft.Azure.WebJobs.Host/Executors/DefaultJobActivator.cs index a2e6d34ba2..425d078574 100644 --- a/src/Microsoft.Azure.WebJobs.Host/Executors/DefaultJobActivator.cs +++ b/src/Microsoft.Azure.WebJobs.Host/Executors/DefaultJobActivator.cs @@ -11,7 +11,7 @@ namespace Microsoft.Azure.WebJobs.Host.Executors /// The default integrates with DI, /// supporting constructor injection for registered services. /// - internal class DefaultJobActivator : IJobActivator + internal class DefaultJobActivator : IJobActivatorEx { private readonly IServiceProvider _serviceProvider; private readonly ConcurrentDictionary _factories; @@ -23,13 +23,23 @@ public DefaultJobActivator(IServiceProvider serviceProvider) } public T CreateInstance() + { + return CreateInstance(_serviceProvider); + } + + public T CreateInstance(IFunctionInstanceEx functionInstance) + { + return CreateInstance(functionInstance.InstanceServices); + } + + private T CreateInstance(IServiceProvider serviceProvider) { var factory = _factories.GetOrAdd(typeof(T), t => { return ActivatorUtilities.CreateFactory(t, Type.EmptyTypes); }); - return (T)factory(_serviceProvider, null); + return (T)factory(serviceProvider, null); } } } diff --git a/src/Microsoft.Azure.WebJobs.Host/Executors/FunctionExecutor.cs b/src/Microsoft.Azure.WebJobs.Host/Executors/FunctionExecutor.cs index ab0151025d..6dc3eafce3 100644 --- a/src/Microsoft.Azure.WebJobs.Host/Executors/FunctionExecutor.cs +++ b/src/Microsoft.Azure.WebJobs.Host/Executors/FunctionExecutor.cs @@ -17,6 +17,7 @@ using Microsoft.Azure.WebJobs.Host.Protocols; using Microsoft.Azure.WebJobs.Host.Timers; using Microsoft.Azure.WebJobs.Logging; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Microsoft.Azure.WebJobs.Host.Executors @@ -26,6 +27,7 @@ internal class FunctionExecutor : IFunctionExecutor private readonly IFunctionInstanceLogger _functionInstanceLogger; private readonly IWebJobsExceptionHandler _exceptionHandler; private readonly IAsyncCollector _functionEventCollector; + private readonly IServiceScopeFactory _serviceScopeFactory; private readonly ILoggerFactory _loggerFactory; private readonly ILogger _resultsLogger; private readonly IEnumerable _globalFunctionFilters; @@ -50,6 +52,7 @@ public FunctionExecutor( IFunctionOutputLogger functionOutputLogger, IWebJobsExceptionHandler exceptionHandler, IAsyncCollector functionEventCollector, + IServiceScopeFactory serviceScopeFactory, ILoggerFactory loggerFactory = null, IEnumerable globalFunctionFilters = null) { @@ -57,6 +60,7 @@ public FunctionExecutor( _functionOutputLogger = functionOutputLogger; _exceptionHandler = exceptionHandler ?? throw new ArgumentNullException(nameof(exceptionHandler)); _functionEventCollector = functionEventCollector ?? throw new ArgumentNullException(nameof(functionEventCollector)); + _serviceScopeFactory = serviceScopeFactory; _loggerFactory = loggerFactory; _resultsLogger = _loggerFactory?.CreateLogger(LogCategories.Results); _globalFunctionFilters = globalFunctionFilters ?? Enumerable.Empty(); @@ -68,8 +72,26 @@ public HostOutputMessage HostOutputMessage set { _hostOutputMessage = value; } } - public async Task TryExecuteAsync(IFunctionInstance functionInstance, CancellationToken cancellationToken) + public async Task TryExecuteAsync(IFunctionInstance instance, CancellationToken cancellationToken) { + if (!(instance is IFunctionInstanceEx functionInstance)) + { + functionInstance = new FunctionInstanceWrapper(instance, _serviceScopeFactory); + } + + try + { + return await TryExecuteAsyncCore(functionInstance, cancellationToken); + } + finally + { + (functionInstance as FunctionInstanceWrapper)?.Dispose(); + } + } + + private async Task TryExecuteAsyncCore(IFunctionInstanceEx functionInstance, CancellationToken cancellationToken) + { + ILogger logger = _loggerFactory?.CreateLogger(LogCategories.CreateFunctionCategory(functionInstance.FunctionDescriptor.LogName)); FunctionStartedMessage functionStartedMessage = CreateStartedMessageWithoutArguments(functionInstance); @@ -196,7 +218,7 @@ internal static async Task HandleExceptionAsync(TimeoutAttribute timeout, Except } } - private async Task ExecuteWithLoggingAsync(IFunctionInstance instance, FunctionStartedMessage message, + private async Task ExecuteWithLoggingAsync(IFunctionInstanceEx instance, FunctionStartedMessage message, FunctionInstanceLogEntry instanceLogEntry, ParameterHelper parameterHelper, ILogger logger, CancellationToken cancellationToken) { IFunctionOutputDefinition outputDefinition = null; @@ -420,7 +442,7 @@ private static ITaskSeriesTimer StartParameterLogTimer(IRecurrentCommand updateC return timer; } - private async Task ExecuteWithLoggingAsync(IFunctionInstance instance, + private async Task ExecuteWithLoggingAsync(IFunctionInstanceEx instance, ParameterHelper parameterHelper, IFunctionOutputDefinition outputDefinition, ILogger logger, @@ -458,12 +480,12 @@ private async Task ExecuteWithLoggingAsync(IFunctionInstance instance, } } - internal async Task ExecuteWithWatchersAsync(IFunctionInstance instance, + internal async Task ExecuteWithWatchersAsync(IFunctionInstanceEx instance, ParameterHelper parameterHelper, ILogger logger, CancellationTokenSource functionCancellationTokenSource) { - IFunctionInvoker invoker = instance.Invoker; + IFunctionInvokerEx invoker = instance.GetFunctionInvoker(); IDelayedException delayedBindingException = await parameterHelper.PrepareParametersAsync(); if (delayedBindingException != null) @@ -760,7 +782,7 @@ private Task NotifyCompleteAsync(FunctionInstanceLogEntry instanceLogEntry, IDic // 3. System.Object[]. which can be passed to the actual MethodInfo for execution internal class ParameterHelper : IDisposable { - private readonly IFunctionInstance _functionInstance; + private readonly IFunctionInstanceEx _functionInstance; // Logs, contain the result from invoking the IWatchers. private IDictionary _parameterLogCollector = new Dictionary(); @@ -789,7 +811,7 @@ public ParameterHelper() { } - public ParameterHelper(IFunctionInstance functionInstance) + public ParameterHelper(IFunctionInstanceEx functionInstance) { if (functionInstance == null) { @@ -814,7 +836,7 @@ public ParameterHelper(IFunctionInstance functionInstance) public void Initialize() { - JobInstance = _functionInstance.Invoker.CreateInstance(); + JobInstance = _functionInstance.GetFunctionInvoker().CreateInstance(); } // Convert the parameters and their names to a dictionary @@ -1083,17 +1105,17 @@ private static BindStepOrder GetStepOrder(IValueProvider provider) } } - private class FunctionInvocationFilterInvoker : IFunctionInvoker + private class FunctionInvocationFilterInvoker : IFunctionInvokerEx { private List _filters; - private IFunctionInvoker _innerInvoker; + private IFunctionInvokerEx _innerInvoker; private IFunctionInstance _functionInstance; private ParameterHelper _parameterHelper; private ILogger _logger; public IReadOnlyList ParameterNames => _innerInvoker.ParameterNames; - public static IFunctionInvoker Create(IFunctionInvoker innerInvoker, List filters, IFunctionInstance functionInstance, ParameterHelper parameterHelper, ILogger logger) + public static IFunctionInvokerEx Create(IFunctionInvokerEx innerInvoker, List filters, IFunctionInstance functionInstance, ParameterHelper parameterHelper, ILogger logger) { if (filters.Count == 0) { @@ -1112,7 +1134,12 @@ public static IFunctionInvoker Create(IFunctionInvoker innerInvoker, List InvokeAsync(object instance, object[] arguments) diff --git a/src/Microsoft.Azure.WebJobs.Host/Executors/FunctionInstance.cs b/src/Microsoft.Azure.WebJobs.Host/Executors/FunctionInstance.cs index b85c86b281..b2cb06df45 100644 --- a/src/Microsoft.Azure.WebJobs.Host/Executors/FunctionInstance.cs +++ b/src/Microsoft.Azure.WebJobs.Host/Executors/FunctionInstance.cs @@ -9,59 +9,30 @@ namespace Microsoft.Azure.WebJobs.Host.Executors { internal class FunctionInstance : IFunctionInstance { - private readonly Guid _id; - private readonly IDictionary _triggerDetails; - private readonly Guid? _parentId; - private readonly ExecutionReason _reason; - private readonly IBindingSource _bindingSource; - private readonly IFunctionInvoker _invoker; - private readonly FunctionDescriptor _functionDescriptor; - public FunctionInstance(Guid id, IDictionary triggerDetails, Guid? parentId, ExecutionReason reason, IBindingSource bindingSource, IFunctionInvoker invoker, FunctionDescriptor functionDescriptor) { - _id = id; - _triggerDetails = triggerDetails; - _parentId = parentId; - _reason = reason; - _bindingSource = bindingSource; - _invoker = invoker; - _functionDescriptor = functionDescriptor; + Id = id; + TriggerDetails = triggerDetails; + ParentId = parentId; + Reason = reason; + BindingSource = bindingSource; + Invoker = invoker; + FunctionDescriptor = functionDescriptor; } - public Guid Id - { - get { return _id; } - } + public Guid Id { get; } - public IDictionary TriggerDetails - { - get { return _triggerDetails; } - } + public IDictionary TriggerDetails { get; } - public Guid? ParentId - { - get { return _parentId; } - } + public Guid? ParentId { get; } - public ExecutionReason Reason - { - get { return _reason; } - } + public ExecutionReason Reason { get; } - public IBindingSource BindingSource - { - get { return _bindingSource; } - } + public IBindingSource BindingSource { get; } - public IFunctionInvoker Invoker - { - get { return _invoker; } - } + public IFunctionInvoker Invoker { get; } - public FunctionDescriptor FunctionDescriptor - { - get { return _functionDescriptor; } - } + public FunctionDescriptor FunctionDescriptor { get; } } } diff --git a/src/Microsoft.Azure.WebJobs.Host/Executors/FunctionInstanceExtensions.cs b/src/Microsoft.Azure.WebJobs.Host/Executors/FunctionInstanceExtensions.cs new file mode 100644 index 0000000000..ec83d37439 --- /dev/null +++ b/src/Microsoft.Azure.WebJobs.Host/Executors/FunctionInstanceExtensions.cs @@ -0,0 +1,38 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.Azure.WebJobs.Host.Executors +{ + public static class FunctionInstanceExtensions + { + public static IServiceProvider GetInstanceServices(this IFunctionInstance instance) + { + if (instance is IFunctionInstanceEx functionInstance) + { + return functionInstance.InstanceServices; + } + + return null; + } + + internal static IFunctionInvokerEx GetFunctionInvoker(this IFunctionInstance instance) + { + if (instance.Invoker == null) + { + return null; + } + + if (instance.Invoker is IFunctionInvokerEx invoker) + { + return invoker; + } + + + return new FunctionInvokerWrapper(instance.Invoker); + } + } +} diff --git a/src/Microsoft.Azure.WebJobs.Host/Executors/FunctionInstanceWrapper.cs b/src/Microsoft.Azure.WebJobs.Host/Executors/FunctionInstanceWrapper.cs new file mode 100644 index 0000000000..7a08289e16 --- /dev/null +++ b/src/Microsoft.Azure.WebJobs.Host/Executors/FunctionInstanceWrapper.cs @@ -0,0 +1,63 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.Azure.WebJobs.Host.Protocols; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.Azure.WebJobs.Host.Executors +{ + internal class FunctionInstanceWrapper : IFunctionInstanceEx, IDisposable + { + private readonly IFunctionInstance _instance; + private readonly IServiceScopeFactory _serviceScopeFactory; + private IServiceScope _instanceServicesScope; + private IServiceProvider _instanceServices; + + public FunctionInstanceWrapper(IFunctionInstance instance, IServiceScopeFactory serviceScopeFactory) + { + _instance = instance; + _serviceScopeFactory = serviceScopeFactory; + } + + public Guid Id => _instance.Id; + + public IDictionary TriggerDetails => _instance.TriggerDetails; + + public Guid? ParentId => _instance.ParentId; + + public ExecutionReason Reason => _instance.Reason; + + public IBindingSource BindingSource => _instance.BindingSource; + + public IFunctionInvoker Invoker => _instance.Invoker; + + public FunctionDescriptor FunctionDescriptor => _instance.FunctionDescriptor; + + public IServiceProvider InstanceServices + { + get + { + if (_instanceServicesScope == null && _serviceScopeFactory != null) + { + _instanceServicesScope = _serviceScopeFactory.CreateScope(); + _instanceServices = _instanceServicesScope.ServiceProvider; + } + + return _instanceServices; + } + } + + public void Dispose() + { + if (_instanceServicesScope != null) + { + _instanceServicesScope.Dispose(); + } + + _instanceServicesScope = null; + _instanceServices = null; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.Azure.WebJobs.Host/Executors/FunctionInvoker.cs b/src/Microsoft.Azure.WebJobs.Host/Executors/FunctionInvoker.cs index d88378dcb4..73bd7023cb 100644 --- a/src/Microsoft.Azure.WebJobs.Host/Executors/FunctionInvoker.cs +++ b/src/Microsoft.Azure.WebJobs.Host/Executors/FunctionInvoker.cs @@ -7,38 +7,23 @@ namespace Microsoft.Azure.WebJobs.Host.Executors { - internal class FunctionInvoker : IFunctionInvoker + internal class FunctionInvoker : IFunctionInvokerEx { private readonly IReadOnlyList _parameterNames; - private readonly IFactory _instanceFactory; + private readonly IJobInstanceFactory _instanceFactory; private readonly IMethodInvoker _methodInvoker; public FunctionInvoker( IReadOnlyList parameterNames, - IFactory instanceFactory, + IJobInstanceFactory instanceFactory, IMethodInvoker methodInvoker) { - if (parameterNames == null) - { - throw new ArgumentNullException("parameterNames"); - } - - if (instanceFactory == null) - { - throw new ArgumentNullException("instanceFactory"); - } - - if (methodInvoker == null) - { - throw new ArgumentNullException("methodInvoker"); - } - - _parameterNames = parameterNames; - _instanceFactory = instanceFactory; - _methodInvoker = methodInvoker; + _parameterNames = parameterNames ?? throw new ArgumentNullException(nameof(parameterNames)); + _instanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory)); + _methodInvoker = methodInvoker ?? throw new ArgumentNullException(nameof(methodInvoker)); } - public IFactory InstanceFactory + public IJobInstanceFactory InstanceFactory { get { return _instanceFactory; } } @@ -51,8 +36,12 @@ public IReadOnlyList ParameterNames public object CreateInstance() { - TReflected instance = _instanceFactory.Create(); - return instance; + throw new NotSupportedException($"{nameof(CreateInstance)} is not supported, please use the overload that accepts an {nameof(IFunctionInstance)} argument."); + } + + public object CreateInstance(IFunctionInstanceEx functionInstance) + { + return _instanceFactory.Create(functionInstance); } public async Task InvokeAsync(object instance, object[] arguments) @@ -60,7 +49,7 @@ public async Task InvokeAsync(object instance, object[] arguments) // Return a task immediately in case the method is not async. await Task.Yield(); - return await _methodInvoker.InvokeAsync((TReflected) instance, arguments); + return await _methodInvoker.InvokeAsync((TReflected)instance, arguments); } } } diff --git a/src/Microsoft.Azure.WebJobs.Host/Executors/FunctionInvokerFactory.cs b/src/Microsoft.Azure.WebJobs.Host/Executors/FunctionInvokerFactory.cs index 621c6bee9c..43f46264c7 100644 --- a/src/Microsoft.Azure.WebJobs.Host/Executors/FunctionInvokerFactory.cs +++ b/src/Microsoft.Azure.WebJobs.Host/Executors/FunctionInvokerFactory.cs @@ -53,12 +53,12 @@ private static IFunctionInvoker CreateGeneric( IMethodInvoker methodInvoker = MethodInvokerFactory.Create(method); - IFactory instanceFactory = CreateInstanceFactory(method, activator); + IJobInstanceFactory instanceFactory = CreateInstanceFactory(method, activator); return new FunctionInvoker(parameterNames, instanceFactory, methodInvoker); } - private static IFactory CreateInstanceFactory(MethodInfo method, + private static IJobInstanceFactory CreateInstanceFactory(MethodInfo method, IJobActivator jobActivator) { Debug.Assert(method != null); diff --git a/src/Microsoft.Azure.WebJobs.Host/Executors/FunctionInvokerWrapper.cs b/src/Microsoft.Azure.WebJobs.Host/Executors/FunctionInvokerWrapper.cs new file mode 100644 index 0000000000..1cce9bfe1d --- /dev/null +++ b/src/Microsoft.Azure.WebJobs.Host/Executors/FunctionInvokerWrapper.cs @@ -0,0 +1,39 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.Azure.WebJobs.Host.Executors +{ + /// + /// Wraps an instance of an into an implementation of , maintaining + /// the behavior of the wrapped instance. + /// + internal class FunctionInvokerWrapper : IFunctionInvokerEx + { + private readonly IFunctionInvoker _functionInvoker; + + public FunctionInvokerWrapper(IFunctionInvoker functionInvoker) + { + _functionInvoker = functionInvoker; + } + + public IReadOnlyList ParameterNames => _functionInvoker.ParameterNames; + + public object CreateInstance(IFunctionInstanceEx functionInstance) + { + return CreateInstance(); + } + + public object CreateInstance() + { + return _functionInvoker.CreateInstance(); + } + + public Task InvokeAsync(object instance, object[] arguments) + { + return _functionInvoker.InvokeAsync(instance, arguments); + } + } +} diff --git a/src/Microsoft.Azure.WebJobs.Host/Executors/HostMessageExecutor.cs b/src/Microsoft.Azure.WebJobs.Host/Executors/HostMessageExecutor.cs index 12be340099..4997056b68 100644 --- a/src/Microsoft.Azure.WebJobs.Host/Executors/HostMessageExecutor.cs +++ b/src/Microsoft.Azure.WebJobs.Host/Executors/HostMessageExecutor.cs @@ -120,6 +120,7 @@ private IFunctionInstance CreateFunctionInstance(CallAndOverrideMessage message) private async Task ProcessCallAndOverrideMessage(CallAndOverrideMessage message, CancellationToken cancellationToken) { + // TODO: Add disposal pattern IFunctionInstance instance = CreateFunctionInstance(message); if (instance != null) diff --git a/src/Microsoft.Azure.WebJobs.Host/Executors/IActivatorInstanceFactory.cs b/src/Microsoft.Azure.WebJobs.Host/Executors/IActivatorInstanceFactory.cs new file mode 100644 index 0000000000..b27b2c826a --- /dev/null +++ b/src/Microsoft.Azure.WebJobs.Host/Executors/IActivatorInstanceFactory.cs @@ -0,0 +1,10 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.Azure.WebJobs.Host.Executors +{ + internal interface IJobInstanceFactory + { + T Create(IFunctionInstanceEx functionInstance); + } +} \ No newline at end of file diff --git a/src/Microsoft.Azure.WebJobs.Host/Executors/IFunctionInstance.cs b/src/Microsoft.Azure.WebJobs.Host/Executors/IFunctionInstance.cs index 0119c338a0..f3adcd127f 100644 --- a/src/Microsoft.Azure.WebJobs.Host/Executors/IFunctionInstance.cs +++ b/src/Microsoft.Azure.WebJobs.Host/Executors/IFunctionInstance.cs @@ -23,4 +23,9 @@ public interface IFunctionInstance FunctionDescriptor FunctionDescriptor { get; } } -} + + public interface IFunctionInstanceEx : IFunctionInstance + { + IServiceProvider InstanceServices { get; } + } +} \ No newline at end of file diff --git a/src/Microsoft.Azure.WebJobs.Host/Executors/IFunctionInvokerEx.cs b/src/Microsoft.Azure.WebJobs.Host/Executors/IFunctionInvokerEx.cs new file mode 100644 index 0000000000..52be54236f --- /dev/null +++ b/src/Microsoft.Azure.WebJobs.Host/Executors/IFunctionInvokerEx.cs @@ -0,0 +1,11 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + + +namespace Microsoft.Azure.WebJobs.Host.Executors +{ + internal interface IFunctionInvokerEx : IFunctionInvoker + { + object CreateInstance(IFunctionInstanceEx functionInstance); + } +} diff --git a/src/Microsoft.Azure.WebJobs.Host/Executors/NullInstanceFactory.cs b/src/Microsoft.Azure.WebJobs.Host/Executors/NullInstanceFactory.cs index 71d03a5709..fec5d07628 100644 --- a/src/Microsoft.Azure.WebJobs.Host/Executors/NullInstanceFactory.cs +++ b/src/Microsoft.Azure.WebJobs.Host/Executors/NullInstanceFactory.cs @@ -3,20 +3,15 @@ namespace Microsoft.Azure.WebJobs.Host.Executors { - internal class NullInstanceFactory : IFactory + internal class NullInstanceFactory : IJobInstanceFactory { - private static readonly NullInstanceFactory Singleton = new NullInstanceFactory(); - private NullInstanceFactory() { } - public static NullInstanceFactory Instance - { - get { return Singleton; } - } + public static NullInstanceFactory Instance { get; } = new NullInstanceFactory(); - public TReflected Create() + public TReflected Create(IFunctionInstanceEx functionInstance) { return default(TReflected); } diff --git a/src/Microsoft.Azure.WebJobs.Host/Executors/TriggeredFunctionExecutor.cs b/src/Microsoft.Azure.WebJobs.Host/Executors/TriggeredFunctionExecutor.cs index bd008d0b46..0328867b60 100644 --- a/src/Microsoft.Azure.WebJobs.Host/Executors/TriggeredFunctionExecutor.cs +++ b/src/Microsoft.Azure.WebJobs.Host/Executors/TriggeredFunctionExecutor.cs @@ -35,7 +35,7 @@ public async Task TryExecuteAsync(TriggeredFunctionData input, C var context = new FunctionInstanceFactoryContext() { TriggerValue = (TTriggerValue)input.TriggerValue, - ParentId = input.ParentId, + ParentId = input.ParentId, TriggerDetails = input.TriggerDetails }; @@ -52,6 +52,7 @@ public async Task TryExecuteAsync(TriggeredFunctionData input, C } IFunctionInstance instance = _instanceFactory.Create(context); + IDelayedException exception = await _executor.TryExecuteAsync(instance, cancellationToken); FunctionResult result = exception != null ? diff --git a/src/Microsoft.Azure.WebJobs.Host/IJobActivatorEx.cs b/src/Microsoft.Azure.WebJobs.Host/IJobActivatorEx.cs new file mode 100644 index 0000000000..78b2210122 --- /dev/null +++ b/src/Microsoft.Azure.WebJobs.Host/IJobActivatorEx.cs @@ -0,0 +1,20 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.Azure.WebJobs.Host.Executors; + +namespace Microsoft.Azure.WebJobs.Host +{ + /// + /// Defines an activator that creates an instance of a job type. + /// + public interface IJobActivatorEx : IJobActivator + { + /// + /// Creates a new instance of a job type. + /// + /// The job type. + /// A new instance of the job type. + T CreateInstance(IFunctionInstanceEx functionInstance); + } +} diff --git a/src/Microsoft.Azure.WebJobs.Host/Indexers/FunctionIndexProvider.cs b/src/Microsoft.Azure.WebJobs.Host/Indexers/FunctionIndexProvider.cs index f7fb554b92..2230386e9c 100644 --- a/src/Microsoft.Azure.WebJobs.Host/Indexers/FunctionIndexProvider.cs +++ b/src/Microsoft.Azure.WebJobs.Host/Indexers/FunctionIndexProvider.cs @@ -9,6 +9,7 @@ using Microsoft.Azure.WebJobs.Host.Dispatch; using Microsoft.Azure.WebJobs.Host.Executors; using Microsoft.Azure.WebJobs.Host.Triggers; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -21,7 +22,6 @@ internal class FunctionIndexProvider : IFunctionIndexProvider private readonly CompositeBindingProvider _bindingProviderFactory; private readonly IJobActivator _activator; private readonly IFunctionExecutor _executor; - private readonly IExtensionRegistry _extensions; private readonly SingletonManager _singletonManager; private readonly ILoggerFactory _loggerFactory; private readonly SharedQueueHandler _sharedQueue; @@ -35,7 +35,6 @@ public FunctionIndexProvider(ITypeLocator typeLocator, CompositeBindingProvider bindingProviderFactory, IJobActivator activator, IFunctionExecutor executor, - IExtensionRegistry extensions, SingletonManager singletonManager, ILoggerFactory loggerFactory, SharedQueueHandler sharedQueue, @@ -48,10 +47,8 @@ public FunctionIndexProvider(ITypeLocator typeLocator, _bindingProviderFactory = bindingProviderFactory ?? throw new ArgumentNullException(nameof(bindingProviderFactory)); _activator = activator ?? throw new ArgumentNullException(nameof(activator)); _executor = executor ?? throw new ArgumentNullException(nameof(executor)); - _extensions = extensions ?? throw new ArgumentNullException(nameof(extensions)); _singletonManager = singletonManager ?? throw new ArgumentNullException(nameof(singletonManager)); _sharedQueue = sharedQueue ?? throw new ArgumentNullException(nameof(sharedQueue)); - _loggerFactory = loggerFactory; _defaultTimeout = timeoutOptions.Value.ToAttribute(); _allowPartialHostStartup = hostOptions.Value.AllowPartialHostStartup; @@ -71,7 +68,7 @@ private async Task CreateAsync(CancellationToken cancellationTok { FunctionIndex index = new FunctionIndex(); IBindingProvider bindingProvider = _bindingProviderFactory; - FunctionIndexer indexer = new FunctionIndexer(_triggerBindingProvider, bindingProvider, _activator, _executor, _extensions, _singletonManager, _loggerFactory, null, _sharedQueue, _defaultTimeout, _allowPartialHostStartup); + FunctionIndexer indexer = new FunctionIndexer(_triggerBindingProvider, bindingProvider, _activator, _executor, _singletonManager, _loggerFactory, null, _sharedQueue, _defaultTimeout, _allowPartialHostStartup); IReadOnlyList types = _typeLocator.GetTypes(); foreach (Type type in types) diff --git a/src/Microsoft.Azure.WebJobs.Host/Indexers/FunctionIndexer.cs b/src/Microsoft.Azure.WebJobs.Host/Indexers/FunctionIndexer.cs index 913654e9ab..1d1f7fae96 100644 --- a/src/Microsoft.Azure.WebJobs.Host/Indexers/FunctionIndexer.cs +++ b/src/Microsoft.Azure.WebJobs.Host/Indexers/FunctionIndexer.cs @@ -18,6 +18,7 @@ using Microsoft.Azure.WebJobs.Host.Protocols; using Microsoft.Azure.WebJobs.Host.Triggers; using Microsoft.Azure.WebJobs.Logging; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Microsoft.Azure.WebJobs.Host.Indexers @@ -44,7 +45,6 @@ public FunctionIndexer( IBindingProvider bindingProvider, IJobActivator activator, IFunctionExecutor executor, - IExtensionRegistry extensions, SingletonManager singletonManager, ILoggerFactory loggerFactory, INameResolver nameResolver = null, @@ -52,41 +52,11 @@ public FunctionIndexer( TimeoutAttribute defaultTimeout = null, bool allowPartialHostStartup = false) { - if (triggerBindingProvider == null) - { - throw new ArgumentNullException("triggerBindingProvider"); - } - - if (bindingProvider == null) - { - throw new ArgumentNullException("bindingProvider"); - } - - if (activator == null) - { - throw new ArgumentNullException("activator"); - } - - if (executor == null) - { - throw new ArgumentNullException("executor"); - } - - if (extensions == null) - { - throw new ArgumentNullException("extensions"); - } - - if (singletonManager == null) - { - throw new ArgumentNullException("singletonManager"); - } - - _triggerBindingProvider = triggerBindingProvider; - _bindingProvider = bindingProvider; - _activator = activator; - _executor = executor; - _singletonManager = singletonManager; + _triggerBindingProvider = triggerBindingProvider ?? throw new ArgumentNullException(nameof(triggerBindingProvider)); + _bindingProvider = bindingProvider ?? throw new ArgumentNullException(nameof(bindingProvider)); + _activator = activator ?? throw new ArgumentNullException(nameof(activator)); + _executor = executor ?? throw new ArgumentNullException(nameof(executor)); + _singletonManager = singletonManager ?? throw new ArgumentNullException(nameof(singletonManager)); _nameResolver = nameResolver; _logger = loggerFactory?.CreateLogger(LogCategories.Startup); _sharedQueue = sharedQueue; @@ -359,7 +329,7 @@ internal async Task IndexMethodAsyncCore(MethodInfo method, IFunctionIndexCollec private FunctionDefinition CreateTriggeredFunctionDefinition( ITriggerBinding triggerBinding, string parameterName, FunctionDescriptor descriptor, - IReadOnlyDictionary nonTriggerBindings, IFunctionInvoker invoker) + IReadOnlyDictionary nonTriggerBindings, IFunctionInvokerEx invoker) { ITriggeredFunctionBinding functionBinding = new TriggeredFunctionBinding(descriptor, parameterName, triggerBinding, nonTriggerBindings, _singletonManager); ITriggeredFunctionInstanceFactory instanceFactory = new TriggeredFunctionInstanceFactory(functionBinding, invoker, descriptor); diff --git a/src/Microsoft.Azure.WebJobs.Host/JobHost.cs b/src/Microsoft.Azure.WebJobs.Host/JobHost.cs index ae33abc986..1a08d76420 100644 --- a/src/Microsoft.Azure.WebJobs.Host/JobHost.cs +++ b/src/Microsoft.Azure.WebJobs.Host/JobHost.cs @@ -192,16 +192,20 @@ protected virtual async Task StopAsyncCore(CancellationToken cancellationToken) /// The argument names and values to bind to parameters in the job method. In addition to parameter values, these may also include binding data values. /// The token to monitor for cancellation requests. /// A that will call the job method. - public Task CallAsync(MethodInfo method, IDictionary arguments, CancellationToken cancellationToken = default(CancellationToken)) + public async Task CallAsync(MethodInfo method, IDictionary arguments, CancellationToken cancellationToken = default(CancellationToken)) { if (method == null) { - throw new ArgumentNullException("method"); + throw new ArgumentNullException(nameof(method)); } ThrowIfDisposed(); - return CallAsyncCore(method, arguments, cancellationToken); + await EnsureHostInitializedAsync(cancellationToken); + + IFunctionDefinition function = _context.FunctionLookup.Lookup(method); + + await CallAsyncCore(function, method, arguments, cancellationToken); } /// Calls a job method. @@ -221,26 +225,20 @@ protected virtual async Task StopAsyncCore(CancellationToken cancellationToken) await EnsureHostInitializedAsync(cancellationToken); IFunctionDefinition function = _context.FunctionLookup.LookupByName(name); - Validate(function, name); - IFunctionInstance instance = CreateFunctionInstance(function, arguments); - - IDelayedException exception = await _context.Executor.TryExecuteAsync(instance, cancellationToken); - - if (exception != null) - { - exception.Throw(); - } + + await CallAsyncCore(function, name, arguments, cancellationToken); } - private async Task CallAsyncCore(MethodInfo method, IDictionary arguments, CancellationToken cancellationToken) + + private async Task CallAsyncCore(IFunctionDefinition function, object functionKey, IDictionary arguments, CancellationToken cancellationToken) { - await EnsureHostInitializedAsync(cancellationToken); + Validate(function, functionKey); - IFunctionDefinition function = _context.FunctionLookup.Lookup(method); - Validate(function, method); IFunctionInstance instance = CreateFunctionInstance(function, arguments); - IDelayedException exception = await _context.Executor.TryExecuteAsync(instance, cancellationToken); + IDelayedException exception = null; + + exception = await _context.Executor.TryExecuteAsync(instance, cancellationToken); if (exception != null) { @@ -248,6 +246,7 @@ private async Task CallAsyncCore(MethodInfo method, IDictionary } } + /// /// Dispose the instance /// diff --git a/src/Microsoft.Azure.WebJobs.Host/Triggers/TriggeredFunctionInstanceFactory.cs b/src/Microsoft.Azure.WebJobs.Host/Triggers/TriggeredFunctionInstanceFactory.cs index 06015f4836..87a707097c 100644 --- a/src/Microsoft.Azure.WebJobs.Host/Triggers/TriggeredFunctionInstanceFactory.cs +++ b/src/Microsoft.Azure.WebJobs.Host/Triggers/TriggeredFunctionInstanceFactory.cs @@ -6,17 +6,18 @@ using System.Threading.Tasks; using Microsoft.Azure.WebJobs.Host.Executors; using Microsoft.Azure.WebJobs.Host.Protocols; +using Microsoft.Extensions.DependencyInjection; namespace Microsoft.Azure.WebJobs.Host.Triggers { internal class TriggeredFunctionInstanceFactory : ITriggeredFunctionInstanceFactory { private readonly ITriggeredFunctionBinding _binding; - private readonly IFunctionInvoker _invoker; + private readonly IFunctionInvokerEx _invoker; private readonly FunctionDescriptor _descriptor; - + public TriggeredFunctionInstanceFactory(ITriggeredFunctionBinding binding, - IFunctionInvoker invoker, FunctionDescriptor descriptor) + IFunctionInvokerEx invoker, FunctionDescriptor descriptor) { _binding = binding; _invoker = invoker; @@ -44,7 +45,7 @@ public IFunctionInstance Create(FunctionInstanceFactoryContext context) return new FunctionInstance(context.Id, context.TriggerDetails, context.ParentId, context.ExecutionReason, bindingSource, invoker, _descriptor); } - private IFunctionInvoker CreateInvoker(FunctionInstanceFactoryContext context) + private IFunctionInvokerEx CreateInvoker(FunctionInstanceFactoryContext context) { if (context.InvokeHandler != null) { @@ -56,12 +57,12 @@ private IFunctionInvoker CreateInvoker(FunctionInstanceFactoryContext context) } } - private class InvokeWrapper : IFunctionInvoker + private class InvokeWrapper : IFunctionInvokerEx { - private readonly IFunctionInvoker _inner; + private readonly IFunctionInvokerEx _inner; private readonly Func>, Task> _handler; - public InvokeWrapper(IFunctionInvoker inner, Func>, Task> handler) + public InvokeWrapper(IFunctionInvokerEx inner, Func>, Task> handler) { _inner = inner; _handler = handler; @@ -74,9 +75,14 @@ public Task InvokeAsync(object instance, object[] arguments) return _handler(inner); } + public object CreateInstance(IFunctionInstanceEx functionInstance) + { + return _inner.CreateInstance(functionInstance); + } + public object CreateInstance() { - return _inner.CreateInstance(); + throw new NotSupportedException($"{nameof(CreateInstance)} is not supported. Please use the overload that accepts an instance of an {nameof(IFunctionInstance)}"); } } } diff --git a/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/DependencyInjection/DependencyInjectionTests.cs b/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/DependencyInjection/DependencyInjectionTests.cs new file mode 100644 index 0000000000..ad9b37124e --- /dev/null +++ b/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/DependencyInjection/DependencyInjectionTests.cs @@ -0,0 +1,129 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.Azure.WebJobs.Host.TestCommon; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using System; +using System.Threading.Tasks; +using Xunit; +using static Microsoft.Azure.WebJobs.Host.UnitTests.Singleton.SingletonEnd2EndTests; + +namespace Microsoft.Azure.WebJobs.Host.FunctionalTests.DependencyInjection +{ + public class DependencyInjectionTests + { + [Fact] + public async Task AssertScopedServicesAreEqual() + { + var executionState = new ExecutionState(); + + IHost host = new HostBuilder() + .ConfigureDefaultTestHost() + .ConfigureServices(services => + { + services.AddSingleton(); + services.AddTransient(); + services.AddTransient(); + services.AddScoped(); + services.AddSingleton(executionState); + }) + .Build(); + + var jobHost = host.GetJobHost(); + await jobHost.CallAsync(nameof(Program.Func1), null); + + // Make sure the service was disposed + Assert.True(executionState.ExecutionService.IsDisposed); + } + + public class Program + { + private readonly IServiceA _serviceA; + private readonly IServiceB _serviceB; + private readonly ExecutionState _state; + + public Program(IServiceA serviceA, IServiceB serviceB, ICommonService commonService, ExecutionState state) + { + _serviceA = serviceA; + _serviceB = serviceB; + _state = state; + _state.ExecutionService = serviceA.CommonService; + } + + [NoAutomaticTrigger] + [Singleton] + public void Func1() + { + if (!ReferenceEquals(_serviceA.CommonService, _serviceB.CommonService) || !ReferenceEquals(_state.ExecutionService, _serviceA.CommonService)) + { + throw new Exception("Common services are not the same instance."); + } + } + } + + public interface ICommonService : IDisposable + { + bool IsDisposed { get; } + } + + public interface IServiceA + { + void Run(); + + ICommonService CommonService { get; } + } + + public interface IServiceB + { + void Run(); + + ICommonService CommonService { get; } + } + + public class ServiceA : IServiceA + { + public ServiceA(ICommonService commonService) + { + CommonService = commonService; + } + + public ICommonService CommonService { get; } + + public void Run() + { + throw new NotImplementedException(); + } + } + + public class ServiceB : IServiceB + { + public ServiceB(ICommonService commonService) + { + CommonService = commonService; + } + + public ICommonService CommonService { get; } + + public void Run() + { + throw new NotImplementedException(); + } + } + + public class CommonService : ICommonService + { + public bool IsDisposed { get; private set; } + + public void Dispose() + { + IsDisposed = true; + } + } + + public class ExecutionState + { + public ICommonService ExecutionService { get; set; } + } + } +} diff --git a/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/Indexers/FunctionIndexerFactory.cs b/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/Indexers/FunctionIndexerFactory.cs index 2a9eeb02d8..4476c1e8c3 100644 --- a/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/Indexers/FunctionIndexerFactory.cs +++ b/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/Indexers/FunctionIndexerFactory.cs @@ -62,8 +62,7 @@ public static FunctionIndexer Create(CloudStorageAccount account = null, INameRe IFunctionExecutor executor = host.Services.GetService(); // TODO: This should be using DI internally and not be so complicated to construct - return new FunctionIndexer(triggerBindingProvider, bindingProvider, activator, executor, - extensionRegistry, singletonManager, loggerFactory); + return new FunctionIndexer(triggerBindingProvider, bindingProvider, activator, executor, singletonManager, loggerFactory); } } } diff --git a/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/Indexers/FunctionIndexerIntegrationErrorTests.cs b/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/Indexers/FunctionIndexerIntegrationErrorTests.cs index 8038a0a519..d003b02c07 100644 --- a/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/Indexers/FunctionIndexerIntegrationErrorTests.cs +++ b/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/Indexers/FunctionIndexerIntegrationErrorTests.cs @@ -24,11 +24,6 @@ public void TestFails() { foreach (var method in this.GetType().GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)) { - Mock extensionsMock = new Mock(MockBehavior.Strict); - extensionsMock.Setup(p => p.GetExtensions(typeof(IExtensionConfigProvider))).Returns(Enumerable.Empty()); - extensionsMock.Setup(p => p.GetExtensions(typeof(ITriggerBindingProvider))).Returns(Enumerable.Empty()); - extensionsMock.Setup(p => p.GetExtensions(typeof(IBindingProvider))).Returns(Enumerable.Empty()); - extensionsMock.Setup(p => p.GetExtensions(typeof(IArgumentBindingProvider<>))).Returns(Enumerable.Empty()); Mock executorMock = new Mock(MockBehavior.Strict); IFunctionIndexCollector stubIndex = new Mock().Object; @@ -38,7 +33,6 @@ public void TestFails() new Mock(MockBehavior.Strict).Object, new Mock(MockBehavior.Strict).Object, executorMock.Object, - extensionsMock.Object, new SingletonManager(), null); diff --git a/test/Microsoft.Azure.WebJobs.Host.UnitTests/Executors/ActivatorInstanceFactoryTests.cs b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Executors/ActivatorInstanceFactoryTests.cs index 8c4bcce1ea..3c8ae6c9be 100644 --- a/test/Microsoft.Azure.WebJobs.Host.UnitTests/Executors/ActivatorInstanceFactoryTests.cs +++ b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Executors/ActivatorInstanceFactoryTests.cs @@ -21,10 +21,12 @@ public void Create_DelegatesToJobActivator() .Verifiable(); IJobActivator activator = activatorMock.Object; - IFactory product = CreateProductUnderTest(activator); + IJobInstanceFactory product = CreateProductUnderTest(activator); + + var functionInstanceMock = new Mock(); // Act - object instance = product.Create(); + object instance = product.Create(functionInstanceMock.Object); // Assert Assert.Same(expectedInstance, instance); diff --git a/test/Microsoft.Azure.WebJobs.Host.UnitTests/Executors/FunctionInvokerTests.cs b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Executors/FunctionInvokerTests.cs index ae1057cadc..67d291df92 100644 --- a/test/Microsoft.Azure.WebJobs.Host.UnitTests/Executors/FunctionInvokerTests.cs +++ b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Executors/FunctionInvokerTests.cs @@ -21,11 +21,11 @@ public void InvokeAsync_DelegatesToInstanceFactoryAndMethodInvoker() object expectedInstance = new object(); object[] expectedArguments = new object[0]; - Mock> instanceFactoryMock = new Mock>(MockBehavior.Strict); - instanceFactoryMock.Setup(f => f.Create()) + Mock> instanceFactoryMock = new Mock>(MockBehavior.Strict); + instanceFactoryMock.Setup(f => f.Create(It.IsAny())) .Returns(expectedInstance) .Verifiable(); - IFactory instanceFactory = instanceFactoryMock.Object; + IJobInstanceFactory instanceFactory = instanceFactoryMock.Object; Mock> methodInvokerMock = new Mock>(MockBehavior.Strict); methodInvokerMock.Setup(i => i.InvokeAsync(expectedInstance, expectedArguments)) @@ -87,7 +87,7 @@ public void InvokeAsync_IfInstanceIsDisposable_DoesNotDisposeWhileTaskIsRunning2 Assert.True(prog._disposed, "User job should be disposed."); } - private static FunctionInvoker CreateProductUnderTest(IFactory instanceFactory, + private static FunctionInvoker CreateProductUnderTest(IJobInstanceFactory instanceFactory, IMethodInvoker methodInvoker) { return CreateProductUnderTest(new string[0], instanceFactory, methodInvoker); @@ -95,7 +95,7 @@ private static FunctionInvoker CreateProductUnderTest(IFactory CreateProductUnderTest( IReadOnlyList parameterNames, - IFactory instanceFactory, + IJobInstanceFactory instanceFactory, IMethodInvoker methodInvoker) { return new FunctionInvoker(parameterNames, instanceFactory, methodInvoker); diff --git a/test/Microsoft.Azure.WebJobs.Host.UnitTests/Executors/NullInstanceFactoryTests.cs b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Executors/NullInstanceFactoryTests.cs index 493248c397..5fbbee60d2 100644 --- a/test/Microsoft.Azure.WebJobs.Host.UnitTests/Executors/NullInstanceFactoryTests.cs +++ b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Executors/NullInstanceFactoryTests.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using Microsoft.Azure.WebJobs.Host.Executors; +using Moq; using Xunit; namespace Microsoft.Azure.WebJobs.Host.UnitTests.Executors @@ -12,10 +13,11 @@ public class NullInstanceFactoryTests public void Create_ReturnsNull() { // Arrange - IFactory product = CreateProductUnderTest(); + IJobInstanceFactory product = CreateProductUnderTest(); + var functionInstanceMock = new Mock(); // Act - object instance = product.Create(); + object instance = product.Create(functionInstanceMock.Object); // Assert Assert.Null(instance); diff --git a/test/Microsoft.Azure.WebJobs.Host.UnitTests/Executors/TriggeredFunctionExecutorTests.cs b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Executors/TriggeredFunctionExecutorTests.cs index 0840593cad..b2d0e4c5e1 100644 --- a/test/Microsoft.Azure.WebJobs.Host.UnitTests/Executors/TriggeredFunctionExecutorTests.cs +++ b/test/Microsoft.Azure.WebJobs.Host.UnitTests/Executors/TriggeredFunctionExecutorTests.cs @@ -26,7 +26,7 @@ public async Task TryExecuteAsync_WithInvokeHandler_InvokesHandler() }); bool innerInvokerInvoked = false; - Mock mockInvoker = new Mock(); + Mock mockInvoker = new Mock(); mockInvoker.Setup(m => m.InvokeAsync(null, null)).Returns(() => { innerInvokerInvoked = true;