Skip to content

Commit 86b9cb4

Browse files
committed
setting MaxConnectionsPerServer for CloudTableClient usage in Windows Consumption
1 parent 261eee4 commit 86b9cb4

File tree

8 files changed

+126
-14
lines changed

8 files changed

+126
-14
lines changed

src/WebJobs.Script.WebHost/Diagnostics/FunctionInstanceLogCollectorProvider.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using Microsoft.Azure.WebJobs.Host.Executors;
66
using Microsoft.Azure.WebJobs.Host.Loggers;
77
using Microsoft.Azure.WebJobs.Script.Diagnostics;
8+
using Microsoft.Azure.WebJobs.Script.WebHost.Storage;
89
using Microsoft.Extensions.Configuration;
910
using Microsoft.Extensions.Logging;
1011

@@ -17,20 +18,22 @@ internal class FunctionInstanceLogCollectorProvider : IEventCollectorProvider
1718
private readonly IHostIdProvider _hostIdProvider;
1819
private readonly IConfiguration _configuration;
1920
private readonly ILoggerFactory _loggerFactory;
21+
private readonly IDelegatingHandlerProvider _delegatingHandlerProvider;
2022

21-
public FunctionInstanceLogCollectorProvider(IFunctionMetadataManager metadataManager,
22-
IMetricsLogger metrics, IHostIdProvider hostIdProvider, IConfiguration configuration, ILoggerFactory loggerFactory)
23+
public FunctionInstanceLogCollectorProvider(IFunctionMetadataManager metadataManager, IMetricsLogger metrics,
24+
IHostIdProvider hostIdProvider, IConfiguration configuration, ILoggerFactory loggerFactory, IDelegatingHandlerProvider delegatingHandlerProvider)
2325
{
2426
_metadataManager = metadataManager ?? throw new ArgumentNullException(nameof(metadataManager));
2527
_metrics = metrics ?? throw new ArgumentNullException(nameof(metrics));
2628
_hostIdProvider = hostIdProvider ?? throw new ArgumentNullException(nameof(hostIdProvider));
2729
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
2830
_loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
31+
_delegatingHandlerProvider = delegatingHandlerProvider ?? throw new ArgumentNullException(nameof(delegatingHandlerProvider));
2932
}
3033

3134
public IAsyncCollector<FunctionInstanceLogEntry> Create()
3235
{
33-
return new FunctionInstanceLogger(_metadataManager, _metrics, _hostIdProvider, _configuration, _loggerFactory);
36+
return new FunctionInstanceLogger(_metadataManager, _metrics, _hostIdProvider, _configuration, _loggerFactory, _delegatingHandlerProvider);
3437
}
3538
}
3639
}

src/WebJobs.Script.WebHost/Diagnostics/FunctionInstanceLogger.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using Microsoft.Azure.WebJobs.Logging;
1313
using Microsoft.Azure.WebJobs.Script.Description;
1414
using Microsoft.Azure.WebJobs.Script.Diagnostics;
15+
using Microsoft.Azure.WebJobs.Script.WebHost.Storage;
1516
using Microsoft.Extensions.Configuration;
1617
using Microsoft.Extensions.Logging;
1718

@@ -31,7 +32,8 @@ public FunctionInstanceLogger(
3132
IMetricsLogger metrics,
3233
IHostIdProvider hostIdProvider,
3334
IConfiguration configuration,
34-
ILoggerFactory loggerFactory)
35+
ILoggerFactory loggerFactory,
36+
IDelegatingHandlerProvider delegatingHandlerProvider)
3537
: this(metadataManager, metrics)
3638
{
3739
if (hostIdProvider == null)
@@ -49,11 +51,19 @@ public FunctionInstanceLogger(
4951
throw new ArgumentNullException(nameof(loggerFactory));
5052
}
5153

54+
if (delegatingHandlerProvider == null)
55+
{
56+
throw new ArgumentNullException(nameof(delegatingHandlerProvider));
57+
}
58+
5259
string accountConnectionString = configuration.GetWebJobsConnectionString(ConnectionStringNames.Dashboard);
5360
if (accountConnectionString != null)
5461
{
5562
CloudStorageAccount account = CloudStorageAccount.Parse(accountConnectionString);
56-
var client = account.CreateCloudTableClient();
63+
var restConfig = new RestExecutorConfiguration { DelegatingHandler = delegatingHandlerProvider.Create() };
64+
var tableClientConfig = new TableClientConfiguration { RestExecutorConfiguration = restConfig };
65+
66+
var client = new CloudTableClient(account.TableStorageUri, account.Credentials, tableClientConfig);
5767
var tableProvider = LogFactory.NewLogTableProvider(client);
5868

5969
ILogger logger = loggerFactory.CreateLogger(ScriptConstants.LogCategoryHostGeneral);

src/WebJobs.Script.WebHost/Scale/TableStorageScaleMetricsRepository.cs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using Microsoft.Azure.WebJobs.Host.Scale;
1313
using Microsoft.Azure.WebJobs.Script.Scale;
1414
using Microsoft.Azure.WebJobs.Script.WebHost.Scale;
15+
using Microsoft.Azure.WebJobs.Script.WebHost.Storage;
1516
using Microsoft.Extensions.Configuration;
1617
using Microsoft.Extensions.Logging;
1718
using Microsoft.Extensions.Options;
@@ -31,20 +32,23 @@ public class TableStorageScaleMetricsRepository : IScaleMetricsRepository
3132
private readonly ScaleOptions _scaleOptions;
3233
private readonly ILogger _logger;
3334
private readonly int _tableCreationRetries;
35+
private readonly IDelegatingHandlerProvider _delegatingHandlerProvider;
3436
private CloudTableClient _tableClient;
3537

36-
public TableStorageScaleMetricsRepository(IConfiguration configuration, IHostIdProvider hostIdProvider, IOptions<ScaleOptions> scaleOptions, ILoggerFactory loggerFactory)
37-
: this(configuration, hostIdProvider, scaleOptions, loggerFactory, DefaultTableCreationRetries)
38+
public TableStorageScaleMetricsRepository(IConfiguration configuration, IHostIdProvider hostIdProvider, IOptions<ScaleOptions> scaleOptions, ILoggerFactory loggerFactory, IEnvironment environment)
39+
: this(configuration, hostIdProvider, scaleOptions, loggerFactory, DefaultTableCreationRetries, new DefaultDelegatingHandlerProvider(environment))
3840
{
3941
}
4042

41-
internal TableStorageScaleMetricsRepository(IConfiguration configuration, IHostIdProvider hostIdProvider, IOptions<ScaleOptions> scaleOptions, ILoggerFactory loggerFactory, int tableCreationRetries)
43+
internal TableStorageScaleMetricsRepository(IConfiguration configuration, IHostIdProvider hostIdProvider, IOptions<ScaleOptions> scaleOptions, ILoggerFactory loggerFactory,
44+
int tableCreationRetries, IDelegatingHandlerProvider delegatingHandlerProvider)
4245
{
4346
_configuration = configuration;
4447
_hostIdProvider = hostIdProvider;
4548
_scaleOptions = scaleOptions.Value;
4649
_logger = loggerFactory.CreateLogger<TableStorageScaleMetricsRepository>();
4750
_tableCreationRetries = tableCreationRetries;
51+
_delegatingHandlerProvider = delegatingHandlerProvider ?? throw new ArgumentNullException(nameof(delegatingHandlerProvider));
4852
}
4953

5054
internal CloudTableClient TableClient
@@ -54,11 +58,13 @@ internal CloudTableClient TableClient
5458
if (_tableClient == null)
5559
{
5660
string storageConnectionString = _configuration.GetWebJobsConnectionString(ConnectionStringNames.Storage);
57-
CloudStorageAccount account = null;
5861
if (!string.IsNullOrEmpty(storageConnectionString) &&
59-
CloudStorageAccount.TryParse(storageConnectionString, out account))
62+
CloudStorageAccount.TryParse(storageConnectionString, out CloudStorageAccount account))
6063
{
61-
_tableClient = account.CreateCloudTableClient();
64+
var restConfig = new RestExecutorConfiguration { DelegatingHandler = _delegatingHandlerProvider.Create() };
65+
var tableClientConfig = new TableClientConfiguration { RestExecutorConfiguration = restConfig };
66+
67+
_tableClient = new CloudTableClient(account.TableStorageUri, account.Credentials, tableClientConfig);
6268
}
6369
else
6470
{
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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.Net.Http;
6+
7+
namespace Microsoft.Azure.WebJobs.Script.WebHost.Storage
8+
{
9+
internal class DefaultDelegatingHandlerProvider : IDelegatingHandlerProvider
10+
{
11+
private readonly IEnvironment _environment;
12+
13+
public DefaultDelegatingHandlerProvider(IEnvironment environment)
14+
{
15+
_environment = environment ?? throw new ArgumentNullException(nameof(environment));
16+
}
17+
18+
public DelegatingHandler Create()
19+
{
20+
// The DelegatingHandler only applies to the Antares Sandbox where connections are limited.
21+
return _environment.IsWindowsConsumption() ? new WebJobsStorageDelegatingHandler() : null;
22+
}
23+
}
24+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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.Net.Http;
5+
6+
namespace Microsoft.Azure.WebJobs.Script.WebHost.Storage
7+
{
8+
/// <summary>
9+
/// Represents a type used to create a <see cref="DelegatingHandler"/> to be used by the WebJobs Azure Storage clients.
10+
/// </summary>
11+
public interface IDelegatingHandlerProvider
12+
{
13+
/// <summary>
14+
/// Creates a new <see cref="DelegatingHandler"/>.
15+
/// </summary>
16+
/// <returns>The <see cref="DelegatingHandler"/>.</returns>
17+
DelegatingHandler Create();
18+
}
19+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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.Net.Http;
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
9+
namespace Microsoft.Azure.WebJobs.Script.WebHost.Storage
10+
{
11+
internal class WebJobsStorageDelegatingHandler : DelegatingHandler
12+
{
13+
private const int MaxConnectionsPerServer = 50;
14+
private bool _isInnerHandlerConfigured = false;
15+
16+
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
17+
{
18+
if (!_isInnerHandlerConfigured)
19+
{
20+
InitializeInnerHandler();
21+
22+
_isInnerHandlerConfigured = true;
23+
}
24+
25+
return base.SendAsync(request, cancellationToken);
26+
}
27+
28+
private void InitializeInnerHandler()
29+
{
30+
try
31+
{
32+
// Storage ensures that the inner handler is an HttpClientHandler
33+
if (!(InnerHandler is HttpClientHandler innerHandler))
34+
{
35+
throw new InvalidOperationException("The inner handler has not been initialized by the Storage SDK.");
36+
}
37+
38+
innerHandler.MaxConnectionsPerServer = MaxConnectionsPerServer;
39+
}
40+
catch (InvalidOperationException)
41+
{
42+
// This exception is thrown if there's a race and this was set by another thread.
43+
}
44+
}
45+
}
46+
}

src/WebJobs.Script.WebHost/WebScriptHostBuilderExtension.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
using Microsoft.Azure.WebJobs.Script.WebHost.Diagnostics;
1717
using Microsoft.Azure.WebJobs.Script.WebHost.Management;
1818
using Microsoft.Azure.WebJobs.Script.WebHost.Middleware;
19+
using Microsoft.Azure.WebJobs.Script.WebHost.Storage;
1920
using Microsoft.Extensions.DependencyInjection;
2021
using Microsoft.Extensions.DependencyInjection.Extensions;
2122
using Microsoft.Extensions.Hosting;
@@ -109,6 +110,8 @@ public static IHostBuilder AddWebScriptHost(this IHostBuilder builder, IServiceP
109110
services.AddSingleton<IHostIdProvider>(provider);
110111
}
111112

113+
services.AddSingleton<IDelegatingHandlerProvider, DefaultDelegatingHandlerProvider>();
114+
112115
// Logging and diagnostics
113116
services.AddSingleton<IMetricsLogger>(a => new NonDisposableMetricsLogger(metricsLogger));
114117
services.AddSingleton<IEventCollectorProvider, FunctionInstanceLogCollectorProvider>();

test/WebJobs.Script.Tests.Integration/Scale/TableStorageScaleMetricsRepositoryTests.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@
66
using System.Linq;
77
using System.Threading;
88
using System.Threading.Tasks;
9+
using Microsoft.Azure.Cosmos.Table;
910
using Microsoft.Azure.WebJobs.Host.Executors;
1011
using Microsoft.Azure.WebJobs.Host.Scale;
1112
using Microsoft.Azure.WebJobs.Script.WebHost;
13+
using Microsoft.Azure.WebJobs.Script.WebHost.Storage;
1214
using Microsoft.Extensions.Configuration;
1315
using Microsoft.Extensions.Logging;
1416
using Microsoft.Extensions.Options;
1517
using Microsoft.WebJobs.Script.Tests;
16-
using Microsoft.Azure.Cosmos.Table;
1718
using Moq;
1819
using Xunit;
1920

@@ -42,7 +43,7 @@ public TableStorageScaleMetricsRepositoryTests()
4243
loggerFactory.AddProvider(_loggerProvider);
4344

4445
// Allow for up to 30 seconds of creation retries for tests due to slow table deletes
45-
_repository = new TableStorageScaleMetricsRepository(configuration, _hostIdProviderMock.Object, new OptionsWrapper<ScaleOptions>(_scaleOptions), loggerFactory, 60);
46+
_repository = new TableStorageScaleMetricsRepository(configuration, _hostIdProviderMock.Object, new OptionsWrapper<ScaleOptions>(_scaleOptions), loggerFactory, 60, new DefaultDelegatingHandlerProvider(new TestEnvironment()));
4647

4748
EmptyMetricsTableAsync().GetAwaiter().GetResult();
4849
}
@@ -56,7 +57,7 @@ public async Task InvalidStorageConnection_Handled()
5657
var options = new ScaleOptions();
5758
ILoggerFactory loggerFactory = new LoggerFactory();
5859
loggerFactory.AddProvider(_loggerProvider);
59-
var localRepository = new TableStorageScaleMetricsRepository(configuration, _hostIdProviderMock.Object, new OptionsWrapper<ScaleOptions>(options), loggerFactory);
60+
var localRepository = new TableStorageScaleMetricsRepository(configuration, _hostIdProviderMock.Object, new OptionsWrapper<ScaleOptions>(options), loggerFactory, new TestEnvironment());
6061

6162
var monitor1 = new TestScaleMonitor1();
6263
var monitor2 = new TestScaleMonitor2();

0 commit comments

Comments
 (0)