From 451680c06bf2ebb9665076b7c8016a95fb320748 Mon Sep 17 00:00:00 2001 From: Albie Date: Wed, 23 Dec 2020 09:03:11 +0000 Subject: [PATCH] add read-write lock to replace loops --- DragonFruit.Common.Data/ApiClient.cs | 94 +++++++++---------- .../Headers/HeaderCollection.cs | 3 +- 2 files changed, 46 insertions(+), 51 deletions(-) diff --git a/DragonFruit.Common.Data/ApiClient.cs b/DragonFruit.Common.Data/ApiClient.cs index db38b05..2f0d65a 100644 --- a/DragonFruit.Common.Data/ApiClient.cs +++ b/DragonFruit.Common.Data/ApiClient.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using System.Net.Http; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using DragonFruit.Common.Data.Exceptions; @@ -45,11 +46,15 @@ public ApiClient(ISerializer serializer) Serializer = serializer; Headers = new HeaderCollection(this); + _lock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); + RequestClientReset(true); } ~ApiClient() { + _lock?.Dispose(); + Client?.Dispose(); } @@ -105,22 +110,18 @@ public Func Handler public ISerializer Serializer { get; set; } /// - /// used by these requests. This is used by the library and as such, should **not** be disposed in any way + /// used by these requests. + /// This is used by the library and as such, should **not** be disposed in any way /// protected HttpClient Client { get; private set; } - /// - /// Time, in milliseconds to wait to modify a before failing the request - /// - protected virtual int AdjustmentTimeout => 200; - #endregion #region Private Vars + private readonly ReaderWriterLockSlim _lock; + private long _clientAdjustmentRequestSignal; private Func _handler; - private int _clientAdjustmentSignal; - private long _clientAdjustmentRequestSignal, _currentRequests; #endregion @@ -134,50 +135,41 @@ protected HttpClient GetClient() // return current client if there are no changes var resetLevel = Interlocked.Read(ref _clientAdjustmentRequestSignal); - if (resetLevel == 0) - { - return Client; - } - - // if we're waiting, then don't cause a crash from getting the wrong client - just wait. - while (Interlocked.CompareExchange(ref _clientAdjustmentSignal, 1, 0) == 1) + if (resetLevel > 0) { - Timeout(); - } + // block all reads and let all current requests finish + _lock.EnterWriteLock(); - try - { - // wait for all ongoing requests to end - while (_currentRequests > 0) + try { - Timeout(); - } + // only reset the client if the handler has changed (signal = 2) + var resetClient = resetLevel == 2; - // only reset the client if the handler has changed (signal = 2) - var resetClient = resetLevel == 2; + if (resetClient) + { + var handler = CreateHandler(); - if (resetClient) - { - var handler = CreateHandler(); - - Client?.Dispose(); - Client = handler != null ? new HttpClient(handler, true) : new HttpClient(); - } + Client?.Dispose(); + Client = handler != null ? new HttpClient(handler, true) : new HttpClient(); + } - // apply new headers - Headers.ApplyTo(Client); + // apply new headers + Headers.ApplyTo(Client); - // allow the conumer to change the client - SetupClient(Client, resetClient); + // allow the consumer to change the client + SetupClient(Client, resetClient); - // reset the state - Interlocked.Exchange(ref _clientAdjustmentRequestSignal, 0); - return Client; - } - finally - { - Interlocked.Exchange(ref _clientAdjustmentSignal, 0); + // reset the state + Interlocked.Exchange(ref _clientAdjustmentRequestSignal, 0); + } + finally + { + _lock.ExitWriteLock(); + } } + + _lock.EnterReadLock(); + return Client; } #endregion @@ -299,9 +291,7 @@ HttpResponseMessage CopyProcess(HttpResponseMessage response) /// Whether to dispose of the produced after has been invoked. protected T InternalPerform(HttpRequestMessage request, Func processResult, bool disposeResponse, CancellationToken token = default) { - //get client and request (disposables) var client = GetClient(); - Interlocked.Increment(ref _currentRequests); // post-modification SetupRequest(request); @@ -318,15 +308,14 @@ protected T InternalPerform(HttpRequestMessage request, Func + /// Requests the client is reset on the next request + /// + /// Whether to reset the as well as the headers public void RequestClientReset(bool fullReset) { if (fullReset) @@ -378,6 +371,7 @@ public void RequestClientReset(bool fullReset) } } - private void Timeout() => Thread.Sleep(AdjustmentTimeout / 2); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected void RequestFinished() => _lock.ExitReadLock(); } } diff --git a/DragonFruit.Common.Data/Headers/HeaderCollection.cs b/DragonFruit.Common.Data/Headers/HeaderCollection.cs index ea8c74f..53d50f8 100644 --- a/DragonFruit.Common.Data/Headers/HeaderCollection.cs +++ b/DragonFruit.Common.Data/Headers/HeaderCollection.cs @@ -9,11 +9,12 @@ namespace DragonFruit.Common.Data.Headers { public class HeaderCollection { - private readonly ConcurrentDictionary _values = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _values; private readonly ApiClient _client; public HeaderCollection(ApiClient client) { + _values = new ConcurrentDictionary(); _client = client; }