Skip to content

Commit e265feb

Browse files
authored
Shutdown placeholder language worker channels if not needed (#3867)
1 parent bba26a3 commit e265feb

File tree

14 files changed

+243
-215
lines changed

14 files changed

+243
-215
lines changed

src/WebJobs.Script/Host/ScriptHost.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -273,11 +273,10 @@ public async Task InitializeAsync()
273273

274274
// Generate Functions
275275
IEnumerable<FunctionMetadata> functions = GetFunctionsMetadata();
276-
_workerRuntime = _workerRuntime ?? Utility.GetWorkerRuntime(functions);
277-
if (Utility.ShouldInitializeFunctionDispatcher(_environment, functions, _workerRuntime))
278-
{
279-
_functionDispatcher.CreateWorkerState(_workerRuntime);
280-
}
276+
277+
// Initialize language worker function dispatcher
278+
_functionDispatcher.Initialize(_workerRuntime, functions);
279+
281280
var directTypes = GetDirectTypes(functions);
282281
await InitializeFunctionDescriptorsAsync(functions);
283282
GenerateFunctions(directTypes);

src/WebJobs.Script/Rpc/FunctionRegistration/FunctionDispatcher.cs

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using Microsoft.Azure.WebJobs.Script.Eventing;
1313
using Microsoft.Extensions.Logging;
1414
using Microsoft.Extensions.Options;
15+
1516
using FunctionMetadata = Microsoft.Azure.WebJobs.Script.Description.FunctionMetadata;
1617

1718
namespace Microsoft.Azure.WebJobs.Script.Rpc
@@ -90,27 +91,38 @@ public LanguageWorkerState CreateWorkerStateWithExistingChannel(string language,
9091
return state;
9192
}
9293

93-
public LanguageWorkerState CreateWorkerState(string language)
94+
public void Initialize(string workerRuntime, IEnumerable<FunctionMetadata> functions)
9495
{
95-
ILanguageWorkerChannel initializedChannel = _languageWorkerChannelManager.GetChannel(language);
96-
if (initializedChannel != null)
97-
{
98-
return CreateWorkerStateWithExistingChannel(language, initializedChannel);
99-
}
100-
else
96+
_languageWorkerChannelManager.ShutdownStandbyChannels(functions);
97+
98+
workerRuntime = workerRuntime ?? Utility.GetWorkerRuntime(functions);
99+
100+
if (Utility.IsSupportedRuntime(workerRuntime, _workerConfigs))
101101
{
102-
var state = new LanguageWorkerState();
103-
WorkerConfig config = _workerConfigs.Where(c => c.Language.Equals(language, StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
104-
state.Channel = ChannelFactory(language, state.Functions, 0);
105-
_workerStates[language] = state;
106-
return state;
102+
ILanguageWorkerChannel initializedChannel = _languageWorkerChannelManager.GetChannel(workerRuntime);
103+
if (initializedChannel != null)
104+
{
105+
CreateWorkerStateWithExistingChannel(workerRuntime, initializedChannel);
106+
}
107+
else
108+
{
109+
CreateWorkerState(workerRuntime);
110+
}
107111
}
108112
}
109113

114+
private LanguageWorkerState CreateWorkerState(string runtime)
115+
{
116+
var state = new LanguageWorkerState();
117+
WorkerConfig config = _workerConfigs.Where(c => c.Language.Equals(runtime, StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
118+
state.Channel = ChannelFactory(runtime, state.Functions, 0);
119+
_workerStates[runtime] = state;
120+
return state;
121+
}
122+
110123
public void Register(FunctionRegistrationContext context)
111124
{
112-
var state = _workerStates.GetOrAdd(context.Metadata.Language, CreateWorkerState);
113-
state.Functions.OnNext(context);
125+
_workerStates[context.Metadata.Language].Functions.OnNext(context);
114126
}
115127

116128
public void WorkerError(WorkerErrorEvent workerError)

src/WebJobs.Script/Rpc/FunctionRegistration/IFunctionDispatcher.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Threading.Tasks;
67
using Microsoft.Azure.WebJobs.Script.Description;
78

89
namespace Microsoft.Azure.WebJobs.Script.Rpc
@@ -17,6 +18,6 @@ public interface IFunctionDispatcher : IDisposable
1718
// Registers a supported function with the dispatcher
1819
void Register(FunctionRegistrationContext context);
1920

20-
LanguageWorkerState CreateWorkerState(string language);
21+
void Initialize(string runtime, IEnumerable<FunctionMetadata> functions);
2122
}
2223
}

src/WebJobs.Script/Rpc/ILanguageWorkerChannelManager.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@
44
using System;
55
using System.Collections.Generic;
66
using System.Threading.Tasks;
7+
using Microsoft.Azure.WebJobs.Host;
78
using Microsoft.Azure.WebJobs.Script.Diagnostics;
89

10+
using FunctionMetadata = Microsoft.Azure.WebJobs.Script.Description.FunctionMetadata;
11+
912
namespace Microsoft.Azure.WebJobs.Script.Rpc
1013
{
1114
public interface ILanguageWorkerChannelManager
@@ -18,7 +21,9 @@ public interface ILanguageWorkerChannelManager
1821

1922
bool ShutdownChannelIfExists(string language);
2023

21-
Task ShutdownChannelsAsync();
24+
void ShutdownStandbyChannels(IEnumerable<FunctionMetadata> functions);
25+
26+
void ShutdownChannels();
2227

2328
ILanguageWorkerChannel CreateLanguageWorkerChannel(string workerId, string scriptRootPath, string language, IObservable<FunctionRegistrationContext> functionRegistrations, IMetricsLogger metricsLogger, int attemptCount);
2429
}

src/WebJobs.Script/Rpc/LanguageWorkerChannelManager.cs

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
using Microsoft.Extensions.Logging;
1313
using Microsoft.Extensions.Options;
1414

15+
using FunctionMetadata = Microsoft.Azure.WebJobs.Script.Description.FunctionMetadata;
16+
1517
namespace Microsoft.Azure.WebJobs.Script.Rpc
1618
{
1719
public class LanguageWorkerChannelManager : ILanguageWorkerChannelManager
@@ -150,8 +152,7 @@ public bool ShutdownChannelIfExists(string language)
150152

151153
internal void ScheduleShutdownStandbyChannels()
152154
{
153-
_workerRuntime = _environment.GetEnvironmentVariable(LanguageWorkerConstants.FunctionWorkerRuntimeSettingName);
154-
_logger.LogInformation("{FunctionWorkerRuntimeSettingName} is set to: {workerRuntime}. Will shutdown other standby channels", LanguageWorkerConstants.FunctionWorkerRuntimeSettingName, _workerRuntime);
155+
_workerRuntime = _workerRuntime ?? _environment.GetEnvironmentVariable(LanguageWorkerConstants.FunctionWorkerRuntimeSettingName);
155156
if (!string.IsNullOrEmpty(_workerRuntime))
156157
{
157158
var standbyChannels = _workerChannels.Where(ch => ch.Key.ToLower() != _workerRuntime.ToLower()).ToList();
@@ -164,15 +165,33 @@ internal void ScheduleShutdownStandbyChannels()
164165
}
165166
}
166167

167-
public Task ShutdownChannelsAsync()
168+
public void ShutdownStandbyChannels(IEnumerable<FunctionMetadata> functions)
169+
{
170+
if (_environment.GetEnvironmentVariable(EnvironmentSettingNames.AzureWebsitePlaceholderMode) == "1")
171+
{
172+
return;
173+
}
174+
_workerRuntime = _environment.GetEnvironmentVariable(LanguageWorkerConstants.FunctionWorkerRuntimeSettingName) ?? Utility.GetWorkerRuntime(functions);
175+
_logger.LogInformation("WorkerRuntime: {workerRuntime}. Will shutdown other standby channels", _workerRuntime);
176+
if (string.IsNullOrEmpty(_workerRuntime))
177+
{
178+
ShutdownChannels();
179+
return;
180+
}
181+
else
182+
{
183+
ScheduleShutdownStandbyChannels();
184+
}
185+
}
186+
187+
public void ShutdownChannels()
168188
{
169-
foreach (string lang in _workerChannels.Keys)
189+
foreach (string runtime in _workerChannels.Keys)
170190
{
171-
_logger.LogDebug("Language worker channel for runtime:{language} disposed", lang);
172-
_workerChannels[lang]?.Dispose();
191+
_logger.LogInformation("Shutting down language worker channel for runtime:{runtime}", runtime);
192+
_workerChannels[runtime]?.Dispose();
173193
}
174194
_workerChannels.Clear();
175-
return Task.CompletedTask;
176195
}
177196

178197
private void AddOrUpdateWorkerChannels(RpcChannelReadyEvent rpcChannelReadyEvent)

src/WebJobs.Script/Rpc/RpcInitializationService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public async Task StartAsync(CancellationToken cancellationToken)
4949
public async Task StopAsync(CancellationToken cancellationToken)
5050
{
5151
_logger.LogInformation("Shuttingdown Rpc Channels Manager");
52-
await _languageWorkerChannelManager.ShutdownChannelsAsync();
52+
_languageWorkerChannelManager.ShutdownChannels();
5353
await _rpcServer.KillAsync();
5454
}
5555

src/WebJobs.Script/Utility.cs

Lines changed: 20 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -439,22 +439,22 @@ internal static bool TryReadFunctionConfig(string scriptDir, out string json, IF
439439
return true;
440440
}
441441

442-
internal static void VerifyFunctionsMatchSpecifiedLanguage(IEnumerable<FunctionMetadata> functions, string currentRuntimeLanguage)
442+
internal static void VerifyFunctionsMatchSpecifiedLanguage(IEnumerable<FunctionMetadata> functions, string workerRuntime)
443443
{
444-
if (!IsSingleLanguage(functions, currentRuntimeLanguage))
444+
if (!IsSingleLanguage(functions, workerRuntime))
445445
{
446-
if (string.IsNullOrEmpty(currentRuntimeLanguage))
446+
if (string.IsNullOrEmpty(workerRuntime))
447447
{
448448
throw new HostInitializationException($"Found functions with more than one language. Select a language for your function app by specifying {LanguageWorkerConstants.FunctionWorkerRuntimeSettingName} AppSetting");
449449
}
450450
else
451451
{
452-
throw new HostInitializationException($"Did not find functions with language [{currentRuntimeLanguage}].");
452+
throw new HostInitializationException($"Did not find functions with language [{workerRuntime}].");
453453
}
454454
}
455455
}
456456

457-
internal static bool IsSingleLanguage(IEnumerable<FunctionMetadata> functions, string currentRuntimeLanguage)
457+
internal static bool IsSingleLanguage(IEnumerable<FunctionMetadata> functions, string workerRuntime)
458458
{
459459
if (functions == null)
460460
{
@@ -465,54 +465,36 @@ internal static bool IsSingleLanguage(IEnumerable<FunctionMetadata> functions, s
465465
{
466466
return true;
467467
}
468-
if (string.IsNullOrEmpty(currentRuntimeLanguage))
468+
if (string.IsNullOrEmpty(workerRuntime))
469469
{
470470
return functionsListWithoutProxies.Select(f => f.Language).Distinct().Count() <= 1;
471471
}
472-
return ContainsFunctionWithWorkerRuntime(functionsListWithoutProxies, currentRuntimeLanguage);
473-
}
474-
475-
internal static bool ShouldInitializeFunctionDispatcher(IEnvironment environment, IEnumerable<FunctionMetadata> functions, string workerRuntime)
476-
{
477-
if (environment.GetEnvironmentVariable(EnvironmentSettingNames.AzureWebsitePlaceholderMode) == "1")
478-
{
479-
return false;
480-
}
481-
if (!string.IsNullOrEmpty(workerRuntime) && workerRuntime.Equals(LanguageWorkerConstants.DotNetLanguageWorkerName, StringComparison.OrdinalIgnoreCase))
482-
{
483-
return false;
484-
}
485-
var functionsListWithoutProxies = functions?.Where(f => f.IsProxy == false);
486-
if (!string.IsNullOrEmpty(workerRuntime) && ContainsFunctionWithWorkerRuntime(functionsListWithoutProxies, workerRuntime))
487-
{
488-
return true;
489-
}
490-
return ContainsNonDotNetFunctions(functionsListWithoutProxies);
472+
return ContainsFunctionWithWorkerRuntime(functionsListWithoutProxies, workerRuntime);
491473
}
492474

493475
internal static string GetWorkerRuntime(IEnumerable<FunctionMetadata> functions)
494476
{
495-
var functionsListWithoutProxies = functions?.Where(f => f.IsProxy == false);
496-
string functionLanguage = functionsListWithoutProxies.FirstOrDefault()?.Language;
497-
if (IsDotNetLanguageFunction(functionLanguage))
477+
if (IsSingleLanguage(functions, null))
498478
{
499-
return LanguageWorkerConstants.DotNetLanguageWorkerName;
479+
var functionsListWithoutProxies = functions?.Where(f => f.IsProxy == false);
480+
string functionLanguage = functionsListWithoutProxies.FirstOrDefault()?.Language;
481+
if (IsDotNetLanguageFunction(functionLanguage))
482+
{
483+
return LanguageWorkerConstants.DotNetLanguageWorkerName;
484+
}
485+
return functionLanguage;
500486
}
501-
return functionLanguage;
487+
return null;
502488
}
503489

504-
private static bool ContainsNonDotNetFunctions(IEnumerable<FunctionMetadata> functions)
490+
public static bool IsDotNetLanguageFunction(string functionLanguage)
505491
{
506-
if (functions != null && functions.Any())
507-
{
508-
return functions.Any(f => !dotNetLanguages.Contains(f.Language, StringComparer.OrdinalIgnoreCase));
509-
}
510-
return false;
492+
return dotNetLanguages.Any(lang => string.Equals(lang, functionLanguage, StringComparison.OrdinalIgnoreCase));
511493
}
512494

513-
public static bool IsDotNetLanguageFunction(string functionLanguage)
495+
public static bool IsSupportedRuntime(string workerRuntime, IEnumerable<WorkerConfig> workerConfigs)
514496
{
515-
return dotNetLanguages.Any(lang => string.Equals(lang, functionLanguage, StringComparison.OrdinalIgnoreCase));
497+
return workerConfigs.Any(config => string.Equals(config.Language, workerRuntime, StringComparison.OrdinalIgnoreCase));
516498
}
517499

518500
private static bool ContainsFunctionWithWorkerRuntime(IEnumerable<FunctionMetadata> functions, string workerRuntime)

test/WebJobs.Script.Tests.Integration/Rpc/LanguageWorkerChannelManagerEndToEndTests.cs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
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;
5-
using System.Diagnostics;
6-
using System.Linq;
7-
using System.Threading.Tasks;
84
using Microsoft.Azure.WebJobs.Script.Rpc;
5+
using System.Threading.Tasks;
96
using Xunit;
107

118
namespace Microsoft.Azure.WebJobs.Script.Tests
@@ -23,17 +20,23 @@ public LanguageWorkerChannelManagerEndToEndTests(TestFixture fixture)
2320
public TestFixture Fixture { get; set; }
2421

2522
[Fact]
26-
public void InitializeAsync_DoNotInitialize_JavaWorker()
23+
public void InitializeAsync_DoNotInitialize_JavaWorker_ProxiesOnly()
2724
{
2825
var javaChannel = _languageWorkerChannelManager.GetChannel(LanguageWorkerConstants.JavaLanguageWorkerName);
2926
Assert.Null(javaChannel);
3027
}
3128

3229
public class TestFixture : ScriptHostEndToEndTestFixture
3330
{
34-
public TestFixture() : base(@"TestScripts\Node", "node", LanguageWorkerConstants.NodeLanguageWorkerName,
35-
startHost: true, functions: new[] { "HttpTrigger" })
31+
public TestFixture() : base(@"TestScripts\ProxiesOnly", "proxiesOnly", string.Empty,
32+
startHost: true)
33+
{
34+
}
35+
36+
protected override Task CreateTestStorageEntities()
3637
{
38+
// No need for this.
39+
return Task.CompletedTask;
3740
}
3841
}
3942
}

test/WebJobs.Script.Tests.Integration/ScriptHostEndToEnd/NodeContentTests.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,12 @@ namespace Microsoft.Azure.WebJobs.Script.Tests
1717
{
1818
public class NodeContentTests : IClassFixture<NodeContentTests.TestFixture>
1919
{
20+
private ILanguageWorkerChannelManager _languageWorkerChannelManager;
21+
2022
public NodeContentTests(TestFixture fixture)
2123
{
2224
Fixture = fixture;
25+
_languageWorkerChannelManager = (ILanguageWorkerChannelManager)fixture.Host.Services.GetService(typeof(ILanguageWorkerChannelManager));
2326
}
2427

2528
public TestFixture Fixture { get; set; }
@@ -129,6 +132,13 @@ public async Task ObjectXml()
129132
Assert.Equal(str, content);
130133
}
131134

135+
[Fact]
136+
public void InitializeAsync_WorkerRuntime_Node_DoNotInitialize_JavaWorker()
137+
{
138+
var javaChannel = _languageWorkerChannelManager.GetChannel(LanguageWorkerConstants.JavaLanguageWorkerName);
139+
Assert.Null(javaChannel);
140+
}
141+
132142
// Get response with default ObjectResult content negotiation enabled
133143
protected Task<string> ResponseWithConneg<Req>(Req content, string contentType, string expectedContentType = null)
134144
{
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"version": "2.0"
3+
}

0 commit comments

Comments
 (0)