Skip to content

Commit af29fd6

Browse files
committed
Merged PR 742087: Remove version restriction from blob options and centralize HttpClient
Centralizes the storage options further to make sure that all across cache we're not going over the connection limit. Also removes the version limit we're placing on the storage sdk because of our old version of azurite. This also fixes a bug when using managed identities that would cause the user to not utilize the singleton HttpClient, potentially running into the FD limit
1 parent 9ff85be commit af29fd6

File tree

6 files changed

+214
-207
lines changed

6 files changed

+214
-207
lines changed

Public/Src/Cache/ContentStore/Distributed/Blob/ShardedBlobCacheTopology.cs

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,6 @@ public record Configuration(
4343
private readonly BlobCacheContainerName[] _containers;
4444
private readonly IShardingScheme<int, BlobCacheStorageAccountName> _scheme;
4545

46-
/// <remarks>
47-
/// We will reuse an HttpClient for the transport backing the blob clients. HttpClient is meant to be reused anyway
48-
/// (https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient?view=net-7.0#instancing)
49-
/// but crucially we have the need to configure the amount of open connections: when using the defaults,
50-
/// the number of connections is unbounded, and we have observed builds where there end up being tens of thousands
51-
/// of open sockets, which can (and did) hit the per-process limit of open files, crashing the engine.
52-
/// </summary>
53-
private readonly HttpClient _httpClient;
54-
5546
private readonly record struct Location(BlobCacheStorageAccountName Account, BlobCacheContainerName Container);
5647

5748
/// <summary>
@@ -75,19 +66,6 @@ public ShardedBlobCacheTopology(Configuration configuration)
7566

7667
_scheme = _configuration.ShardingScheme.Create();
7768
_containers = GenerateContainerNames(_configuration.Universe, _configuration.Namespace, _configuration.ShardingScheme);
78-
79-
_httpClient = new HttpClient(
80-
new HttpClientHandler()
81-
{
82-
// If left unbounded, we have observed spikes of >65k open sockets (at which point we hit
83-
// the OS limit of open files for the process - on Linux, where sockets count as files).
84-
// Running builds where we limit this value all the way down to 100 didn't see
85-
// any noticeable performance impact, so 30k shouldn't pose a problem.
86-
// The configurable limit is per-client and per-server, but because we will reuse this HttpClient
87-
// for all BlobClients and the 'server' (blob storage endpoint) is also always the same,
88-
// we are effectively limiting the number of open connections in general.
89-
MaxConnectionsPerServer = 30_000
90-
});
9169
}
9270

9371
internal static BlobCacheContainerName[] GenerateContainerNames(string universe, string @namespace, ShardingScheme scheme)
@@ -207,13 +185,7 @@ private Task<Result<BlobContainerClient>> CreateClientAsync(OperationContext con
207185
async context =>
208186
{
209187
var credentials = await _configuration.SecretsProvider.RetrieveBlobCredentialsAsync(context, account);
210-
211-
BlobClientOptions blobClientOptions = new(BlobClientOptions.ServiceVersion.V2021_02_12)
212-
{
213-
Transport = new HttpClientTransport(_httpClient)
214-
};
215-
216-
var containerClient = credentials.CreateContainerClient(container.ContainerName, blobClientOptions);
188+
var containerClient = credentials.CreateContainerClient(container.ContainerName);
217189

218190
try
219191
{

Public/Src/Cache/ContentStore/DistributedTest/Redis/AzuriteStorageProcess.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ private void Start(List<string> accounts = null)
264264
_fileSystem.CreateDirectory(storageServerWorkspacePath);
265265
_portNumber = PortExtensions.GetNextAvailablePort();
266266

267-
var args = $"--blobPort {_portNumber} --location {storageServerWorkspacePath} --skipApiVersionCheck";
267+
var args = $"--blobPort {_portNumber} --location {storageServerWorkspacePath} --skipApiVersionCheck --silent --loose";
268268
_logger.Debug($"Running cmd=[{storageServerPath} {args}]");
269269

270270
_process = new ProcessUtility(storageServerPath, args, createNoWindow: true, workingDirectory: Path.GetDirectoryName(storageServerPath), environment: CreateEnvironment(accounts));
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System.Net.Http;
5+
using Azure.Core.Pipeline;
6+
using Azure.Storage.Blobs;
7+
8+
namespace BuildXL.Cache.ContentStore.Interfaces.Auth;
9+
10+
/// <summary>
11+
/// Centralized point for common methods pertaining to Azure Storage authentication.
12+
/// </summary>
13+
internal static class BlobClientOptionsFactory
14+
{
15+
/// <summary>
16+
/// We will reuse an HttpClient for the transport backing the blob clients. HttpClient is meant to be reused anyway
17+
/// (https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient?view=net-7.0#instancing)
18+
/// but crucially we have the need to configure the amount of open connections: when using the defaults,
19+
/// the number of connections is unbounded, and we have observed builds where there end up being tens of thousands
20+
/// of open sockets, which can (and did) hit the per-process limit of open files, crashing the engine.
21+
/// </summary>
22+
private static readonly HttpClient HttpClient = new(
23+
new HttpClientHandler
24+
{
25+
// If left unbounded, we have observed spikes of >65k open sockets (at which point we hit
26+
// the OS limit of open files for the process - on Linux, where sockets count as files).
27+
// Running builds where we limit this value all the way down to 100 didn't see
28+
// any noticeable performance impact, so 30k shouldn't pose a problem.
29+
// The configurable limit is per-client and per-server, but because we will reuse this HttpClient
30+
// for all BlobClients, we are effectively limiting the number of open connections in general.
31+
MaxConnectionsPerServer = 30_000
32+
});
33+
34+
private static readonly HttpClientTransport Transport = new(HttpClient);
35+
36+
/// <summary>
37+
/// Centralized point to override options for all blob clients created by the application.
38+
/// </summary>
39+
public static BlobClientOptions CreateOrOverride(BlobClientOptions? baseline)
40+
{
41+
baseline ??= new BlobClientOptions();
42+
baseline.Transport = Transport;
43+
return baseline;
44+
}
45+
}

Public/Src/Cache/ContentStore/Interfaces/Auth/ManagedIdentityAzureStorageCredentials.cs

Lines changed: 30 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,46 +7,45 @@
77
using Azure.Storage.Blobs;
88
using Azure.Storage.Blobs.ChangeFeed;
99

10-
namespace BuildXL.Cache.ContentStore.Interfaces.Auth
10+
namespace BuildXL.Cache.ContentStore.Interfaces.Auth;
11+
12+
/// <nodoc />
13+
public class ManagedIdentityAzureStorageCredentials : IAzureStorageCredentials
1114
{
15+
private static readonly Regex StorageAccountNameRegex = new("https://(?<accountName>[^\\.]+)\\.blob\\..*", RegexOptions.Compiled | RegexOptions.IgnoreCase);
16+
17+
private readonly ManagedIdentityCredential _credentials;
18+
private readonly Uri _blobUri;
19+
1220
/// <nodoc />
13-
public class ManagedIdentityAzureStorageCredentials : IAzureStorageCredentials
21+
public ManagedIdentityAzureStorageCredentials(string managedIdentityClientId, Uri blobUri)
1422
{
15-
private static readonly Regex StorageAccountNameRegex = new("https://(?<accountName>[^\\.]+)\\.blob\\..*", RegexOptions.Compiled | RegexOptions.IgnoreCase);
23+
_credentials = new ManagedIdentityCredential(managedIdentityClientId);
24+
_blobUri = blobUri;
25+
}
1626

17-
private readonly ManagedIdentityCredential _credentials;
18-
private readonly Uri _blobUri;
27+
/// <inheritdoc />
28+
public string GetAccountName()
29+
{
30+
var match = StorageAccountNameRegex.Match(_blobUri.ToString());
1931

20-
/// <nodoc />
21-
public ManagedIdentityAzureStorageCredentials(string managedIdentityClientId, Uri blobUri)
32+
if (match.Success)
2233
{
23-
_credentials = new ManagedIdentityCredential(managedIdentityClientId);
24-
_blobUri = blobUri;
34+
return match.Groups["accountName"].Value;
2535
}
2636

27-
/// <inheritdoc />
28-
public string GetAccountName()
29-
{
30-
var match = StorageAccountNameRegex.Match(_blobUri.ToString());
31-
32-
if (match.Success)
33-
{
34-
return match.Groups["accountName"].Value;
35-
}
36-
37-
throw new InvalidOperationException($"The provided URI is malformed and the account name could not be retrieved.");
38-
}
37+
throw new InvalidOperationException($"The provided URI is malformed and the account name could not be retrieved.");
38+
}
3939

40-
/// <inheritdoc />
41-
public BlobServiceClient CreateBlobServiceClient(BlobClientOptions? blobClientOptions = null)
42-
=> new(_blobUri, _credentials, blobClientOptions ?? new(BlobClientOptions.ServiceVersion.V2021_02_12));
40+
/// <inheritdoc />
41+
public BlobServiceClient CreateBlobServiceClient(BlobClientOptions? blobClientOptions = null)
42+
=> new(_blobUri, _credentials, BlobClientOptionsFactory.CreateOrOverride(blobClientOptions));
4343

44-
/// <inheritdoc />
45-
public BlobContainerClient CreateContainerClient(string containerName, BlobClientOptions? blobClientOptions = null)
46-
=> new(new Uri(_blobUri, containerName), _credentials, blobClientOptions ?? new(BlobClientOptions.ServiceVersion.V2021_02_12));
44+
/// <inheritdoc />
45+
public BlobContainerClient CreateContainerClient(string containerName, BlobClientOptions? blobClientOptions = null)
46+
=> new(new Uri(_blobUri, containerName), _credentials, BlobClientOptionsFactory.CreateOrOverride(blobClientOptions));
4747

48-
/// <inheritdoc />
49-
public BlobChangeFeedClient CreateBlobChangeFeedClient(BlobClientOptions? blobClientOptions = null, BlobChangeFeedClientOptions? changeFeedClientOptions = null)
50-
=> new BlobChangeFeedClient(_blobUri, _credentials, blobClientOptions ?? new(BlobClientOptions.ServiceVersion.V2021_02_12), changeFeedClientOptions ?? new());
51-
}
48+
/// <inheritdoc />
49+
public BlobChangeFeedClient CreateBlobChangeFeedClient(BlobClientOptions? blobClientOptions = null, BlobChangeFeedClientOptions? changeFeedClientOptions = null)
50+
=> new BlobChangeFeedClient(_blobUri, _credentials, BlobClientOptionsFactory.CreateOrOverride(blobClientOptions), changeFeedClientOptions ?? new());
5251
}

0 commit comments

Comments
 (0)