diff --git a/src/.editorconfig b/src/.editorconfig index dbffb65a01b..c1cf5298594 100644 --- a/src/.editorconfig +++ b/src/.editorconfig @@ -5,6 +5,11 @@ root = false # Code files [*.cs] +# Design rules + +# CA1001: Types that own disposable fields should be disposable +dotnet_diagnostic.CA1001.severity = error + # Globalization rules # CA1303: Do not pass literals as localized parameters diff --git a/src/EFCore/Internal/AsyncLock.cs b/src/EFCore/Internal/AsyncLock.cs deleted file mode 100644 index 74dd39964e6..00000000000 --- a/src/EFCore/Internal/AsyncLock.cs +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Threading; -using System.Threading.Tasks; -using JetBrains.Annotations; - -namespace Microsoft.EntityFrameworkCore.Internal -{ - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public sealed class AsyncLock - { - private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1); - private readonly Releaser _releaser; - private readonly Task _releaserTask; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public AsyncLock() - { - _releaser = new Releaser(this); - _releaserTask = Task.FromResult(_releaser); - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public Task LockAsync(CancellationToken cancellationToken = default) - { - var wait = _semaphore.WaitAsync(cancellationToken); - - return wait.IsCompleted - ? _releaserTask - : wait.ContinueWith( - (_, state) => ((AsyncLock)state)._releaser, - this, CancellationToken.None, - TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public Releaser Lock() - { - _semaphore.Wait(); - - return _releaser; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public readonly struct Releaser : IDisposable - { - private readonly AsyncLock _toRelease; - - internal Releaser([NotNull] AsyncLock toRelease) - { - _toRelease = toRelease; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public void Dispose() - => _toRelease._semaphore.Release(); - } - } -} diff --git a/src/EFCore/ValueGeneration/HiLoValueGeneratorState.cs b/src/EFCore/ValueGeneration/HiLoValueGeneratorState.cs index 9492508e9a0..86752c94740 100644 --- a/src/EFCore/ValueGeneration/HiLoValueGeneratorState.cs +++ b/src/EFCore/ValueGeneration/HiLoValueGeneratorState.cs @@ -15,9 +15,9 @@ namespace Microsoft.EntityFrameworkCore.ValueGeneration /// /// The thread safe state used by . /// - public class HiLoValueGeneratorState + public class HiLoValueGeneratorState : IDisposable { - private readonly AsyncLock _asyncLock = new AsyncLock(); + private readonly SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1); private HiLoValue _currentValue; private readonly int _blockSize; @@ -58,7 +58,8 @@ public virtual TValue Next([NotNull] Func getNewLowValue) // gets a chance to use the new value, so use a while here to do it all again. while (newValue.Low >= newValue.High) { - using (_asyncLock.Lock()) + _semaphoreSlim.Wait(); + try { // Once inside the lock check to see if another thread already got a new block, in which // case just get a value out of the new block instead of requesting one. @@ -73,6 +74,10 @@ public virtual TValue Next([NotNull] Func getNewLowValue) newValue = GetNextValue(); } } + finally + { + _semaphoreSlim.Release(); + } } return ConvertResult(newValue); @@ -100,7 +105,8 @@ public virtual async ValueTask NextAsync( // gets a chance to use the new value, so use a while here to do it all again. while (newValue.Low >= newValue.High) { - using (await _asyncLock.LockAsync(cancellationToken).ConfigureAwait(false)) + await _semaphoreSlim.WaitAsync(cancellationToken).ConfigureAwait(false); + try { // Once inside the lock check to see if another thread already got a new block, in which // case just get a value out of the new block instead of requesting one. @@ -115,6 +121,10 @@ public virtual async ValueTask NextAsync( newValue = GetNextValue(); } } + finally + { + _semaphoreSlim.Release(); + } } return ConvertResult(newValue); @@ -152,5 +162,10 @@ public HiLoValue(long low, long high) public HiLoValue NextValue() => new HiLoValue(Low + 1, High); } + + /// + /// Releases the allocated resources for this instance. + /// + public virtual void Dispose() => _semaphoreSlim.Dispose(); } }