// Copyright (C) 2024, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; using System.Collections.Generic; using System.Text; namespace Duplicati.Library.Utility { /// /// Class for wrapping a as an /// public class BlockingQueueAsEnumerable : IEnumerable { private IEnumerator m_enumerator; public BlockingQueueAsEnumerable(BlockingQueue queue) { m_enumerator = new BlockingQueueEnumerator(queue); } #region IEnumerable implementation public System.Collections.IEnumerator GetEnumerator() { return ((IEnumerable)this).GetEnumerator(); } #endregion #region IEnumerable implementation IEnumerator IEnumerable.GetEnumerator() { if (m_enumerator == null) throw new Exception("Can only return a single enumerator for a blocking queue"); IEnumerator tmp = m_enumerator; m_enumerator = null; return tmp; } #endregion private class BlockingQueueEnumerator : IEnumerator { private readonly BlockingQueue m_queue; private readonly bool m_first = true; private T m_current; public BlockingQueueEnumerator(BlockingQueue queue) { m_queue = queue; } #region IDisposable implementation public void Dispose() { } #endregion #region IEnumerator implementation public bool MoveNext() { m_current = m_queue.Dequeue(); return !(object.Equals(m_current, default(T)) && m_queue.Completed); } public void Reset() { if (!m_first) throw new System.NotImplementedException(); } public object Current { get { return m_current; } } #endregion #region IEnumerator implementation T IEnumerator.Current { get { return m_current; } } #endregion } } /// /// Implementation of a blocking queue that serves as a producer/consumer contact point /// /// The type of data in the queue public class BlockingQueue { /// /// The lock that protects the data structures /// private readonly object m_lock = new object(); /// /// The queue storing the elements produced /// private readonly Queue m_queue = new Queue(); /// /// A flag indicating if the queue is now empty forever /// private bool m_completed = false; /// /// The event that signals that items have been produced (consumers wait for this) /// private readonly System.Threading.ManualResetEvent m_itemsProduced = new System.Threading.ManualResetEvent(false); /// /// The event that signals that items have been consumed (producers wait for this) /// private readonly System.Threading.ManualResetEvent m_itemsConsumed = new System.Threading.ManualResetEvent(false); /// /// The maximum capacity of the queue, the producers will be blocked if more elements are put into the queue /// private readonly long m_maxCapacity = 100; /// /// Initializes a new instance of the class with the default capacity /// public BlockingQueue() { } /// /// Gets a value indicating whether this is completed /// public bool Completed { get { return m_completed; } } /// /// Initializes a new instance of the class /// /// The maximum capacity of the queue, the producers will be blocked if more elements are put into the queue public BlockingQueue(long maxCapacity) : this() { if (maxCapacity <= 0) throw new ArgumentException("The maximum capacity must be a positive number", nameof(maxCapacity)); m_maxCapacity = maxCapacity; } /// /// Signals that the producers are done and no more elements will be put into the queue /// public void SetCompleted() { m_completed = true; m_itemsProduced.Set(); m_itemsConsumed.Set(); } /// /// Enqueues the specified item. /// /// The item to put into the queue public bool Enqueue(T item) { while (true) { lock(m_lock) { if (m_completed) return false; if (m_queue.Count < m_maxCapacity) { m_queue.Enqueue(item); m_itemsProduced.Set(); return true; } m_itemsConsumed.Reset(); } m_itemsConsumed.WaitOne(); } } /// /// Dequeues an element from the queue /// /// The dequeued element or default(T) if there are no more elements public T Dequeue() { while(true) { lock(m_lock) { if (m_queue.Count > 0) { m_itemsConsumed.Set(); return m_queue.Dequeue(); } if (m_completed) return default(T); m_itemsProduced.Reset(); } m_itemsProduced.WaitOne(); } } } }