Skip to content

Commit a61ce6c

Browse files
authored
Add metadata provider timeout host config (#10526)
1 parent 8090047 commit a61ce6c

File tree

8 files changed

+59
-12
lines changed

8 files changed

+59
-12
lines changed

release_notes.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,6 @@
88
- Improving console log handling during specialization (#10345)
99
- Update Node.js Worker Version to [3.10.1](https://github.com/Azure/azure-functions-nodejs-worker/releases/tag/v3.10.1)
1010
- Remove packages `Microsoft.Azure.Cosmos.Table` and `Microsoft.Azure.DocumentDB.Core` (#10503)
11+
- Implement host configuration property to allow configuration of the metadata provider timeout period (#10526)
12+
- The value can be set via `metadataProviderTimeout` in host.json and defaults to "00:00:30" (30 seconds).
13+
- For logic apps, unless configured via the host.json, the timeout is disabled by default.

src/WebJobs.Script/Config/ConfigurationSectionNames.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,6 @@ public static class ConfigurationSectionNames
2525
public const string SequentialJobHostRestart = JobHost + ":sequentialRestart";
2626
public const string SendCanceledInvocationsToWorker = "sendCanceledInvocationsToWorker";
2727
public const string TelemetryMode = "telemetryMode";
28+
public const string MetadataProviderTimeout = "metadataProviderTimeout";
2829
}
2930
}

src/WebJobs.Script/Config/HostJsonFileConfigurationSource.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ public class HostJsonFileConfigurationProvider : ConfigurationProvider
5151
{
5252
"version", "functionTimeout", "retry", "functions", "http", "watchDirectories", "watchFiles", "queues", "serviceBus",
5353
"eventHub", "singleton", "logging", "aggregator", "healthMonitor", "extensionBundle", "managedDependencies",
54-
"customHandler", "httpWorker", "extensions", "concurrency", ConfigurationSectionNames.SendCanceledInvocationsToWorker
54+
"customHandler", "httpWorker", "extensions", "concurrency", ConfigurationSectionNames.SendCanceledInvocationsToWorker,
55+
ConfigurationSectionNames.MetadataProviderTimeout
5556
};
5657

5758
private readonly HostJsonFileConfigurationSource _configurationSource;

src/WebJobs.Script/Config/ScriptJobHostOptions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,5 +135,10 @@ public string RootScriptPath
135135
/// Gets or sets the telemetry mode.
136136
/// </summary>
137137
internal TelemetryMode TelemetryMode { get; set; } = TelemetryMode.ApplicationInsights;
138+
139+
/// <summary>
140+
/// Gets or sets a value indicating the timeout duration for the function metadata provider.
141+
/// </summary>
142+
public TimeSpan MetadataProviderTimeout { get; set; } = TimeSpan.Zero;
138143
}
139144
}

src/WebJobs.Script/Config/ScriptJobHostOptionsSetup.cs

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

44
using System;
5+
using System.Threading;
56
using Microsoft.Azure.WebJobs.Script.Diagnostics.OpenTelemetry;
67
using Microsoft.Extensions.Configuration;
78
using Microsoft.Extensions.Options;
@@ -18,6 +19,7 @@ internal class ScriptJobHostOptionsSetup : IConfigureOptions<ScriptJobHostOption
1819
internal static readonly TimeSpan DefaultConsumptionFunctionTimeout = TimeSpan.FromMinutes(5);
1920
internal static readonly TimeSpan MaxFunctionTimeoutDynamic = TimeSpan.FromMinutes(10);
2021
internal static readonly TimeSpan DefaultFunctionTimeout = TimeSpan.FromMinutes(30);
22+
internal static readonly TimeSpan DefaultMetadataProviderTimeout = TimeSpan.FromSeconds(30);
2123

2224
public ScriptJobHostOptionsSetup(IConfiguration configuration, IEnvironment environment, IOptions<ScriptApplicationHostOptions> applicationHostOptions)
2325
{
@@ -59,6 +61,12 @@ public void Configure(ScriptJobHostOptions options)
5961
// FunctionTimeout
6062
ConfigureFunctionTimeout(options);
6163

64+
if (options.MetadataProviderTimeout == TimeSpan.Zero)
65+
{
66+
// If the timeout value is not configured and we are running in a logic app, we want to disable the timeout
67+
options.MetadataProviderTimeout = _environment.IsLogicApp() ? Timeout.InfiniteTimeSpan : DefaultMetadataProviderTimeout;
68+
}
69+
6270
// If we have a read only file system, override any configuration and
6371
// disable file watching
6472
if (_applicationHostOptions.Value.IsFileSystemReadOnly)
@@ -88,7 +96,7 @@ public void Configure(ScriptJobHostOptions options)
8896

8997
private void ConfigureFunctionTimeout(ScriptJobHostOptions options)
9098
{
91-
if (options.FunctionTimeout == null)
99+
if (options.FunctionTimeout is null)
92100
{
93101
options.FunctionTimeout = (_environment.IsConsumptionSku() && !_environment.IsFlexConsumptionSku()) ? DefaultConsumptionFunctionTimeout : DefaultFunctionTimeout;
94102
}

src/WebJobs.Script/Host/FunctionMetadataManager.cs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Collections.Generic;
77
using System.Collections.Immutable;
88
using System.Linq;
9+
using System.Threading;
910
using System.Threading.Tasks;
1011
using Microsoft.Azure.WebJobs.Logging;
1112
using Microsoft.Azure.WebJobs.Script.Description;
@@ -21,9 +22,8 @@ namespace Microsoft.Azure.WebJobs.Script
2122
{
2223
public class FunctionMetadataManager : IFunctionMetadataManager
2324
{
24-
private const string FunctionConfigurationErrorMessage = "Unable to determine the primary function script.Make sure atleast one script file is present.Try renaming your entry point script to 'run' or alternatively you can specify the name of the entry point script explicitly by adding a 'scriptFile' property to your function metadata.";
25+
private const string FunctionConfigurationErrorMessage = "Unable to determine the primary function script. Make sure at least one script file is present. Try renaming your entry point script to 'run' or alternatively you can specify the name of the entry point script explicitly by adding a 'scriptFile' property to your function metadata.";
2526
private const string MetadataProviderName = "Custom";
26-
private const int DefaultMetadataProviderTimeoutInSeconds = 30;
2727
private readonly IServiceProvider _serviceProvider;
2828
private IFunctionMetadataProvider _functionMetadataProvider;
2929
private bool _isHttpWorker;
@@ -57,9 +57,6 @@ public FunctionMetadataManager(IOptions<ScriptJobHostOptions> scriptOptions, IFu
5757
};
5858
}
5959

60-
// Property is settable for testing purposes.
61-
internal int MetadataProviderTimeoutInSeconds { get; set; } = DefaultMetadataProviderTimeoutInSeconds;
62-
6360
public ImmutableDictionary<string, ImmutableArray<string>> Errors { get; private set; }
6461

6562
public bool TryGetFunctionMetadata(string functionName, out FunctionMetadata functionMetadata, bool forceRefresh)
@@ -221,11 +218,12 @@ private void AddMetadataFromCustomProviders(IEnumerable<IFunctionProvider> funct
221218
_logger.ReadingFunctionMetadataFromProvider(MetadataProviderName);
222219

223220
var functionProviderTasks = new List<Task<ImmutableArray<FunctionMetadata>>>();
221+
var metadataProviderTimeout = _scriptOptions.Value.MetadataProviderTimeout;
224222

225223
foreach (var functionProvider in functionProviders)
226224
{
227225
var getFunctionMetadataFromProviderTask = functionProvider.GetFunctionMetadataAsync();
228-
var delayTask = Task.Delay(TimeSpan.FromSeconds(MetadataProviderTimeoutInSeconds));
226+
var delayTask = Task.Delay(metadataProviderTimeout);
229227

230228
var completedTask = Task.WhenAny(getFunctionMetadataFromProviderTask, delayTask).ContinueWith(t =>
231229
{
@@ -235,7 +233,7 @@ private void AddMetadataFromCustomProviders(IEnumerable<IFunctionProvider> funct
235233
}
236234

237235
// Timeout case.
238-
throw new TimeoutException($"Timeout occurred while retrieving metadata from provider '{functionProvider.GetType().FullName}'. The operation exceeded the configured timeout of {MetadataProviderTimeoutInSeconds} seconds.");
236+
throw new TimeoutException($"Timeout occurred while retrieving metadata from provider '{functionProvider.GetType().FullName}'. The operation exceeded the configured timeout of {metadataProviderTimeout.TotalSeconds} seconds.");
239237
});
240238

241239
functionProviderTasks.Add(completedTask);

test/WebJobs.Script.Tests/Configuration/ScriptJobHostOptionsSetupTests.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,38 @@ public void Configure_WithConfiguration_AppliesTelemetryMode(string setting, str
334334
Assert.Equal(expectedMode, options.TelemetryMode.ToString());
335335
}
336336

337+
[Fact]
338+
public void Configure_MetadataProviderTimeout_ValidateDefaultTimeoutValue()
339+
{
340+
var options = GetConfiguredOptions(new Dictionary<string, string>());
341+
342+
Assert.Equal(TimeSpan.FromSeconds(30), options.MetadataProviderTimeout);
343+
}
344+
345+
[Fact]
346+
public void Configure_MetadataProviderTimeout_IsLogicApp_SetTimeoutToInfinite()
347+
{
348+
var environment = new TestEnvironment();
349+
environment.SetEnvironmentVariable(EnvironmentSettingNames.AppKind, "workflowapp");
350+
351+
var options = GetConfiguredOptions(new Dictionary<string, string>(), environment);
352+
353+
Assert.Equal(TimeSpan.FromMilliseconds(-1), options.MetadataProviderTimeout);
354+
}
355+
356+
[Fact]
357+
public void Configure_MetadataProviderTimeout_AppliesConfiguredTimeoutValue()
358+
{
359+
var settings = new Dictionary<string, string>
360+
{
361+
{ ConfigurationPath.Combine(ConfigurationSectionNames.JobHost, "metadataProviderTimeout"), "00:00:43" }
362+
};
363+
364+
var options = GetConfiguredOptions(settings);
365+
366+
Assert.Equal(TimeSpan.FromSeconds(43), options.MetadataProviderTimeout);
367+
}
368+
337369
private ScriptJobHostOptions GetConfiguredOptions(Dictionary<string, string> settings, IEnvironment environment = null)
338370
{
339371
ScriptJobHostOptionsSetup setup = CreateSetupWithConfiguration(settings, environment);

test/WebJobs.Script.Tests/FunctionMetadataManagerTests.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,12 @@
1717
using Microsoft.WebJobs.Script.Tests;
1818
using Moq;
1919
using Xunit;
20-
using static Microsoft.Azure.AppService.Proxy.Common.Constants.WellKnownHttpHeaders;
2120

2221
namespace Microsoft.Azure.WebJobs.Script.Tests
2322
{
2423
public class FunctionMetadataManagerTests
2524
{
26-
private const string _expectedErrorMessage = "Unable to determine the primary function script.Make sure atleast one script file is present.Try renaming your entry point script to 'run' or alternatively you can specify the name of the entry point script explicitly by adding a 'scriptFile' property to your function metadata.";
25+
private const string _expectedErrorMessage = "Unable to determine the primary function script. Make sure at least one script file is present. Try renaming your entry point script to 'run' or alternatively you can specify the name of the entry point script explicitly by adding a 'scriptFile' property to your function metadata.";
2726
private ScriptJobHostOptions _scriptJobHostOptions = new ScriptJobHostOptions();
2827
private Mock<IFunctionMetadataProvider> _mockFunctionMetadataProvider;
2928
private FunctionMetadataManager _testFunctionMetadataManager;
@@ -256,7 +255,7 @@ public void FunctionMetadataManager_LoadFunctionMetadata_Throws_WhenFunctionProv
256255
mockFunctionMetadataProvider.Object, new List<IFunctionProvider>() { goodFunctionMetadataProvider.Object, badFunctionMetadataProvider.Object }, new OptionsWrapper<HttpWorkerOptions>(_defaultHttpWorkerOptions), loggerFactory, new TestOptionsMonitor<LanguageWorkerOptions>(TestHelpers.GetTestLanguageWorkerOptions()));
257256

258257
// Set the timeout to 1 second for the test.
259-
testFunctionMetadataManager.MetadataProviderTimeoutInSeconds = 1;
258+
_scriptJobHostOptions.MetadataProviderTimeout = TimeSpan.FromSeconds(1);
260259

261260
var exception = Assert.Throws<TimeoutException>(() => testFunctionMetadataManager.LoadFunctionMetadata());
262261
Assert.Contains($"Timeout occurred while retrieving metadata from provider '{badFunctionMetadataProvider.Object.GetType().FullName}'. The operation exceeded the configured timeout of 1 seconds.", exception.Message);

0 commit comments

Comments
 (0)