Skip to content

Refactor code to move the current logic to search for WorkerConfigs to a default worker configuration resolver #11229

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

Open
wants to merge 28 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
5cf51b1
Refactoring to add DefaultWorkerConfigurationResolver which resolves …
surgupta-msft Jul 30, 2025
e644d3c
Addressing Copilot PR feedback
surgupta-msft Jul 30, 2025
6dfc898
Updating release notes
surgupta-msft Jul 31, 2025
1c98a16
Removed obsolete code
surgupta-msft Aug 1, 2025
f20fa53
DI fix in tests
surgupta-msft Aug 1, 2025
b0dc43f
Merge with dev
surgupta-msft Aug 1, 2025
ad13ac2
Implemented IOptionsFormatter for WorkerConfigurationResolverOptions
surgupta-msft Aug 1, 2025
a566a9f
Addressing PR feedback
surgupta-msft Aug 1, 2025
76ccdc9
Build fix
surgupta-msft Aug 1, 2025
0de1312
Addressing PR feedback
surgupta-msft Aug 2, 2025
b036344
Remove _workerConfigResolverOptions from LanguageWorkerOptionsSetup
surgupta-msft Aug 3, 2025
190531d
Taking IOptionsMonitor as input in resolver
surgupta-msft Aug 4, 2025
a484c96
Added more tests in class WorkerConfigurationResolverOptionsSetupTests
surgupta-msft Aug 4, 2025
ad6a445
Rebase with main
surgupta-msft Aug 5, 2025
6317c0b
Update interface IWorkerConfigurationResolver
surgupta-msft Aug 5, 2025
0daca28
Addressing PR feedback
surgupta-msft Aug 6, 2025
c53f0fc
Adding FileSystem in TestHostBuilderExtensions
surgupta-msft Aug 6, 2025
95bf08f
Fix failing test
surgupta-msft Aug 6, 2025
70040bd
Adding only required configuration section in WorkerConfigurationReso…
surgupta-msft Aug 7, 2025
b80e363
Minor fix in test
surgupta-msft Aug 7, 2025
7324929
Fixing GetDefaultWorkersDirectory_Returns_Expected test
surgupta-msft Aug 8, 2025
ec1929d
Updating WorkerConfigurationResolverOptionsSetup
surgupta-msft Aug 11, 2025
c816553
Adding WorkerConfigPaths to WorkerConfigurationResolutionInfo
surgupta-msft Aug 12, 2025
fe19a38
Rebase with dev
surgupta-msft Aug 12, 2025
2726cc7
Addressing PR feedback
surgupta-msft Aug 12, 2025
92ec61b
Addressing PR feedback
surgupta-msft Aug 12, 2025
2da1eb5
Naming update
surgupta-msft Aug 13, 2025
0ee0871
Naming updates
surgupta-msft Aug 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion release_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
- Add JitTrace Files for v4.1042
- Updating OTel related nuget packages (#11216)
- Moving to version 1.5.7 of Microsoft.Azure.AppService.Middleware.Functions (https://github.com/Azure/azure-functions-host/pull/11231)
- Refactor code to move the logic to search for WorkerConfigs to a default worker configuration resolver (#11219)
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@
using Microsoft.Azure.WebJobs.Script.WebHost.Security.Authorization.Policies;
using Microsoft.Azure.WebJobs.Script.WebHost.Standby;
using Microsoft.Azure.WebJobs.Script.Workers.FunctionDataCache;
using Microsoft.Azure.WebJobs.Script.Workers.Profiles;
using Microsoft.Azure.WebJobs.Script.Workers.Rpc;
using Microsoft.Azure.WebJobs.Script.Workers.Rpc.Configuration;
using Microsoft.Azure.WebJobs.Script.Workers.SharedMemoryDataTransfer;
using Microsoft.Extensions.Azure;
using Microsoft.Extensions.Configuration;
Expand Down Expand Up @@ -228,9 +230,11 @@ public static void AddWebJobsScriptHost(this IServiceCollection services, IConfi
services.AddHostingConfigOptions(configuration);
services.ConfigureOptions<ExtensionRequirementOptionsSetup>();

// Refresh LanguageWorkerOptions when HostBuiltChangeTokenSource is triggered.
// Refresh WorkerConfigurationResolverOptions and LanguageWorkerOptions when HostBuiltChangeTokenSource is triggered.
services.ConfigureOptionsWithChangeTokenSource<WorkerConfigurationResolverOptions, WorkerConfigurationResolverOptionsSetup, HostBuiltChangeTokenSource<WorkerConfigurationResolverOptions>>();
services.ConfigureOptionsWithChangeTokenSource<LanguageWorkerOptions, LanguageWorkerOptionsSetup, HostBuiltChangeTokenSource<LanguageWorkerOptions>>();

services.AddSingleton<IWorkerConfigurationResolver, DefaultWorkerConfigurationResolver>();
services.TryAddSingleton<IDependencyValidator, DependencyValidator>();
services.TryAddSingleton<IJobHostMiddlewarePipeline>(s => DefaultMiddlewarePipeline.Empty);

Expand Down
11 changes: 10 additions & 1 deletion src/WebJobs.Script.WebHost/WebJobsScriptHostService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
using Microsoft.Azure.WebJobs.Script.WebHost.Diagnostics.Extensions;
using Microsoft.Azure.WebJobs.Script.Workers;
using Microsoft.Azure.WebJobs.Script.Workers.Rpc;
using Microsoft.Azure.WebJobs.Script.Workers.Rpc.Configuration;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
Expand Down Expand Up @@ -66,6 +67,7 @@ public class WebJobsScriptHostService : IHostedService, IScriptHostManager, ISer
private readonly bool _originalStandbyModeValue;
private readonly string _originalFunctionsWorkerRuntime;
private readonly string _originalFunctionsWorkerRuntimeVersion;
private readonly IOptionsChangeTokenSource<WorkerConfigurationResolverOptions> _workerConfigResolverOptionsChangeTokenSource;
private readonly IOptionsChangeTokenSource<LanguageWorkerOptions> _languageWorkerOptionsChangeTokenSource;

// we're only using this dictionary's keys so it acts as a "ConcurrentHashSet"
Expand All @@ -89,7 +91,8 @@ public WebJobsScriptHostService(IOptionsMonitor<ScriptApplicationHostOptions> ap
HostPerformanceManager hostPerformanceManager, IOptions<HostHealthMonitorOptions> healthMonitorOptions,
IMetricsLogger metricsLogger, IApplicationLifetime applicationLifetime, IConfiguration config, IScriptEventManager eventManager, IHostMetrics hostMetrics,
IOptions<FunctionsHostingConfigOptions> hostingConfigOptions,
IOptionsChangeTokenSource<LanguageWorkerOptions> languageWorkerOptionsChangeTokenSource)
IOptionsChangeTokenSource<LanguageWorkerOptions> languageWorkerOptionsChangeTokenSource,
IOptionsChangeTokenSource<WorkerConfigurationResolverOptions> workerConfigResolverOptionsChangeTokenSource)
{
ArgumentNullException.ThrowIfNull(loggerFactory);

Expand All @@ -100,6 +103,7 @@ public WebJobsScriptHostService(IOptionsMonitor<ScriptApplicationHostOptions> ap
RegisterApplicationLifetimeEvents();

_metricsLogger = metricsLogger;
_workerConfigResolverOptionsChangeTokenSource = workerConfigResolverOptionsChangeTokenSource ?? throw new ArgumentNullException(nameof(workerConfigResolverOptionsChangeTokenSource));
_languageWorkerOptionsChangeTokenSource = languageWorkerOptionsChangeTokenSource ?? throw new ArgumentNullException(nameof(languageWorkerOptionsChangeTokenSource));
_applicationHostOptions = applicationHostOptions ?? throw new ArgumentNullException(nameof(applicationHostOptions));
_scriptWebHostEnvironment = scriptWebHostEnvironment ?? throw new ArgumentNullException(nameof(scriptWebHostEnvironment));
Expand Down Expand Up @@ -378,6 +382,11 @@ private async Task UnsynchronizedStartHostAsync(ScriptHostStartupOperation activ
}
}

if (_workerConfigResolverOptionsChangeTokenSource is HostBuiltChangeTokenSource<WorkerConfigurationResolverOptions> { } hostBuiltChangeTokenResolverOptions)
{
hostBuiltChangeTokenResolverOptions.TriggerChange();
}

if (_languageWorkerOptionsChangeTokenSource is HostBuiltChangeTokenSource<LanguageWorkerOptions> { } hostBuiltChangeTokenSource)
{
hostBuiltChangeTokenSource.TriggerChange();
Expand Down
10 changes: 10 additions & 0 deletions src/WebJobs.Script/Diagnostics/Extensions/LoggerExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,11 @@ internal static class LoggerExtension
new EventId(342, nameof(OutdatedExtensionBundle)),
"Your current bundle version {currentVersion} has reached end of support on Aug 4, 2026. Upgrade to [{suggestedMinVersion}.*, {suggestedMaxVersion}.0.0). For more information, see https://aka.ms/FunctionsBundlesUpgrade");

private static readonly Action<ILogger, string, Exception> _defaultWorkersDirectoryPath =
LoggerMessage.Define<string>(LogLevel.Debug,
new EventId(343, nameof(DefaultWorkersDirectoryPath)),
"Workers Directory set to: {workersDirPath}");

public static void PublishingMetrics(this ILogger logger, string metrics)
{
_publishingMetrics(logger, metrics, null);
Expand Down Expand Up @@ -418,6 +423,11 @@ public static void IncorrectAzureFunctionsFolderPath(this ILogger logger, string
_incorrectAzureFunctionsFolderPath(logger, path, EnvironmentSettingNames.FunctionWorkerRuntime, null);
}

public static void DefaultWorkersDirectoryPath(this ILogger logger, string workersDirPath)
{
_defaultWorkersDirectoryPath(logger, workersDirPath, null);
}

public static void OutdatedExtensionBundle(this ILogger logger, string currentVersion, int suggestedMinVersion, int suggestedMaxVersion)
{
var currentTime = DateTime.UtcNow;
Expand Down
1 change: 1 addition & 0 deletions src/WebJobs.Script/ScriptConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public static class ScriptConstants
public const string LogCategoryHost = "Host";
public const string LogCategoryFunction = "Function";
public const string LogCategoryWorker = "Worker";
public const string LogCategoryWorkerConfig = "Host.LanguageWorkerConfig";

public const string SkipHostJsonConfigurationKey = "MS_SkipHostJsonConfiguration";
public const string SkipHostInitializationKey = "MS_SkipHostInitialization";
Expand Down
2 changes: 2 additions & 0 deletions src/WebJobs.Script/ScriptHostBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
using Microsoft.Azure.WebJobs.Script.Workers.Http;
using Microsoft.Azure.WebJobs.Script.Workers.Profiles;
using Microsoft.Azure.WebJobs.Script.Workers.Rpc;
using Microsoft.Azure.WebJobs.Script.Workers.Rpc.Configuration;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
Expand Down Expand Up @@ -346,6 +347,7 @@ public static IHostBuilder AddScriptHostCore(this IHostBuilder builder, ScriptAp
}
else
{
services.ConfigureOptions<WorkerConfigurationResolverOptionsSetup>();
services.ConfigureOptions<LanguageWorkerOptionsSetup>();
AddCommonServices(services);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// 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.IO.Abstractions;
using Microsoft.Azure.WebJobs.Script.Diagnostics.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace Microsoft.Azure.WebJobs.Script.Workers.Rpc.Configuration
{
// This class resolves worker configurations by scanning the "workers" directory within the Host for worker config files.
internal sealed class DefaultWorkerConfigurationResolver : IWorkerConfigurationResolver
{
private readonly ILogger _logger;
private readonly IOptionsMonitor<WorkerConfigurationResolverOptions> _workerConfigurationResolverOptions;
private readonly IFileSystem _fileSystem;

public DefaultWorkerConfigurationResolver(ILoggerFactory loggerFactory,
IFileSystem fileSystem,
IOptionsMonitor<WorkerConfigurationResolverOptions> workerConfigurationResolverOptions)
{
ArgumentNullException.ThrowIfNull(loggerFactory);
_logger = loggerFactory.CreateLogger(ScriptConstants.LogCategoryWorkerConfig);
_workerConfigurationResolverOptions = workerConfigurationResolverOptions ?? throw new ArgumentNullException(nameof(workerConfigurationResolverOptions));
_fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem));
}

public WorkerConfigurationInfo GetConfigurationInfo()
{
var workersRootDirPath = _workerConfigurationResolverOptions.CurrentValue.WorkersRootDirPath;
_logger.DefaultWorkersDirectoryPath(workersRootDirPath);

var workerConfigPaths = new List<string>();

foreach (var workerDir in _fileSystem.Directory.EnumerateDirectories(workersRootDirPath))
{
string workerConfigPath = _fileSystem.Path.Combine(workerDir, RpcWorkerConstants.WorkerConfigFileName);

if (_fileSystem.File.Exists(workerConfigPath))
{
workerConfigPaths.Add(workerDir);
}
}

return new WorkerConfigurationInfo(_workerConfigurationResolverOptions.CurrentValue.WorkersRootDirPath, workerConfigPaths);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Diagnostics;
using Microsoft.Azure.WebJobs.Script.Diagnostics;
using Microsoft.Azure.WebJobs.Script.Workers.Profiles;
using Microsoft.Azure.WebJobs.Script.Workers.Rpc.Configuration;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
Expand All @@ -21,13 +22,15 @@ internal class LanguageWorkerOptionsSetup : IConfigureOptions<LanguageWorkerOpti
private readonly IMetricsLogger _metricsLogger;
private readonly IWorkerProfileManager _workerProfileManager;
private readonly IScriptHostManager _scriptHostManager;
private readonly IWorkerConfigurationResolver _workerConfigurationResolver;

public LanguageWorkerOptionsSetup(IConfiguration configuration,
ILoggerFactory loggerFactory,
IEnvironment environment,
IMetricsLogger metricsLogger,
IWorkerProfileManager workerProfileManager,
IScriptHostManager scriptHostManager)
IScriptHostManager scriptHostManager,
IWorkerConfigurationResolver workerConfigurationResolver)
{
if (loggerFactory is null)
{
Expand All @@ -39,8 +42,9 @@ public LanguageWorkerOptionsSetup(IConfiguration configuration,
_environment = environment ?? throw new ArgumentNullException(nameof(environment));
_metricsLogger = metricsLogger ?? throw new ArgumentNullException(nameof(metricsLogger));
_workerProfileManager = workerProfileManager ?? throw new ArgumentNullException(nameof(workerProfileManager));
_workerConfigurationResolver = workerConfigurationResolver ?? throw new ArgumentNullException(nameof(workerConfigurationResolver));

_logger = loggerFactory.CreateLogger("Host.LanguageWorkerConfig");
_logger = loggerFactory.CreateLogger(ScriptConstants.LogCategoryWorkerConfig);
}

public void Configure(LanguageWorkerOptions options)
Expand Down Expand Up @@ -72,7 +76,7 @@ public void Configure(LanguageWorkerOptions options)
}
}

var configFactory = new RpcWorkerConfigFactory(configuration, _logger, SystemRuntimeInformation.Instance, _environment, _metricsLogger, _workerProfileManager);
var configFactory = new RpcWorkerConfigFactory(configuration, _logger, SystemRuntimeInformation.Instance, _environment, _metricsLogger, _workerProfileManager, _workerConfigurationResolver);
options.WorkerConfigs = configFactory.GetConfigs();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs.Script.Diagnostics;
using Microsoft.Azure.WebJobs.Script.Workers.Profiles;
using Microsoft.Azure.WebJobs.Script.Workers.Rpc.Configuration;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

Expand All @@ -26,6 +27,7 @@ internal class RpcWorkerConfigFactory
private readonly IMetricsLogger _metricsLogger;
private readonly string _workerRuntime;
private readonly IEnvironment _environment;
private readonly IWorkerConfigurationResolver _workerConfigurationResolver;
private readonly JsonSerializerOptions _jsonSerializerOptions = new()
{
PropertyNameCaseInsensitive = true
Expand All @@ -38,7 +40,8 @@ public RpcWorkerConfigFactory(IConfiguration config,
ISystemRuntimeInformation systemRuntimeInfo,
IEnvironment environment,
IMetricsLogger metricsLogger,
IWorkerProfileManager workerProfileManager)
IWorkerProfileManager workerProfileManager,
IWorkerConfigurationResolver workerConfigurationResolver)
{
_config = config ?? throw new ArgumentNullException(nameof(config));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
Expand All @@ -47,17 +50,10 @@ public RpcWorkerConfigFactory(IConfiguration config,
_metricsLogger = metricsLogger ?? throw new ArgumentNullException(nameof(metricsLogger));
_profileManager = workerProfileManager ?? throw new ArgumentNullException(nameof(workerProfileManager));
_workerRuntime = _environment.GetEnvironmentVariable(RpcWorkerConstants.FunctionWorkerRuntimeSettingName);

WorkersDirPath = GetDefaultWorkersDirectory(Directory.Exists);
var workersDirectorySection = _config.GetSection($"{RpcWorkerConstants.LanguageWorkersSectionName}:{WorkerConstants.WorkersDirectorySectionName}");

if (!string.IsNullOrEmpty(workersDirectorySection.Value))
{
WorkersDirPath = workersDirectorySection.Value;
}
_workerConfigurationResolver = workerConfigurationResolver ?? throw new ArgumentNullException(nameof(workerConfigurationResolver));
}

public string WorkersDirPath { get; }
public string WorkersDirPath { get; private set; }

public IList<RpcWorkerConfig> GetConfigs()
{
Expand All @@ -68,20 +64,6 @@ public IList<RpcWorkerConfig> GetConfigs()
}
}

internal static string GetDefaultWorkersDirectory(Func<string, bool> directoryExists)
{
#pragma warning disable SYSLIB0012 // Type or member is obsolete
string assemblyLocalPath = Path.GetDirectoryName(new Uri(typeof(RpcWorkerConfigFactory).Assembly.CodeBase).LocalPath);
#pragma warning restore SYSLIB0012 // Type or member is obsolete
string workersDirPath = Path.Combine(assemblyLocalPath, RpcWorkerConstants.DefaultWorkersDirectoryName);
if (!directoryExists(workersDirPath))
{
// Site Extension. Default to parent directory
workersDirPath = Path.Combine(Directory.GetParent(assemblyLocalPath).FullName, RpcWorkerConstants.DefaultWorkersDirectoryName);
}
return workersDirPath;
}

internal void BuildWorkerProviderDictionary()
{
AddProviders();
Expand All @@ -90,15 +72,13 @@ internal void BuildWorkerProviderDictionary()

internal void AddProviders()
{
_logger.LogDebug("Workers Directory set to: {WorkersDirPath}", WorkersDirPath);
var workerConfigurationInfo = _workerConfigurationResolver.GetConfigurationInfo();
var workerConfigs = workerConfigurationInfo.WorkerConfigPaths;
WorkersDirPath = workerConfigurationInfo.WorkersRootDirPath;

foreach (var workerDir in Directory.EnumerateDirectories(WorkersDirPath))
foreach (var workerConfig in workerConfigs)
{
string workerConfigPath = Path.Combine(workerDir, RpcWorkerConstants.WorkerConfigFileName);
if (File.Exists(workerConfigPath))
{
AddProvider(workerDir);
}
AddProvider(workerConfig);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pass the root worker dir path into this method instead of storing it in a field.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is another method in this class AddProvidersFromAppSettings() which also needs WorkersDirPath. I have again added WorkersDirPath as global variable similar to dev branch -

}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// 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;

namespace Microsoft.Azure.WebJobs.Script.Workers.Rpc.Configuration
{
internal record WorkerConfigurationInfo(string WorkersRootDirPath, IReadOnlyList<string> WorkerConfigPaths);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Azure.WebJobs.Hosting;

namespace Microsoft.Azure.WebJobs.Script.Workers.Rpc.Configuration
{
public sealed class WorkerConfigurationResolverOptions : IOptionsFormatter
{
/// <summary>
/// Gets or sets the workers directory path within the Host or defined by IConfiguration.
/// </summary>
public string WorkersRootDirPath { get; set; }

/// <inheritdoc>
/// Implements the Format method from IOptionsFormatter interface.
/// </inheritdoc>
public string Format()
{
return JsonSerializer.Serialize(this, typeof(WorkerConfigurationResolverOptions), ConfigResolverOptionsJsonSerializerContext.Default);
}
}

[JsonSourceGenerationOptions(WriteIndented = true, GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(WorkerConfigurationResolverOptions))]
internal partial class ConfigResolverOptionsJsonSerializerContext : JsonSerializerContext;
}
Loading