Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions GVFS/GVFS.Common/GVFSConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ public static class GitConfig

public const string ShowHydrationStatus = GVFSPrefix + "show-hydration-status";
public const bool ShowHydrationStatusDefault = false;

public const string MaxHttpConnectionsConfig = GVFSPrefix + "max-http-connections";
}

public static class LocalGVFSConfig
Expand Down
68 changes: 66 additions & 2 deletions GVFS/GVFS.Common/Http/HttpRequestor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public abstract class HttpRequestor : IDisposable
{
private static long requestCount = 0;
private static SemaphoreSlim availableConnections;
private static int connectionLimitConfigured = 0;

private readonly ProductInfoHeaderValue userAgentHeader;

Expand All @@ -34,8 +35,12 @@ static HttpRequestor()
using (var machineConfigLock = GetMachineConfigLock())
{
ServicePointManager.SecurityProtocol = ServicePointManager.SecurityProtocol | SecurityProtocolType.Tls12;
ServicePointManager.DefaultConnectionLimit = Environment.ProcessorCount;
availableConnections = new SemaphoreSlim(ServicePointManager.DefaultConnectionLimit);

// HTTP downloads are I/O-bound, not CPU-bound, so we default to
// 2x ProcessorCount. Can be overridden via gvfs.max-http-connections.
int connectionLimit = 2 * Environment.ProcessorCount;
ServicePointManager.DefaultConnectionLimit = connectionLimit;
availableConnections = new SemaphoreSlim(connectionLimit);
}
}

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

this.Tracer = tracer;

// On first instantiation, check git config for a custom connection limit.
// This runs before any requests are made (during mount initialization).
if (Interlocked.CompareExchange(ref connectionLimitConfigured, 1, 0) == 0)
{
TryApplyConnectionLimitFromConfig(tracer, enlistment);
}

HttpClientHandler httpClientHandler = new HttpClientHandler() { UseDefaultCredentials = true };

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

}

private static void TryApplyConnectionLimitFromConfig(ITracer tracer, Enlistment enlistment)
{
try
{
GitProcess.ConfigResult result = enlistment.CreateGitProcess().GetFromConfig(GVFSConstants.GitConfig.MaxHttpConnectionsConfig);
string error;
int configuredLimit;
if (!result.TryParseAsInt(0, 1, out configuredLimit, out error))
{
EventMetadata metadata = new EventMetadata();
metadata.Add("error", error);
tracer.RelatedWarning(metadata, "HttpRequestor: Invalid gvfs.max-http-connections config value, using default");
return;
}

if (configuredLimit > 0)
{
int currentLimit = ServicePointManager.DefaultConnectionLimit;
ServicePointManager.DefaultConnectionLimit = configuredLimit;

// Adjust the existing semaphore rather than replacing it, so any
// in-flight waiters release permits to the correct instance.
int delta = configuredLimit - currentLimit;
if (delta > 0)
{
for (int i = 0; i < delta; i++)
{
availableConnections.Release();
}
}
else if (delta < 0)
{
for (int i = 0; i < -delta; i++)
{
availableConnections.Wait();
}
}

EventMetadata metadata = new EventMetadata();
metadata.Add("configuredLimit", configuredLimit);
metadata.Add("previousLimit", currentLimit);
tracer.RelatedEvent(EventLevel.Informational, "HttpRequestor_ConnectionLimitConfigured", metadata);
}
}
catch (Exception e)
{
EventMetadata metadata = new EventMetadata();
metadata.Add("Exception", e.ToString());
tracer.RelatedWarning(metadata, "HttpRequestor: Failed to read gvfs.max-http-connections config, using default");
}
}

private static FileStream GetMachineConfigLock()
{
var machineConfigLocation = RuntimeEnvironment.SystemConfigurationFile;
Expand Down
Loading