diff --git a/ArchiSteamFarm/ArchiWebHandler.cs b/ArchiSteamFarm/ArchiWebHandler.cs index 937bc9e27..c67229445 100644 --- a/ArchiSteamFarm/ArchiWebHandler.cs +++ b/ArchiSteamFarm/ArchiWebHandler.cs @@ -53,10 +53,10 @@ namespace ArchiSteamFarm { private static readonly SemaphoreSlim InventorySemaphore = new SemaphoreSlim(1, 1); - private static readonly Dictionary WebLimitingSemaphores = new Dictionary(3) { - { SteamCommunityURL, new SemaphoreSlim(1, 1) }, - { SteamStoreURL, new SemaphoreSlim(1, 1) }, - { WebAPI.DefaultBaseAddress.Host, new SemaphoreSlim(1, 1) } + private static readonly Dictionary WebLimitingSemaphores = new Dictionary(3) { + { SteamCommunityURL, (new SemaphoreSlim(1, 1), new SemaphoreSlim(WebBrowser.MaxConnections, WebBrowser.MaxConnections)) }, + { SteamStoreURL, (new SemaphoreSlim(1, 1), new SemaphoreSlim(WebBrowser.MaxConnections, WebBrowser.MaxConnections)) }, + { WebAPI.DefaultBaseAddress.Host, (new SemaphoreSlim(1, 1), new SemaphoreSlim(WebBrowser.MaxConnections, WebBrowser.MaxConnections)) } }; private readonly SemaphoreSlim ApiKeySemaphore = new SemaphoreSlim(1, 1); @@ -1932,21 +1932,28 @@ namespace ArchiSteamFarm { return await function().ConfigureAwait(false); } - if (!WebLimitingSemaphores.TryGetValue(service, out SemaphoreSlim semaphore)) { + if (!WebLimitingSemaphores.TryGetValue(service, out (SemaphoreSlim RateLimitingSemaphore, SemaphoreSlim OpenConnectionsSemaphore) limiters)) { ASF.ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(service), service)); return await function().ConfigureAwait(false); } - await semaphore.WaitAsync().ConfigureAwait(false); - - Task task = function(); + // Sending a request affects both - number of requests as well as open connections + await limiters.RateLimitingSemaphore.WaitAsync().ConfigureAwait(false); + await limiters.OpenConnectionsSemaphore.WaitAsync().ConfigureAwait(false); + // We release rate-limiter semaphore regardless of our task completion, since we use that one only to guarantee rate-limiting of their creation Utilities.InBackground(async () => { - await Task.WhenAll(task, Task.Delay(Program.GlobalConfig.WebLimiterDelay)).ConfigureAwait(false); - semaphore.Release(); + await Task.Delay(Program.GlobalConfig.WebLimiterDelay).ConfigureAwait(false); + limiters.RateLimitingSemaphore.Release(); }); - return await task.ConfigureAwait(false); + // However, we release open connections semaphore only once we're indeed done sending a particular request + + try { + return await function().ConfigureAwait(false); + } finally { + limiters.OpenConnectionsSemaphore.Release(); + } } private enum ESession : byte { diff --git a/ArchiSteamFarm/WebBrowser.cs b/ArchiSteamFarm/WebBrowser.cs index ba175b4f9..59131c0c7 100644 --- a/ArchiSteamFarm/WebBrowser.cs +++ b/ArchiSteamFarm/WebBrowser.cs @@ -32,10 +32,10 @@ using Newtonsoft.Json; namespace ArchiSteamFarm { internal sealed class WebBrowser : IDisposable { + internal const byte MaxConnections = 10; // Defines maximum number of connections per ServicePoint. Be careful, as it also defines maximum number of sockets in CLOSE_WAIT state internal const byte MaxTries = 5; // Defines maximum number of recommended tries for a single request private const byte ExtendedTimeoutMultiplier = 10; // Defines multiplier of timeout for WebBrowsers dealing with huge data (ASF update) - private const byte MaxConnections = 10; // Defines maximum number of connections per ServicePoint. Be careful, as it also defines maximum number of sockets in CLOSE_WAIT state private const byte MaxIdleTime = 15; // Defines in seconds, how long socket is allowed to stay in CLOSE_WAIT state after there are no connections to it internal readonly CookieContainer CookieContainer = new CookieContainer();