From fa728eaa91e11095e3730a059fe2db0e33b04b7f Mon Sep 17 00:00:00 2001 From: Mark Cilia Vincenti Date: Sun, 28 Apr 2024 17:55:18 +0200 Subject: [PATCH 1/6] Lock optimisation through semaphore pooling --- FolderSyncNet.csproj | 25 +-------- InitialScan.cs | 2 +- Synchronisation.cs | 129 ++++++------------------------------------- packages.config | 1 + 4 files changed, 21 insertions(+), 136 deletions(-) diff --git a/FolderSyncNet.csproj b/FolderSyncNet.csproj index 45125ec..fa72274 100644 --- a/FolderSyncNet.csproj +++ b/FolderSyncNet.csproj @@ -77,6 +77,9 @@ packages\AsyncEnumerator.4.0.2\lib\net461\AsyncEnumerable.dll + + packages\AsyncKeyedLock.6.4.2\lib\netstandard2.0\AsyncKeyedLock.dll + packages\Microsoft.Bcl.AsyncInterfaces.7.0.0\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll @@ -329,31 +332,9 @@ Designer - - Always - - - Always - - - - Always - - - Always - - - Always - Always - - Always - - - Always - Always diff --git a/InitialScan.cs b/InitialScan.cs index 24f6560..8394aef 100644 --- a/InitialScan.cs +++ b/InitialScan.cs @@ -242,7 +242,7 @@ private static IAsyncEnumerable ProcessSubDirs(FolderSyncNetSource.Dir var historyFileInfosDict = new Dictionary(); //var historyFileInfosTask = Task.CompletedTask; - AsyncLockQueueDictionary.LockDictReleaser destOrHistoryDirCacheLock = null; + IDisposable destOrHistoryDirCacheLock = null; //AsyncLockQueueDictionary.LockDictReleaser historyDirCacheLock = null; FileInfo[] fileInfos = null; diff --git a/Synchronisation.cs b/Synchronisation.cs index 864c39a..d67c91a 100644 --- a/Synchronisation.cs +++ b/Synchronisation.cs @@ -7,141 +7,43 @@ // #define ASYNC +using AsyncKeyedLock; using System; using System.Collections.Generic; -using System.Diagnostics; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; -using Nito.AsyncEx; namespace FolderSync { public class AsyncLockQueueDictionary where KeyT : IComparable, IEquatable { - private static readonly bool IsStringDictionary = typeof(KeyT) == typeof(string); - - private readonly object DictionaryAccessMutex = new object(); - //private readonly SemaphoreSlim DictionaryAccessMutex = new SemaphoreSlim(1, 1); - private readonly Dictionary LockQueueDictionary = new Dictionary(); - - public sealed class AsyncLockWithWaiterCount + private readonly AsyncKeyedLocker LockQueueDictionary = new AsyncKeyedLocker(o => { - public readonly AsyncLock LockEntry; -#pragma warning disable S1104 //Warning S1104 Make this field 'private' and encapsulate it in a 'public' property. - public int WaiterCount; -#pragma warning restore S1104 - - public AsyncLockWithWaiterCount() - { - this.LockEntry = new AsyncLock(); - this.WaiterCount = 1; - } - } + o.PoolSize = 20; + o.PoolInitialFill = 1; + }); - public sealed class LockDictReleaser : IDisposable //TODO: implement IAsyncDisposable in .NET 5.0 - { - private readonly KeyT Name; - private readonly AsyncLockWithWaiterCount LockEntry; -#if !NOASYNC - private readonly IDisposable LockHandle; -#endif - private readonly AsyncLockQueueDictionary AsyncLockQueueDictionary; - -#if NOASYNC - internal LockDictReleaser(KeyT name, AsyncLockWithWaiterCount lockEntry, AsyncLockQueueDictionary asyncLockQueueDictionary) -#else - internal LockDictReleaser(KeyT name, AsyncLockWithWaiterCount lockEntry, IDisposable lockHandle, AsyncLockQueueDictionary asyncLockQueueDictionary) -#endif - { - this.Name = name; - this.LockEntry = lockEntry; -#if !NOASYNC - this.LockHandle = lockHandle; -#endif - this.AsyncLockQueueDictionary = asyncLockQueueDictionary; - } - - public void Dispose() - { -#if NOASYNC - this.AsyncLockQueueDictionary.ReleaseLock(this.Name, this.LockEntry); -#else - this.AsyncLockQueueDictionary.ReleaseLock(this.Name, this.LockEntry, this.LockHandle); -#endif - } - } + private static readonly bool IsStringDictionary = typeof(KeyT) == typeof(string); -#if NOASYNC - private void ReleaseLock(KeyT name, AsyncLockWithWaiterCount lockEntry) -#else - private void ReleaseLock(KeyT name, AsyncLockWithWaiterCount lockEntry, IDisposable lockHandle) -#endif + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public async ValueTask LockAsync(KeyT name) { -#if NOASYNC - Monitor.Exit(lockEntry.LockEntry); -#else - lockHandle.Dispose(); -#endif - - lock (DictionaryAccessMutex) - //DictionaryAccessMutex.Wait(); - //try - { - lockEntry.WaiterCount--; - Debug.Assert(lockEntry.WaiterCount >= 0); - - if (lockEntry.WaiterCount == 0) //NB! - { - LockQueueDictionary.Remove(name); - } - } - //finally - //{ - // DictionaryAccessMutex.Release(); - //} + return await LockQueueDictionary.LockAsync(name).ConfigureAwait(false); } - public async Task LockAsync(KeyT name, CancellationToken cancellationToken = default(CancellationToken)) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public async ValueTask LockAsync(KeyT name, CancellationToken cancellationToken) { - AsyncLockWithWaiterCount lockEntry; -#pragma warning disable PH_S023 //Message PH_S023 Using the blocking synchronization mechanics of a monitor inside an async method is discouraged; use SemaphoreSlim instead. - lock (DictionaryAccessMutex) -#pragma warning restore PH_S023 - //await DictionaryAccessMutex.WaitAsync(cancellationToken); - //try - { - if (!LockQueueDictionary.TryGetValue(name, out lockEntry)) - { - lockEntry = new AsyncLockWithWaiterCount(); - LockQueueDictionary.Add(name, lockEntry); - } - else - { - lockEntry.WaiterCount++; //NB! must be done inside the lock and BEFORE waiting for the lock - } - } - //finally - //{ - // DictionaryAccessMutex.Release(); - //} - -#if NOASYNC -#pragma warning disable PH_P006 //warning PH_P006 Favor the use of the lock-statement instead of the use of Monitor.Enter when no timeouts are needed. - Monitor.Enter(lockEntry.LockEntry); -#pragma warning restore PH_P006 - return new LockDictReleaser(name, lockEntry, this); -#else - var lockHandle = await lockEntry.LockEntry.LockAsync(cancellationToken); - return new LockDictReleaser(name, lockEntry, lockHandle, this); -#endif + return await LockQueueDictionary.LockAsync(name, cancellationToken).ConfigureAwait(false); } public sealed class MultiLockDictReleaser : IDisposable //TODO: implement IAsyncDisposable in .NET 5.0 { - private readonly LockDictReleaser[] Releasers; + private readonly IDisposable[] Releasers; - public MultiLockDictReleaser(params LockDictReleaser[] releasers) + public MultiLockDictReleaser(params IDisposable[] releasers) { this.Releasers = releasers; } @@ -156,6 +58,7 @@ public void Dispose() } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public async Task LockAsync(KeyT name1, KeyT name2, CancellationToken cancellationToken = default(CancellationToken)) { var names = new List() diff --git a/packages.config b/packages.config index 9f9985a..94c393b 100644 --- a/packages.config +++ b/packages.config @@ -4,6 +4,7 @@ + From 6dbf463145ebe786a2fe25b05650c93c701c05b6 Mon Sep 17 00:00:00 2001 From: Mark Cilia Vincenti Date: Mon, 11 Nov 2024 16:14:20 +0100 Subject: [PATCH 2/6] Bump AsyncKeyedLock to 7.1.3 and relied on default pooling options. --- App.config | 2 +- FolderSyncNet.csproj | 4 ++-- Synchronisation.cs | 6 +----- packages.config | 2 +- 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/App.config b/App.config index 47e3fd1..3f01dbf 100644 --- a/App.config +++ b/App.config @@ -4,7 +4,7 @@ - + diff --git a/FolderSyncNet.csproj b/FolderSyncNet.csproj index fa72274..1ed2385 100644 --- a/FolderSyncNet.csproj +++ b/FolderSyncNet.csproj @@ -77,8 +77,8 @@ packages\AsyncEnumerator.4.0.2\lib\net461\AsyncEnumerable.dll - - packages\AsyncKeyedLock.6.4.2\lib\netstandard2.0\AsyncKeyedLock.dll + + packages\AsyncKeyedLock.7.1.3\lib\netstandard2.0\AsyncKeyedLock.dll packages\Microsoft.Bcl.AsyncInterfaces.7.0.0\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll diff --git a/Synchronisation.cs b/Synchronisation.cs index d67c91a..831594b 100644 --- a/Synchronisation.cs +++ b/Synchronisation.cs @@ -19,11 +19,7 @@ namespace FolderSync public class AsyncLockQueueDictionary where KeyT : IComparable, IEquatable { - private readonly AsyncKeyedLocker LockQueueDictionary = new AsyncKeyedLocker(o => - { - o.PoolSize = 20; - o.PoolInitialFill = 1; - }); + private readonly AsyncKeyedLocker LockQueueDictionary = new AsyncKeyedLocker(); private static readonly bool IsStringDictionary = typeof(KeyT) == typeof(string); diff --git a/packages.config b/packages.config index 94c393b..47dfd9e 100644 --- a/packages.config +++ b/packages.config @@ -4,7 +4,7 @@ - + From ca27cfa428bfacefd1e59f0cabfab07d3242ab43 Mon Sep 17 00:00:00 2001 From: Mark Cilia Vincenti Date: Mon, 11 Nov 2024 16:15:40 +0100 Subject: [PATCH 3/6] Revert unnecessary change by VS2022 --- App.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/App.config b/App.config index 3f01dbf..47e3fd1 100644 --- a/App.config +++ b/App.config @@ -4,7 +4,7 @@ - + From 05f9cdd9a2818036cdca3bc59cd641109bc594e7 Mon Sep 17 00:00:00 2001 From: Mark Cilia Vincenti Date: Mon, 11 Nov 2024 16:17:03 +0100 Subject: [PATCH 4/6] Revert unnecessary change by VS2022 --- FolderSyncNet.csproj | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/FolderSyncNet.csproj b/FolderSyncNet.csproj index 1ed2385..000ddd9 100644 --- a/FolderSyncNet.csproj +++ b/FolderSyncNet.csproj @@ -332,9 +332,31 @@ Designer + + Always + + + Always + + + + Always + + + Always + + + Always + Always + + Always + + + Always + Always @@ -377,4 +399,4 @@ - \ No newline at end of file + From bb526ff0c749f9fcecd2862278a1628e1cc1c096 Mon Sep 17 00:00:00 2001 From: Mark Cilia Vincenti Date: Mon, 11 Nov 2024 16:17:25 +0100 Subject: [PATCH 5/6] Revert unnecessary change by VS2022 From 511dc3312e54bd89508664a8999426d648edb175 Mon Sep 17 00:00:00 2001 From: Mark Cilia Vincenti Date: Mon, 11 Nov 2024 16:17:50 +0100 Subject: [PATCH 6/6] Revert unnecessary change by VS2022