mirror of
https://github.com/ICDSystems/ICD.Common.Utils.git
synced 2026-02-14 20:25:01 +00:00
Merge branch 'RateLimit' of Common/Utils into dev
This commit is contained in:
@@ -7,10 +7,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
### Added
|
### Added
|
||||||
- Adding SequenceComparer for ordering collections of lists, arrays, etc
|
- Adding SequenceComparer for ordering collections of lists, arrays, etc
|
||||||
|
- Added RateLimitedEventQueue collection for throttling events
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Potential fix for timer disposal on Net Standard
|
- Potential fix for timer disposal on Net Standard
|
||||||
- Added workaround for older RPC servers where the typestring being broadcast would stil include _SimplSharp, now will be stripped
|
- Added workaround for older RPC servers where the typestring being broadcast would stil include _SimplSharp, now will be stripped
|
||||||
|
- Fixing bug where Timer.Reset() would continue repeating on an interval in Net Standard
|
||||||
|
|
||||||
## [3.6.0] - 2018-06-19
|
## [3.6.0] - 2018-06-19
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
100
ICD.Common.Utils.Tests/Collections/RateLimitedEventQueueTest.cs
Normal file
100
ICD.Common.Utils.Tests/Collections/RateLimitedEventQueueTest.cs
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using ICD.Common.Utils.Collections;
|
||||||
|
using ICD.Common.Utils.EventArguments;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace ICD.Common.Utils.Tests.Collections
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public sealed class RateLimitedEventQueueTest
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void ItemDequeuedFeedbackTest()
|
||||||
|
{
|
||||||
|
List<GenericEventArgs<int>> callbacks = new List<GenericEventArgs<int>>();
|
||||||
|
|
||||||
|
using (RateLimitedEventQueue<int> queue = new RateLimitedEventQueue<int> { BetweenMilliseconds = 1000 })
|
||||||
|
{
|
||||||
|
queue.OnItemDequeued += (sender, args) => callbacks.Add(args);
|
||||||
|
|
||||||
|
queue.Enqueue(10);
|
||||||
|
queue.Enqueue(20);
|
||||||
|
queue.Enqueue(30);
|
||||||
|
|
||||||
|
ThreadingUtils.Sleep(100);
|
||||||
|
|
||||||
|
Assert.AreEqual(1, callbacks.Count, "Initial enqueue did not trigger a dequeue");
|
||||||
|
|
||||||
|
queue.OnItemDequeued += (sender, args) => { ThreadingUtils.Sleep(1000); };
|
||||||
|
ThreadingUtils.Sleep(1000);
|
||||||
|
|
||||||
|
Assert.AreEqual(2, callbacks.Count, "Second enqueue did not dequeue");
|
||||||
|
|
||||||
|
ThreadingUtils.Sleep(1000);
|
||||||
|
|
||||||
|
Assert.AreEqual(2, callbacks.Count, "Third enqueue did not wait for process to complete");
|
||||||
|
|
||||||
|
ThreadingUtils.Sleep(1000);
|
||||||
|
|
||||||
|
Assert.AreEqual(3, callbacks.Count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Properties
|
||||||
|
|
||||||
|
[TestCase(1000)]
|
||||||
|
public void BetweenMillisecondsTest(long milliseconds)
|
||||||
|
{
|
||||||
|
using (RateLimitedEventQueue<int> queue = new RateLimitedEventQueue<int> { BetweenMilliseconds = milliseconds })
|
||||||
|
Assert.AreEqual(milliseconds, queue.BetweenMilliseconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void CountTest()
|
||||||
|
{
|
||||||
|
using (RateLimitedEventQueue<int> queue = new RateLimitedEventQueue<int> { BetweenMilliseconds = 100 * 1000 })
|
||||||
|
{
|
||||||
|
queue.Enqueue(1);
|
||||||
|
queue.Enqueue(1);
|
||||||
|
queue.Enqueue(1);
|
||||||
|
|
||||||
|
Assert.AreEqual(3, queue.Count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void EnqueueTest()
|
||||||
|
{
|
||||||
|
using (RateLimitedEventQueue<int> queue = new RateLimitedEventQueue<int> { BetweenMilliseconds = 100 * 1000 })
|
||||||
|
{
|
||||||
|
queue.Enqueue(10);
|
||||||
|
queue.Enqueue(20);
|
||||||
|
queue.Enqueue(30);
|
||||||
|
|
||||||
|
Assert.True(queue.SequenceEqual(new[] { 10, 20, 30 }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ClearTest()
|
||||||
|
{
|
||||||
|
using (RateLimitedEventQueue<int> queue = new RateLimitedEventQueue<int> { BetweenMilliseconds = 100 * 1000 })
|
||||||
|
{
|
||||||
|
queue.Enqueue(1);
|
||||||
|
queue.Enqueue(1);
|
||||||
|
queue.Enqueue(1);
|
||||||
|
|
||||||
|
queue.Clear();
|
||||||
|
|
||||||
|
Assert.AreEqual(0, queue.Count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
182
ICD.Common.Utils/Collections/RateLimitedEventQueue.cs
Normal file
182
ICD.Common.Utils/Collections/RateLimitedEventQueue.cs
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using ICD.Common.Utils.EventArguments;
|
||||||
|
using ICD.Common.Utils.Extensions;
|
||||||
|
using ICD.Common.Utils.Timers;
|
||||||
|
|
||||||
|
namespace ICD.Common.Utils.Collections
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// RateLimitedEventQueue provides features for enqueing items to be raised via an event at a controlled interval.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class RateLimitedEventQueue<T> : IEnumerable<T>, ICollection, IDisposable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Raised to handle to the next item in the queue.
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<GenericEventArgs<T>> OnItemDequeued;
|
||||||
|
|
||||||
|
private readonly SafeTimer m_DequeueTimer;
|
||||||
|
private readonly Queue<T> m_Queue;
|
||||||
|
private readonly SafeCriticalSection m_QueueSection;
|
||||||
|
|
||||||
|
#region Properties
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets/sets the time between dequeues in milliseconds.
|
||||||
|
/// </summary>
|
||||||
|
public long BetweenMilliseconds { get; set; }
|
||||||
|
|
||||||
|
public int Count { get { return m_QueueSection.Execute(() => m_Queue.Count); } }
|
||||||
|
|
||||||
|
bool ICollection.IsSynchronized { get { return true; } }
|
||||||
|
|
||||||
|
object ICollection.SyncRoot { get { return this; } }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor.
|
||||||
|
/// </summary>
|
||||||
|
public RateLimitedEventQueue()
|
||||||
|
{
|
||||||
|
m_Queue = new Queue<T>();
|
||||||
|
m_QueueSection = new SafeCriticalSection();
|
||||||
|
|
||||||
|
m_DequeueTimer = SafeTimer.Stopped(DequeueTimerCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Release resources.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
OnItemDequeued = null;
|
||||||
|
|
||||||
|
m_DequeueTimer.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enqueues the given item.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item"></param>
|
||||||
|
public void Enqueue(T item)
|
||||||
|
{
|
||||||
|
m_QueueSection.Enter();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
m_Queue.Enqueue(item);
|
||||||
|
|
||||||
|
if (m_Queue.Count == 1)
|
||||||
|
SendNext();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
m_QueueSection.Leave();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clears the queued items.
|
||||||
|
/// </summary>
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
m_QueueSection.Enter();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
m_DequeueTimer.Stop();
|
||||||
|
m_Queue.Clear();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
m_QueueSection.Leave();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Private Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends the next pulse in the queue.
|
||||||
|
/// </summary>
|
||||||
|
private void SendNext()
|
||||||
|
{
|
||||||
|
if (!m_QueueSection.TryEnter())
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (m_Queue.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
T item = m_Queue.Peek();
|
||||||
|
|
||||||
|
OnItemDequeued.Raise(this, new GenericEventArgs<T>(item));
|
||||||
|
|
||||||
|
m_DequeueTimer.Reset(BetweenMilliseconds);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
m_QueueSection.Leave();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when the dequeue timer elapses.
|
||||||
|
/// </summary>
|
||||||
|
private void DequeueTimerCallback()
|
||||||
|
{
|
||||||
|
m_QueueSection.Enter();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
m_Queue.Dequeue();
|
||||||
|
SendNext();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
m_QueueSection.Leave();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region IEnumerable/ICollection
|
||||||
|
|
||||||
|
public IEnumerator<T> GetEnumerator()
|
||||||
|
{
|
||||||
|
return m_QueueSection.Execute(() => m_Queue.ToList(m_Queue.Count).GetEnumerator());
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ICollection.CopyTo(Array array, int index)
|
||||||
|
{
|
||||||
|
m_QueueSection.Enter();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (T item in this)
|
||||||
|
{
|
||||||
|
array.SetValue(item, index);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
m_QueueSection.Leave();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -77,6 +77,7 @@
|
|||||||
<Compile Include="Collections\BiDictionary.cs" />
|
<Compile Include="Collections\BiDictionary.cs" />
|
||||||
<Compile Include="Collections\IcdOrderedDictionary.cs" />
|
<Compile Include="Collections\IcdOrderedDictionary.cs" />
|
||||||
<Compile Include="Collections\PriorityQueue.cs" />
|
<Compile Include="Collections\PriorityQueue.cs" />
|
||||||
|
<Compile Include="Collections\RateLimitedEventQueue.cs" />
|
||||||
<Compile Include="Collections\WeakKeyDictionary.cs" />
|
<Compile Include="Collections\WeakKeyDictionary.cs" />
|
||||||
<Compile Include="Comparers\FileNameEqualityComparer.cs" />
|
<Compile Include="Comparers\FileNameEqualityComparer.cs" />
|
||||||
<Compile Include="Comparers\PredicateComparer.cs" />
|
<Compile Include="Comparers\PredicateComparer.cs" />
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ namespace ICD.Common.Utils.Timers
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Callback is called after the dueTime milliseconds.
|
/// Callback is called once after the dueTime milliseconds.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dueTime"></param>
|
/// <param name="dueTime"></param>
|
||||||
public void Reset(long dueTime)
|
public void Reset(long dueTime)
|
||||||
@@ -125,7 +125,7 @@ namespace ICD.Common.Utils.Timers
|
|||||||
#if SIMPLSHARP
|
#if SIMPLSHARP
|
||||||
m_Timer.Reset(dueTime);
|
m_Timer.Reset(dueTime);
|
||||||
#else
|
#else
|
||||||
m_Timer.Change((int)dueTime, m_RepeatPeriod);
|
m_Timer.Change((int)dueTime, Timeout.Infinite);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user