Skip to content

Commit 74d7ccc

Browse files
authored
Reduce allocations in RpcWorkerConfigFactory.AddProviders (#10959)
* AddProviders memory optimizations. * Adding release notes. * CA warning fixes. * Minor cleanup * Extracting GetWorkerConfigJson method to read and parse worker config json. * PR feedback fixes. * PR Feedback fixes.
1 parent fb4c911 commit 74d7ccc

11 files changed

+174
-106
lines changed

release_notes.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
### Release notes
2-
3-
<!-- Please add your release notes in the following format:
4-
- My change description (#PR)
5-
-->
6-
- Improved memory metrics reporting using CGroup data for Linux consumption (#10968)
7-
- Fixing GrpcWorkerChannel concurrency bug (#10998)
8-
- Avoid circular dependency when resolving LinuxContainerLegionMetricsPublisher. (#10991)
1+
### Release notes
2+
3+
<!-- Please add your release notes in the following format:
4+
- My change description (#PR)
5+
-->
6+
- Improved memory metrics reporting using CGroup data for Linux consumption (#10968)
7+
- Memory allocation optimizations in `RpcWorkerConfigFactory.AddProviders` (#10959)
8+
- Fixing GrpcWorkerChannel concurrency bug (#10998)
9+
- Avoid circular dependency when resolving LinuxContainerLegionMetricsPublisher. (#10991)

src/WebJobs.Script/Workers/Profiles/Conditions/EnvironmentCondition.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,15 @@ internal EnvironmentCondition(ILogger logger, IEnvironment environment, WorkerPr
3030
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
3131
_environment = environment ?? throw new ArgumentNullException(nameof(environment));
3232

33-
descriptor.Properties.TryGetValue(WorkerConstants.WorkerDescriptionProfileConditionName, out _name);
34-
descriptor.Properties.TryGetValue(WorkerConstants.WorkerDescriptionProfileConditionExpression, out _expression);
33+
if (descriptor.Properties.TryGetValue(WorkerConstants.WorkerDescriptionProfileConditionName, out var conditionNameElement))
34+
{
35+
_name = conditionNameElement.GetString();
36+
}
37+
38+
if (descriptor.Properties.TryGetValue(WorkerConstants.WorkerDescriptionProfileConditionExpression, out var conditionExpressionElement))
39+
{
40+
_expression = conditionExpressionElement.GetString();
41+
}
3542

3643
Validate();
3744
}

src/WebJobs.Script/Workers/Profiles/Conditions/HostPropertyCondition.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,15 @@ public HostPropertyCondition(ILogger logger, ISystemRuntimeInformation systemRun
3232
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
3333
_systemRuntimeInformation = systemRuntimeInformation ?? throw new ArgumentNullException(nameof(systemRuntimeInformation));
3434

35-
descriptor.Properties.TryGetValue(WorkerConstants.WorkerDescriptionProfileConditionName, out _name);
36-
descriptor.Properties.TryGetValue(WorkerConstants.WorkerDescriptionProfileConditionExpression, out _expression);
35+
if (descriptor.Properties.TryGetValue(WorkerConstants.WorkerDescriptionProfileConditionName, out var conditionNameElement))
36+
{
37+
_name = conditionNameElement.GetString();
38+
}
39+
40+
if (descriptor.Properties.TryGetValue(WorkerConstants.WorkerDescriptionProfileConditionExpression, out var conditionExpressionElement))
41+
{
42+
_expression = conditionExpressionElement.GetString();
43+
}
3744

3845
Validate();
3946
}

src/WebJobs.Script/Workers/Profiles/WorkerProfileConditionDescriptor.cs

Lines changed: 6 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,35 +2,18 @@
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

44
using System.Collections.Generic;
5-
using System.Linq;
6-
using Newtonsoft.Json;
7-
using Newtonsoft.Json.Linq;
5+
using System.Text.Json;
6+
using System.Text.Json.Serialization;
87

98
namespace Microsoft.Azure.WebJobs.Script.Workers.Profiles
109
{
1110
public sealed class WorkerProfileConditionDescriptor
1211
{
13-
[JsonExtensionData]
14-
#pragma warning disable CS0649 // The value is assigned by the serializer
15-
private IDictionary<string, JToken> _extensionData;
16-
#pragma warning restore CS0649
17-
18-
private IDictionary<string, string> _properties;
19-
20-
[JsonProperty(Required = Required.Always, PropertyName = WorkerConstants.WorkerDescriptionProfileConditionType)]
12+
[JsonRequired]
13+
[JsonPropertyName(WorkerConstants.WorkerDescriptionProfileConditionType)]
2114
public string Type { get; set; }
2215

23-
public IDictionary<string, string> Properties
24-
{
25-
get
26-
{
27-
if (_properties == null)
28-
{
29-
_properties = _extensionData?.ToDictionary(kv => kv.Key, kv => kv.Value.ToString()) ?? new Dictionary<string, string>();
30-
}
31-
32-
return _properties;
33-
}
34-
}
16+
[JsonExtensionData]
17+
public Dictionary<string, JsonElement> Properties { get; set; } = new Dictionary<string, JsonElement>();
3518
}
3619
}

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

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
using System.Collections.Generic;
66
using System.IO;
77
using System.Linq;
8+
using System.Text.Json;
89
using System.Text.RegularExpressions;
910
using Microsoft.Azure.WebJobs.Script.Diagnostics;
1011
using Microsoft.Azure.WebJobs.Script.Workers.Profiles;
1112
using Microsoft.Extensions.Configuration;
1213
using Microsoft.Extensions.Logging;
13-
using Newtonsoft.Json.Linq;
1414

1515
namespace Microsoft.Azure.WebJobs.Script.Workers.Rpc
1616
{
@@ -24,6 +24,10 @@ internal class RpcWorkerConfigFactory
2424
private readonly IMetricsLogger _metricsLogger;
2525
private readonly string _workerRuntime;
2626
private readonly IEnvironment _environment;
27+
private readonly JsonSerializerOptions _jsonSerializerOptions = new()
28+
{
29+
PropertyNameCaseInsensitive = true
30+
};
2731

2832
private Dictionary<string, RpcWorkerConfig> _workerDescriptionDictionary = new Dictionary<string, RpcWorkerConfig>();
2933

@@ -116,6 +120,18 @@ internal void AddProvider(string workerDir)
116120
{
117121
try
118122
{
123+
// After specialization, load worker config only for the specified runtime unless it's a multi-language app.
124+
if (!string.IsNullOrWhiteSpace(_workerRuntime) && !_environment.IsPlaceholderModeEnabled() && !_environment.IsMultiLanguageRuntimeEnvironment())
125+
{
126+
string workerRuntime = Path.GetFileName(workerDir);
127+
// Only skip worker directories that don't match the current runtime.
128+
// Do not skip non-worker directories like the function app payload directory
129+
if (!workerRuntime.Equals(_workerRuntime, StringComparison.OrdinalIgnoreCase) && workerDir.StartsWith(WorkersDirPath))
130+
{
131+
return;
132+
}
133+
}
134+
119135
string workerConfigPath = Path.Combine(workerDir, RpcWorkerConstants.WorkerConfigFileName);
120136

121137
if (!File.Exists(workerConfigPath))
@@ -126,15 +142,13 @@ internal void AddProvider(string workerDir)
126142

127143
_logger.LogDebug("Found worker config: {workerConfigPath}", workerConfigPath);
128144

129-
// Parse worker config file
130-
string json = File.ReadAllText(workerConfigPath);
131-
JObject workerConfig = JObject.Parse(json);
132-
RpcWorkerDescription workerDescription = workerConfig.Property(WorkerConstants.WorkerDescription).Value.ToObject<RpcWorkerDescription>();
145+
var workerConfig = GetWorkerConfigJsonElement(workerConfigPath);
146+
var workerDescriptionElement = workerConfig.GetProperty(WorkerConstants.WorkerDescription);
147+
var workerDescription = workerDescriptionElement.Deserialize<RpcWorkerDescription>(_jsonSerializerOptions);
133148
workerDescription.WorkerDirectory = workerDir;
134149

135-
//Read the profiles from worker description and load the profile for which the conditions match
136-
JToken profiles = workerConfig.GetValue(WorkerConstants.WorkerDescriptionProfiles);
137-
if (profiles != null)
150+
// Read the profiles from worker description and load the profile for which the conditions match
151+
if (workerConfig.TryGetProperty(WorkerConstants.WorkerDescriptionProfiles, out var profiles))
138152
{
139153
List<WorkerDescriptionProfile> workerDescriptionProfiles = ReadWorkerDescriptionProfiles(profiles);
140154
if (workerDescriptionProfiles.Count > 0)
@@ -146,7 +160,7 @@ internal void AddProvider(string workerDir)
146160

147161
// Check if any app settings are provided for that language
148162
var languageSection = _config.GetSection($"{RpcWorkerConstants.LanguageWorkersSectionName}:{workerDescription.Language}");
149-
workerDescription.Arguments = workerDescription.Arguments ?? new List<string>();
163+
workerDescription.Arguments ??= new List<string>();
150164
GetWorkerDescriptionFromAppSettings(workerDescription, languageSection);
151165
AddArgumentsFromAppSettings(workerDescription, languageSection);
152166

@@ -197,9 +211,24 @@ internal void AddProvider(string workerDir)
197211
}
198212
}
199213

200-
private List<WorkerDescriptionProfile> ReadWorkerDescriptionProfiles(JToken profilesJToken)
214+
private static JsonElement GetWorkerConfigJsonElement(string workerConfigPath)
201215
{
202-
var profiles = profilesJToken.ToObject<IList<WorkerProfileDescriptor>>();
216+
ReadOnlySpan<byte> jsonSpan = File.ReadAllBytes(workerConfigPath);
217+
218+
if (jsonSpan.StartsWith<byte>([0xEF, 0xBB, 0xBF]))
219+
{
220+
jsonSpan = jsonSpan[3..]; // Skip UTF-8 Byte Order Mark (BOM) if present at the beginning of the file.
221+
}
222+
223+
var reader = new Utf8JsonReader(jsonSpan, isFinalBlock: true, state: default);
224+
using var doc = JsonDocument.ParseValue(ref reader);
225+
226+
return doc.RootElement.Clone();
227+
}
228+
229+
private List<WorkerDescriptionProfile> ReadWorkerDescriptionProfiles(JsonElement profilesElement)
230+
{
231+
var profiles = profilesElement.Deserialize<IList<WorkerProfileDescriptor>>(_jsonSerializerOptions);
203232

204233
if (profiles == null || profiles.Count <= 0)
205234
{
@@ -237,11 +266,16 @@ private List<WorkerDescriptionProfile> ReadWorkerDescriptionProfiles(JToken prof
237266
return descriptionProfiles;
238267
}
239268

240-
internal WorkerProcessCountOptions GetWorkerProcessCount(JObject workerConfig)
269+
internal WorkerProcessCountOptions GetWorkerProcessCount(JsonElement workerConfig)
241270
{
242-
WorkerProcessCountOptions workerProcessCount = workerConfig.Property(WorkerConstants.ProcessCount)?.Value.ToObject<WorkerProcessCountOptions>();
271+
WorkerProcessCountOptions workerProcessCount = null;
272+
273+
if (workerConfig.TryGetProperty(WorkerConstants.ProcessCount, out var processCountElement))
274+
{
275+
workerProcessCount = processCountElement.Deserialize<WorkerProcessCountOptions>();
276+
}
243277

244-
workerProcessCount = workerProcessCount ?? new WorkerProcessCountOptions();
278+
workerProcessCount ??= new WorkerProcessCountOptions();
245279

246280
if (workerProcessCount.SetProcessCountToNumberOfCpuCores)
247281
{

test/WebJobs.Script.Tests/Workers/Profiles/EnvironmentConditionTests.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +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;
54
using System.ComponentModel.DataAnnotations;
5+
using System.Text.Json;
66
using Microsoft.Azure.WebJobs.Script.Workers;
77
using Microsoft.Azure.WebJobs.Script.Workers.Profiles;
88
using Xunit;
@@ -28,8 +28,8 @@ public void EnvironmentConditionTest_ThrowsValidationException(string name, stri
2828
var descriptor = new WorkerProfileConditionDescriptor();
2929
descriptor.Type = WorkerConstants.WorkerDescriptionProfileEnvironmentCondition;
3030

31-
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionName] = name;
32-
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionExpression] = expression;
31+
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionName] = JsonSerializer.SerializeToElement(name);
32+
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionExpression] = JsonSerializer.SerializeToElement(expression);
3333

3434
Assert.Throws<ValidationException>(() => new EnvironmentCondition(testLogger, _testEnvironment, descriptor));
3535
}
@@ -45,8 +45,8 @@ public void EnvironmentConditionTest_EvaluateTrue(string name, string testExpres
4545

4646
var descriptor = new WorkerProfileConditionDescriptor();
4747
descriptor.Type = WorkerConstants.WorkerDescriptionProfileEnvironmentCondition;
48-
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionName] = name;
49-
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionExpression] = testExpression;
48+
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionName] = JsonSerializer.SerializeToElement(name);
49+
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionExpression] = JsonSerializer.SerializeToElement(testExpression);
5050

5151
var environmentCondition = new EnvironmentCondition(testLogger, _testEnvironment, descriptor);
5252

@@ -64,8 +64,8 @@ public void EnvironmentConditionTest_EvaluateFalse(string name, string testExpre
6464

6565
var descriptor = new WorkerProfileConditionDescriptor();
6666
descriptor.Type = WorkerConstants.WorkerDescriptionProfileEnvironmentCondition;
67-
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionName] = name;
68-
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionExpression] = testExpression;
67+
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionName] = JsonSerializer.SerializeToElement(name);
68+
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionExpression] = JsonSerializer.SerializeToElement(testExpression);
6969

7070
var environmentCondition = new EnvironmentCondition(testLogger, _testEnvironment, descriptor);
7171

test/WebJobs.Script.Tests/Workers/Profiles/HostPropertyConditionTests.cs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

44
using System.ComponentModel.DataAnnotations;
5+
using System.Linq.Expressions;
6+
using System.Text.Json;
57
using Microsoft.Azure.WebJobs.Script.Workers;
68
using Microsoft.Azure.WebJobs.Script.Workers.Profiles;
79
using Xunit;
@@ -29,8 +31,8 @@ public void HostPropertyConditionTest_ThrowsValidationException(string name, str
2931
var descriptor = new WorkerProfileConditionDescriptor();
3032
descriptor.Type = WorkerConstants.WorkerDescriptionProfileHostPropertyCondition;
3133

32-
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionName] = name;
33-
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionExpression] = expression;
34+
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionName] = JsonSerializer.SerializeToElement(name);
35+
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionExpression] = JsonSerializer.SerializeToElement(expression);
3436

3537
Assert.Throws<ValidationException>(() => new HostPropertyCondition(testLogger, _testSystemRuntimeInfo, descriptor));
3638
}
@@ -45,8 +47,8 @@ public void HostPropertyConditionTest_EvaluateTrue(string name, string testExpre
4547

4648
var descriptor = new WorkerProfileConditionDescriptor();
4749
descriptor.Type = WorkerConstants.WorkerDescriptionProfileHostPropertyCondition;
48-
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionName] = name;
49-
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionExpression] = testExpression;
50+
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionName] = JsonSerializer.SerializeToElement(name);
51+
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionExpression] = JsonSerializer.SerializeToElement(testExpression);
5052

5153
var hostPropertyCondition = new HostPropertyCondition(testLogger, _testSystemRuntimeInfo, descriptor);
5254

@@ -63,8 +65,8 @@ public void HostPropertyConditionTest_EvaluateFalse(string name, string testExpr
6365

6466
var descriptor = new WorkerProfileConditionDescriptor();
6567
descriptor.Type = WorkerConstants.WorkerDescriptionProfileHostPropertyCondition;
66-
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionName] = name;
67-
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionExpression] = testExpression;
68+
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionName] = JsonSerializer.SerializeToElement(name);
69+
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionExpression] = JsonSerializer.SerializeToElement(testExpression);
6870

6971
var hostPropertyCondition = new HostPropertyCondition(testLogger, _testSystemRuntimeInfo, descriptor);
7072

test/WebJobs.Script.Tests/Workers/Profiles/ProfilesTestUtilities.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
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.Text.Json;
45
using Microsoft.Azure.WebJobs.Script.Workers;
56
using Microsoft.Azure.WebJobs.Script.Workers.Profiles;
67
using Microsoft.Extensions.Logging;
@@ -29,8 +30,8 @@ public static EnvironmentCondition GetTestEnvironmentCondition(ILogger logger, T
2930
{
3031
var descriptor = new WorkerProfileConditionDescriptor();
3132
descriptor.Type = WorkerConstants.WorkerDescriptionProfileEnvironmentCondition;
32-
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionName] = name;
33-
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionExpression] = expression;
33+
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionName] = JsonSerializer.SerializeToElement(name);
34+
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionExpression] = JsonSerializer.SerializeToElement(expression);
3435

3536
return new EnvironmentCondition(logger, testEnvironment, descriptor);
3637
}
@@ -39,8 +40,8 @@ public static HostPropertyCondition GetTestHostPropertyCondition(ILogger logger,
3940
{
4041
var descriptor = new WorkerProfileConditionDescriptor();
4142
descriptor.Type = WorkerConstants.WorkerDescriptionProfileHostPropertyCondition;
42-
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionName] = name;
43-
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionExpression] = expression;
43+
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionName] = JsonSerializer.SerializeToElement(name);
44+
descriptor.Properties[WorkerConstants.WorkerDescriptionProfileConditionExpression] = JsonSerializer.SerializeToElement(expression);
4445

4546
return new HostPropertyCondition(logger, testSystemRuntimeInfo, descriptor);
4647
}

0 commit comments

Comments
 (0)