diff --git a/src/WebJobs.Script/Description/FunctionDescriptorProvider.cs b/src/WebJobs.Script/Description/FunctionDescriptorProvider.cs index a3905803ce..7688c3143d 100644 --- a/src/WebJobs.Script/Description/FunctionDescriptorProvider.cs +++ b/src/WebJobs.Script/Description/FunctionDescriptorProvider.cs @@ -181,7 +181,7 @@ private bool TryParseTriggerParameter(BindingMetadata metadata, out ParameterDes protected internal virtual void ValidateFunction(FunctionMetadata functionMetadata) { - HashSet names = new HashSet(StringComparer.OrdinalIgnoreCase); + HashSet names = new(StringComparer.OrdinalIgnoreCase); foreach (var binding in functionMetadata.Bindings) { ValidateBinding(binding); @@ -189,12 +189,10 @@ protected internal virtual void ValidateFunction(FunctionMetadata functionMetada // Ensure no duplicate binding names if (names.Contains(binding.Name)) { - throw new InvalidOperationException(string.Format("Multiple bindings with name '{0}' discovered. Binding names must be unique.", binding.Name)); - } - else - { - names.Add(binding.Name); + throw new InvalidOperationException($"{nameof(FunctionDescriptorProvider)}: Multiple bindings with name '{binding.Name}' discovered. Binding names must be unique."); } + + names.Add(binding.Name); } // Verify there aren't multiple triggers defined diff --git a/src/WebJobs.Script/Host/WorkerFunctionMetadataProvider.cs b/src/WebJobs.Script/Host/WorkerFunctionMetadataProvider.cs index f57377bdcf..6841f2b092 100644 --- a/src/WebJobs.Script/Host/WorkerFunctionMetadataProvider.cs +++ b/src/WebJobs.Script/Host/WorkerFunctionMetadataProvider.cs @@ -257,13 +257,11 @@ internal static FunctionMetadata ValidateBindings(IEnumerable rawBinding // Ensure no duplicate binding names exist if (bindingNames.Contains(functionBinding.Name)) { - throw new InvalidOperationException(string.Format("Multiple bindings with name '{0}' discovered. Binding names must be unique.", functionBinding.Name)); - } - else - { - bindingNames.Add(functionBinding.Name); + throw new InvalidOperationException($"{nameof(WorkerFunctionDescriptorProvider)}: Multiple bindings with name '{functionBinding.Name}' discovered. Binding names must be unique."); } + bindingNames.Add(functionBinding.Name); + // add binding to function.Bindings once validation is complete function.Bindings.Add(functionBinding); } diff --git a/src/WebJobs.Script/ScriptHostBuilderExtensions.cs b/src/WebJobs.Script/ScriptHostBuilderExtensions.cs index 814811a8d6..ed6fac3237 100644 --- a/src/WebJobs.Script/ScriptHostBuilderExtensions.cs +++ b/src/WebJobs.Script/ScriptHostBuilderExtensions.cs @@ -311,10 +311,13 @@ public static IHostBuilder AddScriptHostCore(this IHostBuilder builder, ScriptAp // Configuration services.AddSingleton>(new OptionsWrapper(applicationHostOptions)); services.AddSingleton>(new ScriptApplicationHostOptionsMonitor(applicationHostOptions)); + + // Forward th host LanguageWorkerOptions to the Job Host. + var languageWorkerOptions = applicationHostOptions.RootServiceProvider.GetService>(); + services.AddSingleton(languageWorkerOptions); + services.AddSingleton>((s) => new OptionsWrapper(languageWorkerOptions.CurrentValue)); services.ConfigureOptions(); services.ConfigureOptions(); - // LanguageWorkerOptionsSetup should be registered in WebHostServiceCollection as well to enable starting worker processing in placeholder mode. - services.ConfigureOptions(); services.AddOptions(); services.ConfigureOptions(); services.ConfigureOptions(); @@ -329,7 +332,11 @@ public static IHostBuilder AddScriptHostCore(this IHostBuilder builder, ScriptAp services.AddSingleton(); - if (!applicationHostOptions.HasParentScope) + if (applicationHostOptions.HasParentScope) + { + services.ConfigureOptions(); + } + else { AddCommonServices(services); } diff --git a/src/WebJobs.Script/Workers/FunctionInvocationDispatcherFactory.cs b/src/WebJobs.Script/Workers/FunctionInvocationDispatcherFactory.cs index c228915e97..0b5dc643a4 100644 --- a/src/WebJobs.Script/Workers/FunctionInvocationDispatcherFactory.cs +++ b/src/WebJobs.Script/Workers/FunctionInvocationDispatcherFactory.cs @@ -27,7 +27,7 @@ public FunctionInvocationDispatcherFactory(IOptions script IHttpWorkerChannelFactory httpWorkerChannelFactory, IRpcWorkerChannelFactory rpcWorkerChannelFactory, IOptions httpWorkerOptions, - IOptionsMonitor rpcWorkerOptions, + IOptionsMonitor workerOptions, IEnvironment environment, IWebHostRpcWorkerChannelManager webHostLanguageWorkerChannelManager, IJobHostRpcWorkerChannelManager jobHostLanguageWorkerChannelManager, @@ -54,7 +54,7 @@ public FunctionInvocationDispatcherFactory(IOptions script eventManager, loggerFactory, rpcWorkerChannelFactory, - rpcWorkerOptions, + workerOptions, webHostLanguageWorkerChannelManager, jobHostLanguageWorkerChannelManager, managedDependencyOptions, diff --git a/src/WebJobs.Script/Workers/Rpc/Configuration/LanguageWorkerOptionsSetup.cs b/src/WebJobs.Script/Workers/Rpc/Configuration/LanguageWorkerOptionsSetup.cs index 4cfa5a14e9..64ecd27e88 100644 --- a/src/WebJobs.Script/Workers/Rpc/Configuration/LanguageWorkerOptionsSetup.cs +++ b/src/WebJobs.Script/Workers/Rpc/Configuration/LanguageWorkerOptionsSetup.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using Microsoft.Azure.WebJobs.Script.Diagnostics; using Microsoft.Azure.WebJobs.Script.Workers.Profiles; using Microsoft.Extensions.Configuration; @@ -38,7 +39,7 @@ public LanguageWorkerOptionsSetup(IConfiguration configuration, _logger = loggerFactory.CreateLogger("Host.LanguageWorkerConfig"); } - public void Configure(LanguageWorkerOptions options) + public virtual void Configure(LanguageWorkerOptions options) { string workerRuntime = _environment.GetEnvironmentVariable(RpcWorkerConstants.FunctionWorkerRuntimeSettingName); @@ -56,4 +57,24 @@ public void Configure(LanguageWorkerOptions options) options.WorkerConfigs = configFactory.GetConfigs(); } } + + internal class JobHostLanguageWorkerOptionsSetup : IPostConfigureOptions + { + private readonly ILoggerFactory _loggerFactory; + + public JobHostLanguageWorkerOptionsSetup(ILoggerFactory loggerFactory) + { + _loggerFactory = loggerFactory; + } + + public void PostConfigure(string name, LanguageWorkerOptions options) + { + var message = $"Call to configure {nameof(LanguageWorkerOptions)} from the JobHost scope. " + + $"If using {nameof(IOptions)}, please use {nameof(IOptionsMonitor)} instead."; + Debug.Fail(message); + + var logger = _loggerFactory.CreateLogger("Host.LanguageWorkerConfig"); + logger.LogInformation(message); + } + } } diff --git a/src/WebJobs.Script/Workers/Rpc/FunctionRegistration/RpcFunctionInvocationDispatcher.cs b/src/WebJobs.Script/Workers/Rpc/FunctionRegistration/RpcFunctionInvocationDispatcher.cs index ab48b8f8cc..8b7393bc88 100644 --- a/src/WebJobs.Script/Workers/Rpc/FunctionRegistration/RpcFunctionInvocationDispatcher.cs +++ b/src/WebJobs.Script/Workers/Rpc/FunctionRegistration/RpcFunctionInvocationDispatcher.cs @@ -67,7 +67,7 @@ public RpcFunctionInvocationDispatcher(IOptions scriptHost IScriptEventManager eventManager, ILoggerFactory loggerFactory, IRpcWorkerChannelFactory rpcWorkerChannelFactory, - IOptionsMonitor languageWorkerOptions, + IOptionsMonitor workerOptions, IWebHostRpcWorkerChannelManager webHostLanguageWorkerChannelManager, IJobHostRpcWorkerChannelManager jobHostLanguageWorkerChannelManager, IOptions managedDependencyOptions, @@ -83,7 +83,11 @@ public RpcFunctionInvocationDispatcher(IOptions scriptHost _webHostLanguageWorkerChannelManager = webHostLanguageWorkerChannelManager; _jobHostLanguageWorkerChannelManager = jobHostLanguageWorkerChannelManager; _eventManager = eventManager; - _workerConfigs = languageWorkerOptions?.CurrentValue?.WorkerConfigs ?? throw new ArgumentNullException(nameof(languageWorkerOptions)); + + // The state of worker configuration and the LanguageWorkerOptions will match the lifetime of the JobHost and the + // RpcFunctionInvocationDispatcher. So, we can safely cache the workerConfigs and workerRuntime here. + // Using IOptionsMonitor here to get the same cached version used by other copmponents and avoid a new instance initialization. + _workerConfigs = workerOptions?.CurrentValue?.WorkerConfigs ?? throw new ArgumentNullException(nameof(workerOptions)); _managedDependencyOptions = managedDependencyOptions ?? throw new ArgumentNullException(nameof(managedDependencyOptions)); _logger = loggerFactory.CreateLogger(); _rpcWorkerChannelFactory = rpcWorkerChannelFactory; diff --git a/test/WebJobs.Script.Tests.Shared/TestHostBuilderExtensions.cs b/test/WebJobs.Script.Tests.Shared/TestHostBuilderExtensions.cs index ab35fb74b7..9572d1962a 100644 --- a/test/WebJobs.Script.Tests.Shared/TestHostBuilderExtensions.cs +++ b/test/WebJobs.Script.Tests.Shared/TestHostBuilderExtensions.cs @@ -69,6 +69,7 @@ public static IHostBuilder ConfigureDefaultTestWebScriptHost(this IHostBuilder b AddMockedSingleton(services); AddMockedSingleton(services); AddMockedSingleton(services); + services.ConfigureOptions(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton();