Skip to content

Commit 286a7df

Browse files
author
Siddharth
authored
[pack] Adding Multi language Support in Azure Function v3.x (#8449)
* Adding Multiple Languages Support in Functions * Addressing PR Comments * Adding Test Cases * Fixing Failing Tests * Resolving PR Comments * Resolving PR Comments * Resolving PR Comments * Resolving PR Comments * Removing IsMultiLanguageRuntime Cache * Resolving PR Comments * Caching IsMultiLanguageEnabled
1 parent 7d2a5ef commit 286a7df

File tree

15 files changed

+836
-60
lines changed

15 files changed

+836
-60
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Threading.Tasks;
7+
using Microsoft.AspNetCore.Hosting;
8+
using Microsoft.Azure.WebJobs.Script.Extensibility;
9+
using Microsoft.Azure.WebJobs.Script.Workers;
10+
using Microsoft.Azure.WebJobs.Script.Workers.Rpc;
11+
using Microsoft.Extensions.Logging;
12+
13+
namespace Microsoft.Azure.WebJobs.Script.Description
14+
{
15+
internal class MultiLanguageFunctionDescriptorProvider : WorkerFunctionDescriptorProvider
16+
{
17+
private readonly IList<RpcWorkerConfig> _workerConfig;
18+
19+
/// <summary>
20+
/// Initializes a new instance of the <see cref="MultiLanguageFunctionDescriptorProvider"/> class.
21+
/// </summary>
22+
/// <param name="host"><see cref="ScriptHost"/> instance.</param>
23+
/// <param name="workerConfig">All supported <see cref="RpcWorkerConfig"/>.</param>
24+
/// <param name="config"><see cref="ScriptJobHostOptions"/> instance.</param>
25+
/// <param name="bindingProviders">List of <see cref="IScriptBindingProvider"/> instances.</param>
26+
/// <param name="dispatcher"><see cref="IFunctionInvocationDispatcher"/> instance.</param>
27+
/// <param name="loggerFactory"><see cref="ILoggerFactory"/> instance.</param>
28+
/// <param name="applicationLifetime"><see cref="IApplicationLifetime"/> instance.</param>
29+
/// <param name="workerInitializationTimeout">Worker initialization timeout.</param>
30+
public MultiLanguageFunctionDescriptorProvider(ScriptHost host, IList<RpcWorkerConfig> workerConfig, ScriptJobHostOptions config, ICollection<IScriptBindingProvider> bindingProviders,
31+
IFunctionInvocationDispatcher dispatcher, ILoggerFactory loggerFactory, IApplicationLifetime applicationLifetime, TimeSpan workerInitializationTimeout)
32+
: base(host, config, bindingProviders, dispatcher, loggerFactory, applicationLifetime, workerInitializationTimeout)
33+
{
34+
_workerConfig = workerConfig ?? throw new ArgumentNullException(nameof(workerConfig));
35+
}
36+
37+
/// <inheritdoc/>
38+
public override async Task<(bool, FunctionDescriptor)> TryCreate(FunctionMetadata functionMetadata)
39+
{
40+
if (functionMetadata == null)
41+
{
42+
throw new ArgumentNullException(nameof(functionMetadata));
43+
}
44+
45+
if (!Utility.IsFunctionMetadataLanguageSupportedByWorkerRuntime(functionMetadata, _workerConfig))
46+
{
47+
return (false, null);
48+
}
49+
50+
return await base.TryCreate(functionMetadata);
51+
}
52+
}
53+
}

src/WebJobs.Script/Environment/EnvironmentExtensions.cs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.IO;
88
using System.Linq;
99
using System.Runtime.InteropServices;
10+
using Microsoft.Azure.WebJobs.Script.Config;
1011
using Microsoft.Azure.WebJobs.Script.Models;
1112
using Microsoft.Azure.WebJobs.Script.Workers.Rpc;
1213
using Microsoft.Extensions.Options;
@@ -16,6 +17,8 @@ namespace Microsoft.Azure.WebJobs.Script
1617
{
1718
internal static class EnvironmentExtensions
1819
{
20+
private static bool? isMultiLanguageEnabled;
21+
1922
// For testing
2023
internal static string BaseDirectory { get; set; }
2124

@@ -304,14 +307,26 @@ public static string GetAntaresComputerName(this IEnvironment environment)
304307
}
305308

306309
/// <summary>
307-
/// Gets the computer name.
310+
/// Gets if runtime enviromnent is logic apps.
308311
/// </summary>
309312
public static bool IsLogicApp(this IEnvironment environment)
310313
{
311314
string appKind = environment.GetEnvironmentVariable(AppKind)?.ToLower();
312315
return !string.IsNullOrEmpty(appKind) && appKind.Contains(ScriptConstants.WorkFlowAppKind);
313316
}
314317

318+
/// <summary>
319+
/// Gets if runtime environment needs multi language
320+
/// </summary>
321+
public static bool IsMultiLanguageRuntimeEnvironment(this IEnvironment environment)
322+
{
323+
if (!isMultiLanguageEnabled.HasValue)
324+
{
325+
isMultiLanguageEnabled = environment.IsLogicApp() && FeatureFlags.IsEnabled(ScriptConstants.FeatureFlagEnableMultiLanguageWorker, environment);
326+
}
327+
return isMultiLanguageEnabled.Value;
328+
}
329+
315330
/// <summary>
316331
/// Gets the Antares version.
317332
/// </summary>
@@ -507,5 +522,14 @@ public static HashSet<string> GetLanguageWorkerListToStartInPlaceholder(this IEn
507522
}
508523
return placeholderRuntimeSet;
509524
}
525+
526+
/// <summary>
527+
/// Clears all cached static flags in <see cref="EnvironmentExtensions"/>.
528+
/// Currently we only use it to purge static initialisations in across tests.
529+
/// </summary>
530+
public static void ClearCache()
531+
{
532+
isMultiLanguageEnabled = null;
533+
}
510534
}
511535
}

src/WebJobs.Script/Host/ScriptHost.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,8 @@ public async Task InitializeAsync(CancellationToken cancellationToken = default)
304304
// Windows Consumption as well.
305305
string runtimeVersion = _environment.GetEnvironmentVariable(RpcWorkerConstants.FunctionWorkerRuntimeVersionSettingName);
306306

307-
if (!string.IsNullOrEmpty(runtimeVersion))
307+
// If the environemt is multi language, we are running multiple language worker, and thus not honoring 'FUNCTIONS_WORKER_RUNTIME_VERSION'
308+
if (!string.IsNullOrEmpty(runtimeVersion) && !_environment.IsMultiLanguageRuntimeEnvironment())
308309
{
309310
runtimeStack = string.Concat(runtimeStack, "-", runtimeVersion);
310311
}
@@ -540,6 +541,13 @@ private void AddFunctionDescriptors(IEnumerable<FunctionMetadata> functionMetada
540541
_logger.AddingDescriptorProviderForLanguage(RpcWorkerConstants.DotNetLanguageWorkerName);
541542
_descriptorProviders.Add(new DotNetFunctionDescriptorProvider(this, ScriptOptions, _bindingProviders, _metricsLogger, _loggerFactory));
542543
}
544+
else if (_environment.IsMultiLanguageRuntimeEnvironment())
545+
{
546+
_logger.AddingDescriptorProviderForLanguage("All (Multi Language)");
547+
548+
_descriptorProviders.Add(new MultiLanguageFunctionDescriptorProvider(this, _languageWorkerOptions.Value.WorkerConfigs, ScriptOptions, _bindingProviders,
549+
_functionDispatcher, _loggerFactory, _applicationLifetime, _languageWorkerOptions.Value.WorkerConfigs.Max(wc => wc.CountOptions.InitializationTimeout)));
550+
}
543551
else if (_isHttpWorker)
544552
{
545553
_logger.AddingDescriptorProviderForHttpWorker();
@@ -557,7 +565,7 @@ private void AddFunctionDescriptors(IEnumerable<FunctionMetadata> functionMetada
557565
var workerConfig = _languageWorkerOptions.Value.WorkerConfigs?.FirstOrDefault(c => c.Description.Language.Equals(_workerRuntime, StringComparison.OrdinalIgnoreCase));
558566

559567
// If there's no worker config, use the default (for legacy behavior; mostly for tests).
560-
TimeSpan initializationTimeout = workerConfig?.CountOptions?.InitializationTimeout ?? WorkerProcessCountOptions.DefaultInitializationTimeout;
568+
var initializationTimeout = workerConfig?.CountOptions?.InitializationTimeout ?? WorkerProcessCountOptions.DefaultInitializationTimeout;
561569

562570
_descriptorProviders.Add(new RpcFunctionDescriptorProvider(this, _workerRuntime, ScriptOptions, _bindingProviders,
563571
_functionDispatcher, _loggerFactory, _applicationLifetime, initializationTimeout));

src/WebJobs.Script/ScriptConstants.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ public static class ScriptConstants
119119
public const string FeatureFlagDisableMergedWebHostScriptHostConfiguration = "DisableMergedConfiguration";
120120
public const string FeatureFlagDisableAspNetCoreGrpc = "DisableAspNetCoreGrpc";
121121
public const string FeatureFlagEnableWorkerIndexing = "EnableWorkerIndexing";
122+
public const string FeatureFlagEnableMultiLanguageWorker = "EnableMultiLanguageWorker";
122123

123124
public const string AdminJwtValidAudienceFormat = "https://{0}.azurewebsites.net/azurefunctions";
124125
public const string AdminJwtValidIssuerFormat = "https://{0}.scm.azurewebsites.net";

src/WebJobs.Script/Utility.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -673,9 +673,15 @@ internal static bool IsFunctionMetadataLanguageSupportedByWorkerRuntime(Function
673673
{
674674
return true;
675675
}
676+
676677
return !string.IsNullOrEmpty(functionMetadata.Language) && functionMetadata.Language.Equals(workerRuntime, StringComparison.OrdinalIgnoreCase);
677678
}
678679

680+
internal static bool IsFunctionMetadataLanguageSupportedByWorkerRuntime(FunctionMetadata functionMetadata, IList<RpcWorkerConfig> workerConfigs)
681+
{
682+
return !string.IsNullOrEmpty(functionMetadata.Language) && workerConfigs.Select(wc => wc.Description.Language).Contains(functionMetadata.Language);
683+
}
684+
679685
public static bool IsDotNetLanguageFunction(string functionLanguage)
680686
{
681687
return dotNetLanguages.Any(lang => string.Equals(lang, functionLanguage, StringComparison.OrdinalIgnoreCase));
@@ -906,9 +912,9 @@ public static void ValidateRetryOptions(RetryOptions
906912

907913
public static bool CanWorkerIndex(IEnumerable<RpcWorkerConfig> workerConfigs, IEnvironment environment)
908914
{
909-
var workerRuntime = environment.GetEnvironmentVariable(EnvironmentSettingNames.FunctionWorkerRuntime);
910-
if (workerConfigs != null)
915+
if (workerConfigs != null && !environment.IsMultiLanguageRuntimeEnvironment())
911916
{
917+
var workerRuntime = environment.GetEnvironmentVariable(EnvironmentSettingNames.FunctionWorkerRuntime);
912918
var workerConfig = workerConfigs.Where(c => c.Description != null && c.Description.Language != null && c.Description.Language.Equals(workerRuntime, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault();
913919

914920
// if feature flag is enabled and workerConfig.WorkerIndexing == true, then return true

src/WebJobs.Script/Workers/Rpc/Configuration/RpcWorkerConfigFactory.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,12 @@ internal bool ShouldAddWorkerConfig(string workerDescriptionLanguage)
284284
return true;
285285
}
286286

287+
if (_environment.IsMultiLanguageRuntimeEnvironment())
288+
{
289+
_logger.LogInformation($"Found multi-language runtime environment. Starting WorkerConfig for language: {workerDescriptionLanguage}");
290+
return true;
291+
}
292+
287293
if (!string.IsNullOrEmpty(_workerRuntime))
288294
{
289295
_logger.LogDebug($"EnvironmentVariable {RpcWorkerConstants.FunctionWorkerRuntimeSettingName}: {_workerRuntime}");

0 commit comments

Comments
 (0)