Skip to content

Commit 2dd8617

Browse files
authored
Address LanguageWorkerOptions null in FunctionMetadataManager (#10550)
* Address language options null by consuming from parent services * Update unit tests to calculate LanguageWorkerOptions * Address merge issue * Update languageWorkerOptions ref on host change * Revert to single monitoring of LanguageWorkerOptions * Remove LanguageWorkerOptions from ScriptStartupTypeLocator * Mark readonly
1 parent 49e1420 commit 2dd8617

File tree

10 files changed

+88
-83
lines changed

10 files changed

+88
-83
lines changed

src/WebJobs.Script/DependencyInjection/ScriptStartupTypeLocator.cs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,20 +38,18 @@ public class ScriptStartupTypeLocator : IWebJobsStartupTypeLocator
3838
private readonly IFunctionMetadataManager _functionMetadataManager;
3939
private readonly IMetricsLogger _metricsLogger;
4040
private readonly Lazy<IEnumerable<Type>> _startupTypes;
41-
private readonly IOptionsMonitor<LanguageWorkerOptions> _languageWorkerOptions;
4241
private readonly IOptions<ExtensionRequirementOptions> _extensionRequirementOptions;
4342
private static string[] _builtinExtensionAssemblies = GetBuiltinExtensionAssemblies();
4443

4544
public ScriptStartupTypeLocator(string rootScriptPath, ILogger<ScriptStartupTypeLocator> logger, IExtensionBundleManager extensionBundleManager,
46-
IFunctionMetadataManager functionMetadataManager, IMetricsLogger metricsLogger, IOptionsMonitor<LanguageWorkerOptions> languageWorkerOptions, IOptions<ExtensionRequirementOptions> extensionRequirementOptions)
45+
IFunctionMetadataManager functionMetadataManager, IMetricsLogger metricsLogger, IOptions<ExtensionRequirementOptions> extensionRequirementOptions)
4746
{
4847
_rootScriptPath = rootScriptPath ?? throw new ArgumentNullException(nameof(rootScriptPath));
4948
_extensionBundleManager = extensionBundleManager ?? throw new ArgumentNullException(nameof(extensionBundleManager));
5049
_logger = logger;
5150
_functionMetadataManager = functionMetadataManager;
5251
_metricsLogger = metricsLogger;
5352
_startupTypes = new Lazy<IEnumerable<Type>>(() => GetExtensionsStartupTypesAsync().ConfigureAwait(false).GetAwaiter().GetResult());
54-
_languageWorkerOptions = languageWorkerOptions;
5553
_extensionRequirementOptions = extensionRequirementOptions;
5654
}
5755

@@ -84,18 +82,17 @@ public async Task<IEnumerable<Type>> GetExtensionsStartupTypesAsync()
8482
bool isPrecompiledFunctionApp = false;
8583

8684
// dotnet app precompiled -> Do not use bundles
87-
var workerConfigs = _languageWorkerOptions.CurrentValue.WorkerConfigs;
8885
ExtensionRequirementsInfo extensionRequirements = GetExtensionRequirementsInfo();
8986
ImmutableArray<FunctionMetadata> functionMetadataCollection = ImmutableArray<FunctionMetadata>.Empty;
9087
if (bundleConfigured)
9188
{
9289
ExtensionBundleDetails bundleDetails = await _extensionBundleManager.GetExtensionBundleDetails();
9390
ValidateBundleRequirements(bundleDetails, extensionRequirements);
9491

95-
functionMetadataCollection = _functionMetadataManager.GetFunctionMetadata(forceRefresh: true, includeCustomProviders: false, workerConfigs: workerConfigs);
92+
functionMetadataCollection = _functionMetadataManager.GetFunctionMetadata(forceRefresh: true, includeCustomProviders: false);
9693
bindingsSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
9794

98-
// Generate a Hashset of all the binding types used in the function app
95+
// Generate a HashSet of all the binding types used in the function app
9996
foreach (var functionMetadata in functionMetadataCollection)
10097
{
10198
foreach (var binding in functionMetadata.Bindings)
@@ -113,7 +110,7 @@ public async Task<IEnumerable<Type>> GetExtensionsStartupTypesAsync()
113110
if (SystemEnvironment.Instance.IsPlaceholderModeEnabled())
114111
{
115112
// Do not move this.
116-
// Calling this log statement in the placeholder mode to avoid jitting during specializtion
113+
// Calling this log statement in the placeholder mode to avoid jitting during specialization
117114
_logger.ScriptStartNotLoadingExtensionBundle("WARMUP_LOG_ONLY", bundleConfigured, isPrecompiledFunctionApp, isLegacyExtensionBundle, isDotnetIsolatedApp, isLogicApp);
118115
}
119116

src/WebJobs.Script/Host/FunctionMetadataManager.cs

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,40 +7,44 @@
77
using System.Collections.Immutable;
88
using System.IO;
99
using System.Linq;
10-
using System.Threading;
1110
using System.Threading.Tasks;
1211
using Microsoft.Azure.WebJobs.Logging;
13-
using Microsoft.Azure.WebJobs.Script.Configuration;
1412
using Microsoft.Azure.WebJobs.Script.Description;
1513
using Microsoft.Azure.WebJobs.Script.Diagnostics.Extensions;
16-
using Microsoft.Azure.WebJobs.Script.Workers;
1714
using Microsoft.Azure.WebJobs.Script.Workers.Http;
1815
using Microsoft.Azure.WebJobs.Script.Workers.Rpc;
19-
using Microsoft.Extensions.Configuration;
2016
using Microsoft.Extensions.DependencyInjection;
2117
using Microsoft.Extensions.Logging;
2218
using Microsoft.Extensions.Options;
2319

2420
namespace Microsoft.Azure.WebJobs.Script
2521
{
26-
public class FunctionMetadataManager : IFunctionMetadataManager
22+
public sealed class FunctionMetadataManager : IFunctionMetadataManager, IDisposable
2723
{
2824
private const string FunctionConfigurationErrorMessage = "Unable to determine the primary function script. Make sure at least one script file is present. Try renaming your entry point script to 'run' or alternatively you can specify the name of the entry point script explicitly by adding a 'scriptFile' property to your function metadata.";
2925
private const string MetadataProviderName = "Custom";
3026
private readonly IServiceProvider _serviceProvider;
31-
private IFunctionMetadataProvider _functionMetadataProvider;
27+
private readonly IFunctionMetadataProvider _functionMetadataProvider;
28+
private readonly IEnvironment _environment;
29+
30+
private readonly IOptionsMonitor<LanguageWorkerOptions> _languageOptions;
31+
private IDisposable _onChangeSubscription;
32+
private IOptions<ScriptJobHostOptions> _scriptOptions;
33+
private ILogger _logger;
3234
private bool _isHttpWorker;
33-
private IEnvironment _environment;
3435
private bool _servicesReset = false;
35-
private ILogger _logger;
36-
private IOptions<ScriptJobHostOptions> _scriptOptions;
3736
private ImmutableArray<FunctionMetadata> _functionMetadataArray;
3837
private Dictionary<string, ICollection<string>> _functionErrors = new Dictionary<string, ICollection<string>>();
3938
private ConcurrentDictionary<string, FunctionMetadata> _functionMetadataMap = new ConcurrentDictionary<string, FunctionMetadata>(StringComparer.OrdinalIgnoreCase);
4039

41-
public FunctionMetadataManager(IOptions<ScriptJobHostOptions> scriptOptions, IFunctionMetadataProvider functionMetadataProvider,
42-
IOptions<HttpWorkerOptions> httpWorkerOptions, IScriptHostManager scriptHostManager, ILoggerFactory loggerFactory,
43-
IEnvironment environment)
40+
public FunctionMetadataManager(
41+
IOptions<ScriptJobHostOptions> scriptOptions,
42+
IFunctionMetadataProvider functionMetadataProvider,
43+
IOptions<HttpWorkerOptions> httpWorkerOptions,
44+
IScriptHostManager scriptHostManager,
45+
ILoggerFactory loggerFactory,
46+
IEnvironment environment,
47+
IOptionsMonitor<LanguageWorkerOptions> languageOptions)
4448
{
4549
_scriptOptions = scriptOptions;
4650
_serviceProvider = scriptHostManager as IServiceProvider;
@@ -49,6 +53,9 @@ public FunctionMetadataManager(IOptions<ScriptJobHostOptions> scriptOptions, IFu
4953
_isHttpWorker = httpWorkerOptions?.Value?.Description != null;
5054
_environment = environment;
5155

56+
_languageOptions = languageOptions;
57+
_onChangeSubscription = languageOptions.OnChange(_ => _servicesReset = true);
58+
5259
// Every time script host is re-initializing, we also need to re-initialize
5360
// services that change with the scope of the script host.
5461
scriptHostManager.ActiveHostChanged += (s, e) =>
@@ -60,8 +67,10 @@ public FunctionMetadataManager(IOptions<ScriptJobHostOptions> scriptOptions, IFu
6067
};
6168
}
6269

70+
/// <inheritdoc />
6371
public ImmutableDictionary<string, ImmutableArray<string>> Errors { get; private set; }
6472

73+
/// <inheritdoc />
6574
public bool TryGetFunctionMetadata(string functionName, out FunctionMetadata functionMetadata, bool forceRefresh)
6675
{
6776
if (forceRefresh)
@@ -85,18 +94,21 @@ public bool TryGetFunctionMetadata(string functionName, out FunctionMetadata fun
8594
/// <param name="applyAllowList">Apply functions allow list filter.</param>
8695
/// <param name="includeCustomProviders">Include any metadata provided by IFunctionProvider when loading the metadata.</param>
8796
/// <returns> An Immutable array of FunctionMetadata.</returns>
88-
public ImmutableArray<FunctionMetadata> GetFunctionMetadata(bool forceRefresh, bool applyAllowList = true, bool includeCustomProviders = true, IList<RpcWorkerConfig> workerConfigs = null)
97+
public ImmutableArray<FunctionMetadata> GetFunctionMetadata(bool forceRefresh, bool applyAllowList = true, bool includeCustomProviders = true)
8998
{
9099
if (forceRefresh || _servicesReset || _functionMetadataArray.IsDefaultOrEmpty)
91100
{
92-
_functionMetadataArray = LoadFunctionMetadata(forceRefresh, includeCustomProviders, workerConfigs: workerConfigs);
101+
_functionMetadataArray = LoadFunctionMetadata(forceRefresh, includeCustomProviders);
93102
_logger.FunctionMetadataManagerFunctionsLoaded(ApplyAllowList(_functionMetadataArray).Count());
94103
_servicesReset = false;
95104
}
96105

97106
return applyAllowList ? ApplyAllowList(_functionMetadataArray) : _functionMetadataArray;
98107
}
99108

109+
/// <inheritdoc />
110+
public void Dispose() => _onChangeSubscription.Dispose();
111+
100112
private ImmutableArray<FunctionMetadata> ApplyAllowList(ImmutableArray<FunctionMetadata> metadataList)
101113
{
102114
var allowList = _scriptOptions.Value?.Functions;
@@ -119,25 +131,16 @@ private void InitializeServices()
119131
// Resetting the logger switches the logger scope to Script Host level,
120132
// also making the logs available to Application Insights
121133
_logger = _serviceProvider?.GetService<ILoggerFactory>().CreateLogger(LogCategories.Startup);
122-
_servicesReset = true;
123-
}
124134

125-
/// <summary>
126-
/// This is the worker configuration created in the jobhost scope during placeholder initialization
127-
/// This is used as a fallback incase the config is not passed down from previous method call.
128-
/// </summary>
129-
private IList<RpcWorkerConfig> GetFallbackWorkerConfig()
130-
{
131-
return _serviceProvider.GetService<IOptionsMonitor<LanguageWorkerOptions>>().CurrentValue.WorkerConfigs;
135+
_servicesReset = true;
132136
}
133137

134138
/// <summary>
135139
/// Read all functions and populate function metadata.
136140
/// </summary>
137-
internal ImmutableArray<FunctionMetadata> LoadFunctionMetadata(bool forceRefresh = false, bool includeCustomProviders = true, IFunctionInvocationDispatcher dispatcher = null, IList<RpcWorkerConfig> workerConfigs = null)
141+
internal ImmutableArray<FunctionMetadata> LoadFunctionMetadata(bool forceRefresh = false, bool includeCustomProviders = true)
138142
{
139-
workerConfigs ??= GetFallbackWorkerConfig();
140-
143+
var workerConfigs = _languageOptions.CurrentValue.WorkerConfigs;
141144
_functionMetadataMap.Clear();
142145

143146
ICollection<string> functionsAllowList = _scriptOptions?.Value?.Functions;

src/WebJobs.Script/Host/IFunctionMetadataManager.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

4-
using System.Collections.Generic;
54
using System.Collections.Immutable;
65
using Microsoft.Azure.WebJobs.Script.Description;
7-
using Microsoft.Azure.WebJobs.Script.Workers;
8-
using Microsoft.Azure.WebJobs.Script.Workers.Rpc;
96

107
namespace Microsoft.Azure.WebJobs.Script
118
{
129
public interface IFunctionMetadataManager
1310
{
1411
ImmutableDictionary<string, ImmutableArray<string>> Errors { get; }
1512

16-
ImmutableArray<FunctionMetadata> GetFunctionMetadata(bool forceRefresh = false, bool applyAllowlist = true, bool includeCustomProviders = true, IList<RpcWorkerConfig> workerConfigs = null);
13+
ImmutableArray<FunctionMetadata> GetFunctionMetadata(bool forceRefresh = false, bool applyAllowlist = true, bool includeCustomProviders = true);
1714

1815
bool TryGetFunctionMetadata(string functionName, out FunctionMetadata functionMetadata, bool forceRefresh = false);
1916
}

src/WebJobs.Script/Host/ScriptHost.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Collections.Immutable;
67
using System.Collections.ObjectModel;
78
using System.Diagnostics;
89
using System.Globalization;
@@ -373,13 +374,10 @@ private void LogHostFunctionErrors()
373374
/// <returns>A metadata collection of functions and proxies configured.</returns>
374375
private IEnumerable<FunctionMetadata> GetFunctionsMetadata()
375376
{
376-
IEnumerable<FunctionMetadata> functionMetadata;
377-
378-
functionMetadata = _functionMetadataManager.GetFunctionMetadata(forceRefresh: false, workerConfigs: _languageWorkerOptions.CurrentValue.WorkerConfigs);
379-
377+
ImmutableArray<FunctionMetadata> functionMetadata = _functionMetadataManager.GetFunctionMetadata(forceRefresh: false);
380378
foreach (var error in _functionMetadataManager.Errors)
381379
{
382-
FunctionErrors.Add(error.Key, error.Value.ToArray());
380+
FunctionErrors.Add(error.Key, error.Value);
383381
}
384382

385383
return functionMetadata;

src/WebJobs.Script/ScriptHostBuilderExtensions.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,9 +141,8 @@ public static IHostBuilder AddScriptHost(this IHostBuilder builder,
141141

142142
var bundleManager = new ExtensionBundleManager(extensionBundleOptions, SystemEnvironment.Instance, loggerFactory, configOption);
143143
var metadataServiceManager = applicationOptions.RootServiceProvider.GetService<IFunctionMetadataManager>();
144-
var languageWorkerOptions = applicationOptions.RootServiceProvider.GetService<IOptionsMonitor<LanguageWorkerOptions>>();
145144

146-
var locator = new ScriptStartupTypeLocator(applicationOptions.ScriptPath, loggerFactory.CreateLogger<ScriptStartupTypeLocator>(), bundleManager, metadataServiceManager, metricsLogger, languageWorkerOptions, extensionRequirementOptions);
145+
var locator = new ScriptStartupTypeLocator(applicationOptions.ScriptPath, loggerFactory.CreateLogger<ScriptStartupTypeLocator>(), bundleManager, metadataServiceManager, metricsLogger, extensionRequirementOptions);
147146

148147
// The locator (and thus the bundle manager) need to be created now in order to configure app configuration.
149148
// Store them so they do not need to be re-created later when configuring services.

test/WebJobs.Script.Tests.Integration/TestFunctionHost.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
using Microsoft.Extensions.Logging.Abstractions;
3636
using Microsoft.Extensions.Options;
3737
using Microsoft.WebJobs.Script.Tests;
38+
using Moq;
3839
using Newtonsoft.Json.Linq;
3940
using IApplicationLifetime = Microsoft.AspNetCore.Hosting.IApplicationLifetime;
4041

@@ -502,12 +503,16 @@ private FunctionMetadataManager GetMetadataManager(IOptionsMonitor<ScriptApplica
502503
WorkerConfigs = TestHelpers.GetTestWorkerConfigs()
503504
};
504505

506+
var mockOptions = new Mock<IOptionsMonitor<LanguageWorkerOptions>>();
507+
mockOptions.Setup(o => o.CurrentValue).Returns(workerOptions);
508+
mockOptions.Setup(o => o.OnChange(It.IsAny<Action<LanguageWorkerOptions, string>>())).Returns(Mock.Of<IDisposable>());
509+
505510
var managerServiceProvider = manager as IServiceProvider;
506511

507512
var metadataProvider = new HostFunctionMetadataProvider(optionsMonitor, NullLogger<HostFunctionMetadataProvider>.Instance, new TestMetricsLogger(), SystemEnvironment.Instance);
508513
var defaultProvider = new FunctionMetadataProvider(NullLogger<FunctionMetadataProvider>.Instance, null, metadataProvider, new OptionsWrapper<FunctionsHostingConfigOptions>(new FunctionsHostingConfigOptions()), SystemEnvironment.Instance);
509514
var metadataManager = new FunctionMetadataManager(managerServiceProvider.GetService<IOptions<ScriptJobHostOptions>>(), defaultProvider,
510-
managerServiceProvider.GetService<IOptions<HttpWorkerOptions>>(), manager, factory, environment);
515+
managerServiceProvider.GetService<IOptions<HttpWorkerOptions>>(), manager, factory, environment, mockOptions.Object);
511516

512517
return metadataManager;
513518
}

test/WebJobs.Script.Tests.Shared/TestFunctionMetadataManager.cs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,10 @@
33

44
using System;
55
using System.Collections.Generic;
6-
using System.IO;
76
using Microsoft.Azure.WebJobs.Script.Description;
8-
using Microsoft.Azure.WebJobs.Script.Diagnostics;
9-
using Microsoft.Azure.WebJobs.Script.Grpc;
107
using Microsoft.Azure.WebJobs.Script.Workers.Http;
118
using Microsoft.Azure.WebJobs.Script.Workers.Rpc;
129
using Microsoft.Extensions.Configuration;
13-
using Microsoft.Extensions.DependencyInjection;
1410
using Microsoft.Extensions.Logging;
1511
using Microsoft.Extensions.Options;
1612
using Moq;
@@ -64,7 +60,7 @@ public static FunctionMetadataManager GetFunctionMetadataManager(IOptions<Script
6460
var source = new TestChangeTokenSource<ScriptApplicationHostOptions>();
6561
var changeTokens = new[] { source };
6662
var optionsMonitor = new OptionsMonitor<ScriptApplicationHostOptions>(factory, changeTokens, factory);
67-
return new FunctionMetadataManager(jobHostOptions, functionMetadataProvider, httpOptions, managerMock.Object, loggerFactory, SystemEnvironment.Instance);
63+
return new FunctionMetadataManager(jobHostOptions, functionMetadataProvider, httpOptions, managerMock.Object, loggerFactory, SystemEnvironment.Instance, languageWorkerOptions);
6864
}
6965

7066
public static FunctionMetadataManager GetFunctionMetadataManagerWithDefaultHostConfig(IOptions<ScriptJobHostOptions> jobHostOptions,
@@ -98,7 +94,7 @@ public static FunctionMetadataManager GetFunctionMetadataManagerWithDefaultHostC
9894
var source = new TestChangeTokenSource<ScriptApplicationHostOptions>();
9995
var changeTokens = new[] { source };
10096
var optionsMonitor = new OptionsMonitor<ScriptApplicationHostOptions>(factory, changeTokens, factory);
101-
return new FunctionMetadataManager(jobHostOptions, functionMetadataProvider, httpOptions, managerMock.Object, loggerFactory, SystemEnvironment.Instance);
97+
return new FunctionMetadataManager(jobHostOptions, functionMetadataProvider, httpOptions, managerMock.Object, loggerFactory, SystemEnvironment.Instance, languageWorkerOptions);
10298
}
10399
}
104100
}

0 commit comments

Comments
 (0)