using System; using System.Threading; namespace Renci.SshNet.Common { /// /// Light implementation of SemaphoreSlim. /// public class SemaphoreLight : IDisposable { private readonly object _lock = new object(); private ManualResetEvent _waitHandle; private int _currentCount; /// /// Initializes a new instance of the class, specifying the initial number of requests that can /// be granted concurrently. /// /// The initial number of requests for the semaphore that can be granted concurrently. /// is a negative number. public SemaphoreLight(int initialCount) { if (initialCount < 0) { throw new ArgumentOutOfRangeException(nameof(initialCount), "The value cannot be negative."); } _currentCount = initialCount; } /// /// Gets the current count of the . /// public int CurrentCount { get { return _currentCount; } } /// /// Gets a that can be used to wait on the semaphore. /// /// /// A that can be used to wait on the semaphore. /// /// /// A successful wait on the does not imply a successful /// wait on the itself. It should be followed by a true wait /// on the semaphore. /// public WaitHandle AvailableWaitHandle { get { if (_waitHandle is null) { lock (_lock) { _waitHandle ??= new ManualResetEvent(_currentCount > 0); } } return _waitHandle; } } /// /// Exits the once. /// /// The previous count of the . public int Release() { return Release(1); } /// /// Exits the a specified number of times. /// /// The number of times to exit the semaphore. /// /// The previous count of the . /// public int Release(int releaseCount) { lock (_lock) { var oldCount = _currentCount; _currentCount += releaseCount; // signal waithandle when the original semaphore count was zero if (_waitHandle != null && oldCount == 0) { _ = _waitHandle.Set(); } Monitor.PulseAll(_lock); return oldCount; } } /// /// Blocks the current thread until it can enter the . /// public void Wait() { lock (_lock) { while (_currentCount < 1) { _ = Monitor.Wait(_lock); } _currentCount--; // unsignal waithandle when the semaphore count reaches zero if (_waitHandle != null && _currentCount == 0) { _ = _waitHandle.Reset(); } Monitor.PulseAll(_lock); } } /// /// Blocks the current thread until it can enter the , using a 32-bit signed /// integer that specifies the timeout. /// /// The number of milliseconds to wait, or Infinite(-1) to wait indefinitely. /// /// true if the current thread successfully entered the ; otherwise, false. /// public bool Wait(int millisecondsTimeout) { if (millisecondsTimeout < -1) { throw new ArgumentOutOfRangeException(nameof(millisecondsTimeout), "The timeout must represent a value between -1 and Int32.MaxValue, inclusive."); } return WaitWithTimeout(millisecondsTimeout); } /// /// Blocks the current thread until it can enter the , using a /// to specify the timeout. /// /// A that represents the number of milliseconds to wait, or a that represents -1 milliseconds to wait indefinitely. /// /// true if the current thread successfully entered the ; otherwise, false. /// public bool Wait(TimeSpan timeout) { var timeoutInMilliseconds = timeout.TotalMilliseconds; if (timeoutInMilliseconds is < -1d or > int.MaxValue) { throw new ArgumentOutOfRangeException(nameof(timeout), "The timeout must represent a value between -1 and Int32.MaxValue, inclusive."); } return WaitWithTimeout((int) timeoutInMilliseconds); } private bool WaitWithTimeout(int timeoutInMilliseconds) { lock (_lock) { if (timeoutInMilliseconds == Session.Infinite) { while (_currentCount < 1) { _ = Monitor.Wait(_lock); } } else { if (_currentCount < 1) { if (timeoutInMilliseconds > 0) { return false; } var remainingTimeInMilliseconds = timeoutInMilliseconds; var startTicks = Environment.TickCount; while (_currentCount < 1) { if (!Monitor.Wait(_lock, remainingTimeInMilliseconds)) { return false; } var elapsed = Environment.TickCount - startTicks; remainingTimeInMilliseconds -= elapsed; if (remainingTimeInMilliseconds < 0) { return false; } } } } _currentCount--; // unsignal waithandle when the semaphore count is zero if (_waitHandle != null && _currentCount == 0) { _ = _waitHandle.Reset(); } Monitor.PulseAll(_lock); return true; } } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } /// /// Releases unmanaged and - optionally - managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected void Dispose(bool disposing) { if (disposing) { var waitHandle = _waitHandle; if (waitHandle is not null) { waitHandle.Dispose(); _waitHandle = null; } } } } }