Skip to content

Conversation

surgupta-msft
Copy link
Contributor

@surgupta-msft surgupta-msft commented Aug 19, 2025

Issue describing the changes in this PR

resolves #10944

Validation

E2E scenarios have been validated as outlined in the doc. We can run another round of E2E validations if there are significant changes in the code. Otherwise, these are not required for general changes and tests in the PR are sufficient.

  • Update 9/22 - Ran another round of E2E validations
  • Update 9/8 - Ran another round of E2E validations till commit id - c8982d0

Gists

A lot of code has been moved from RPCWorkerConfigFactory to WorkerConfigurationHelper so it can be shared by 2 resolvers. Adding below gists help with the review. Most of it is just refactoring and no logic changes.

Pull request checklist

IMPORTANT: Currently, changes must be backported to the in-proc branch to be included in Core Tools and non-Flex deployments.

  • Backporting to the in-proc branch is not required
    • Otherwise: Link to backporting PR
  • My changes do not require documentation changes
    • Otherwise: Documentation issue linked to PR
  • My changes should not be added to the release notes for the next release
    • Otherwise: I've added my notes to release_notes.md
  • My changes do not need to be backported to a previous version
    • Otherwise: Backport tracked by issue/PR #issue_or_pr
  • My changes do not require diagnostic events changes
    • Otherwise: I have added/updated all related diagnostic events and their documentation (Documentation issue linked to PR)
  • I have added all required tests (Unit tests, E2E tests)

Additional information

This pull request introduces changes to decouple language workers from the Host release and enable dynamic worker resolution.

Backlog issues - Link
Design doc - Link

Flows covered in this PR -

  1. Resolving Worker Configurations -
    • Introduced DynamicWorkerConfigurationResolver, which dynamically resolves worker configurations based on -
      • Environment settings and probing paths
      • Host-Worker compatibility check
      • Leveraging Release channel concept to point to a previous version of the worker
      • Fallback to the old flow if worker is not found at the probing path level
      • Ignoring versions of a worker specified via Hosting config
  2. RPCWorkerConfigFactory -
    • Moved some methods to WorkerConfigurationHelper.cs to enable reusing of workers profile evaluation logic between 2 resolvers.
  3. Environment Updates -
    • Added a new feature flag FeatureFlagDisableWorkerProbingPaths to disable dynamic resolution feature by users.
    • Added 2 Hosting config settings
      • To get list of workers available for resolution.
      • To get list of workers versions to be ignored.
  4. WorkerConfigurationResolverOptionsSetup -
    • Configures WorkerConfigurationResolverOptions by getting required values from IConfiguration and IEnvironment such as worker probing paths, release channel, workers available for resolution via hosting config.

Note: The decoupling workers flow is disabled by default in this PR. The flow can be enabled by setting the Hosting config which will be done after completing other relevant backlog items.

var loggerFactory = p.GetService<ILoggerFactory>();
var metricsLogger = p.GetService<IMetricsLogger>();

return workerConfigurationResolverOptions?.CurrentValue?.IsDynamicWorkerResolutionEnabled is true ?
Copy link
Contributor

Choose a reason for hiding this comment

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

Is IsDynamicWorkerResolutionEnabled expected to change value in the lifetime of a host process? You are using IOptionsMonitor and .CurrentValue, which makes it look like you expect it to change. But you are registering a singleton here. This factory will only ever be run once. So, once a dynamic or default config resolver is selected it will never change.

public static readonly long DefaultMaxRequestBodySize = 104857600;

public static readonly ImmutableArray<string> SystemLogCategoryPrefixes = ImmutableArray.Create("Microsoft.Azure.WebJobs.", "Function.", "Worker.", "Host.");
public static readonly ImmutableHashSet<string> HostCapabilities = ImmutableHashSet.Create<string>(StringComparer.OrdinalIgnoreCase);
Copy link
Contributor

Choose a reason for hiding this comment

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

This is immutable, readonly, and has no values. It will never contain a value. What is the purpose of it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Today, we don't have any capability that requires negotiation. But if in future, we introduce a capability then we can add in this structure?

For example, there is a new feature "feature1" and during worker probing we want to make sure that host supports "feature1" then we can add as follows in that Host version.

HostCapabilities = ImmutableHashSet.Create<string>("feature1",  "feature2");

/// Retrieves a dictionary of worker configurations, keyed by language name.
/// </summary>
WorkerConfigurationInfo GetConfigurationInfo();
Dictionary<string, RpcWorkerConfig> GetWorkerConfigs();
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider returning IReadOnlyDictionary, unless you expect callers to mutate it.

Preferring IReadOnlyDictionary opens up the door for implementations to cache their result and return it in future calls, as they can assert no caller has mutated it underneath them

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Caller can modify it - Link1, Link2

/// <summary>
/// Gets or sets the worker runtimes available for resolution via Hosting configuration.
/// </summary>
public ImmutableHashSet<string> WorkersAvailableForResolution { get; set; }
Copy link
Contributor

Choose a reason for hiding this comment

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

I only ever use immutable collections for a very specific case: when you expect callers to take your value AND "mutate" their own copy of it, but you don't want the original instance to be affected.

If these are expected to be purely read only (you don't expect callers to make adjustments), then use a readonly interface.

IReadOnlySet<T>, IReadOnlyDictionary<TKey, TValue>

To give an example of a good immutable collection use case is Roslyn analyzers. Roslyn will provide a bunch of collections to all the different analyzers that are expected to make changes to them and return those changes. But these analyzers are all ran in parallel, so regular collections wouldn't be thread safe! So Roslyn uses immutable collects to send the initial values to all the analyzers, then evaluates any changes the analyzers made afterwards and merges them together. (this is my very basic understanding of Roslyn, it may not be 100% accurate)

ArgumentNullException.ThrowIfNull(loggerFactory);
_logger = loggerFactory.CreateLogger(ScriptConstants.LogCategoryWorkerConfig);
_workerConfigurationResolverOptions = workerConfigurationResolverOptions ?? throw new ArgumentNullException(nameof(workerConfigurationResolverOptions));
_metricsLogger = metricsLogger ?? throw new ArgumentNullException(nameof(metricsLogger));
Copy link
Member

Choose a reason for hiding this comment

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

nit: ArgumentNullException.ThrowIfNull(metricsLogger)

similar recommendation for other arguments as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement changes needed in the Host to decouple workers from the Host release
6 participants