Skip to content

Commit 43ff0ed

Browse files
committed
Increase HTTP connection pool size and add config override
The connection pool was previously sized to Environment.ProcessorCount (e.g., 8 on an 8-core machine). HTTP object downloads are I/O-bound, not CPU-bound, so CPU count is a poor proxy for optimal connection concurrency. Under burst workloads like git checkout, connections saturate almost instantly. Set the default to 2x ProcessorCount. This provides more headroom during burst object download scenarios without being overly aggressive for machines with multiple mounts. The pool size can be overridden via git config: git config gvfs.max-http-connections <value> Any positive integer is accepted. The semaphore is adjusted in-place (via Release/Wait) rather than replaced, so in-flight requests always release permits to the correct instance. Work item: 60167591 Assisted-by: Claude Opus 4.6 Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
1 parent 44b6d6b commit 43ff0ed

2 files changed

Lines changed: 68 additions & 2 deletions

File tree

GVFS/GVFS.Common/GVFSConstants.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ public static class GitConfig
4646

4747
public const string ShowHydrationStatus = GVFSPrefix + "show-hydration-status";
4848
public const bool ShowHydrationStatusDefault = false;
49+
50+
public const string MaxHttpConnectionsConfig = GVFSPrefix + "max-http-connections";
4951
}
5052

5153
public static class LocalGVFSConfig

GVFS/GVFS.Common/Http/HttpRequestor.cs

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public abstract class HttpRequestor : IDisposable
1919
{
2020
private static long requestCount = 0;
2121
private static SemaphoreSlim availableConnections;
22+
private static int connectionLimitConfigured = 0;
2223

2324
private readonly ProductInfoHeaderValue userAgentHeader;
2425

@@ -34,8 +35,12 @@ static HttpRequestor()
3435
using (var machineConfigLock = GetMachineConfigLock())
3536
{
3637
ServicePointManager.SecurityProtocol = ServicePointManager.SecurityProtocol | SecurityProtocolType.Tls12;
37-
ServicePointManager.DefaultConnectionLimit = Environment.ProcessorCount;
38-
availableConnections = new SemaphoreSlim(ServicePointManager.DefaultConnectionLimit);
38+
39+
// HTTP downloads are I/O-bound, not CPU-bound, so we default to
40+
// 2x ProcessorCount. Can be overridden via gvfs.max-http-connections.
41+
int connectionLimit = 2 * Environment.ProcessorCount;
42+
ServicePointManager.DefaultConnectionLimit = connectionLimit;
43+
availableConnections = new SemaphoreSlim(connectionLimit);
3944
}
4045
}
4146

@@ -47,6 +52,13 @@ protected HttpRequestor(ITracer tracer, RetryConfig retryConfig, Enlistment enli
4752

4853
this.Tracer = tracer;
4954

55+
// On first instantiation, check git config for a custom connection limit.
56+
// This runs before any requests are made (during mount initialization).
57+
if (Interlocked.CompareExchange(ref connectionLimitConfigured, 1, 0) == 0)
58+
{
59+
TryApplyConnectionLimitFromConfig(tracer, enlistment);
60+
}
61+
5062
HttpClientHandler httpClientHandler = new HttpClientHandler() { UseDefaultCredentials = true };
5163

5264
this.authentication.ConfigureHttpClientHandlerSslIfNeeded(this.Tracer, httpClientHandler, enlistment.CreateGitProcess());
@@ -337,6 +349,58 @@ private static bool TryGetResponseMessageFromHttpRequestException(HttpRequestExc
337349

338350
}
339351

352+
private static void TryApplyConnectionLimitFromConfig(ITracer tracer, Enlistment enlistment)
353+
{
354+
try
355+
{
356+
GitProcess.ConfigResult result = enlistment.CreateGitProcess().GetFromConfig(GVFSConstants.GitConfig.MaxHttpConnectionsConfig);
357+
string error;
358+
int configuredLimit;
359+
if (!result.TryParseAsInt(0, 1, out configuredLimit, out error))
360+
{
361+
EventMetadata metadata = new EventMetadata();
362+
metadata.Add("error", error);
363+
tracer.RelatedWarning(metadata, "HttpRequestor: Invalid gvfs.max-http-connections config value, using default");
364+
return;
365+
}
366+
367+
if (configuredLimit > 0)
368+
{
369+
int currentLimit = ServicePointManager.DefaultConnectionLimit;
370+
ServicePointManager.DefaultConnectionLimit = configuredLimit;
371+
372+
// Adjust the existing semaphore rather than replacing it, so any
373+
// in-flight waiters release permits to the correct instance.
374+
int delta = configuredLimit - currentLimit;
375+
if (delta > 0)
376+
{
377+
for (int i = 0; i < delta; i++)
378+
{
379+
availableConnections.Release();
380+
}
381+
}
382+
else if (delta < 0)
383+
{
384+
for (int i = 0; i < -delta; i++)
385+
{
386+
availableConnections.Wait();
387+
}
388+
}
389+
390+
EventMetadata metadata = new EventMetadata();
391+
metadata.Add("configuredLimit", configuredLimit);
392+
metadata.Add("previousLimit", currentLimit);
393+
tracer.RelatedEvent(EventLevel.Informational, "HttpRequestor_ConnectionLimitConfigured", metadata);
394+
}
395+
}
396+
catch (Exception e)
397+
{
398+
EventMetadata metadata = new EventMetadata();
399+
metadata.Add("Exception", e.ToString());
400+
tracer.RelatedWarning(metadata, "HttpRequestor: Failed to read gvfs.max-http-connections config, using default");
401+
}
402+
}
403+
340404
private static FileStream GetMachineConfigLock()
341405
{
342406
var machineConfigLocation = RuntimeEnvironment.SystemConfigurationFile;

0 commit comments

Comments
 (0)