Skip to content

Commit f686a42

Browse files
author
Jonathan Gaillard
committed
Add approximate wait parameter to Get()
1 parent 9a9f0d1 commit f686a42

File tree

3 files changed

+156
-8
lines changed

3 files changed

+156
-8
lines changed

DominionEnterprises.Mongo.Tests/QueueTests.cs

Lines changed: 105 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ public void GetBySubDocQuery()
206206
queue.Send(new BsonDocument { { "key1", 0 }, { "key2", true } });
207207
queue.Send(messageTwo);
208208

209-
var result = queue.Get(new QueryDocument("one.two.three", new BsonDocument("$gt", 4)), TimeSpan.MaxValue, TimeSpan.MaxValue);
209+
var result = queue.Get(new QueryDocument("one.two.three", new BsonDocument("$gt", 4)), TimeSpan.MaxValue, TimeSpan.MaxValue, TimeSpan.MinValue, false);
210210

211211
messageTwo.InsertAt(0, new BsonElement("id", result["id"]));
212212
Assert.AreEqual(messageTwo, result);
@@ -280,14 +280,37 @@ public void GetWait()
280280
{
281281
var start = DateTime.Now;
282282

283-
queue.Get(new QueryDocument(), TimeSpan.MaxValue, TimeSpan.FromMilliseconds(200), TimeSpan.MinValue);
283+
queue.Get(new QueryDocument(), TimeSpan.MaxValue, TimeSpan.FromMilliseconds(200), TimeSpan.MinValue, false);
284284

285285
var end = DateTime.Now;
286286

287287
Assert.IsTrue(end - start >= TimeSpan.FromMilliseconds(200));
288288
Assert.IsTrue(end - start < TimeSpan.FromMilliseconds(400));
289289
}
290290

291+
[Test]
292+
public void GetApproximateWait()
293+
{
294+
var min = double.MaxValue;
295+
var max = double.MinValue;
296+
for (var i = 0; i < 10; ++i)
297+
{
298+
var start = DateTime.Now;
299+
300+
queue.Get(new QueryDocument(), TimeSpan.MaxValue, TimeSpan.FromMilliseconds(100), TimeSpan.MinValue, true);
301+
302+
var time = (DateTime.Now - start).TotalMilliseconds;
303+
Assert.IsTrue(time >= 80.0);//minux 0.1 of 100
304+
Assert.IsTrue(time < 200.0);
305+
306+
min = Math.Min(min, time);
307+
max = Math.Max(max, time);
308+
}
309+
310+
Assert.IsTrue(min < 100.0);
311+
Assert.IsTrue(max > 100.0);
312+
}
313+
291314
[Test]
292315
public void EarliestGet()
293316
{
@@ -528,5 +551,85 @@ public void SendWithNullMessage()
528551
queue.Send(null);
529552
}
530553
#endregion
554+
555+
#region GetRandomDouble
556+
[Test]
557+
public void GetRandomDoubleFromZeroToOne()
558+
{
559+
var count = 1000;
560+
var sum = 0.0;
561+
for (var i = 0; i < count; ++i)
562+
{
563+
var randomDouble = Queue.GetRandomDouble(0.0, 1.0);
564+
sum += randomDouble;
565+
Assert.IsTrue(randomDouble <= 1.0);
566+
Assert.IsTrue(randomDouble >= 0.0);
567+
}
568+
569+
var average = sum / (double)count;
570+
571+
Assert.IsTrue(average >= 0.45);
572+
Assert.IsTrue(average <= 0.55);
573+
}
574+
575+
[Test]
576+
public void GetRandomDoubleFromNegativeOneToPositiveOne()
577+
{
578+
var count = 1000;
579+
var sum = 0.0;
580+
for (var i = 0; i < count; ++i)
581+
{
582+
var randomDouble = Queue.GetRandomDouble(-1.0, 1.0);
583+
sum += randomDouble;
584+
Assert.IsTrue(randomDouble <= 1.0);
585+
Assert.IsTrue(randomDouble >= -1.0);
586+
}
587+
588+
var average = sum / (double)count;
589+
590+
Assert.IsTrue(average >= -0.05);
591+
Assert.IsTrue(average <= 0.05);
592+
}
593+
594+
[Test]
595+
public void GetRandomDoubleFromThreeToFour()
596+
{
597+
var count = 1000;
598+
var sum = 0.0;
599+
for (var i = 0; i < count; ++i)
600+
{
601+
var randomDouble = Queue.GetRandomDouble(3.0, 4.0);
602+
sum += randomDouble;
603+
Assert.IsTrue(randomDouble <= 4.0);
604+
Assert.IsTrue(randomDouble >= 3.0);
605+
}
606+
607+
var average = sum / (double)count;
608+
609+
Assert.IsTrue(average >= 3.45);
610+
Assert.IsTrue(average <= 3.55);
611+
}
612+
613+
[Test]
614+
[ExpectedException(typeof(ArgumentException))]
615+
public void GetRandomDoubleWithNaNMin()
616+
{
617+
Queue.GetRandomDouble(double.NaN, 4.0);
618+
}
619+
620+
[Test]
621+
[ExpectedException(typeof(ArgumentException))]
622+
public void GetRandomDoubleWithNaNMax()
623+
{
624+
Queue.GetRandomDouble(4.0, double.NaN);
625+
}
626+
627+
[Test]
628+
[ExpectedException(typeof(ArgumentException))]
629+
public void GetRandomDoubleWithMaxLessThanMin()
630+
{
631+
Queue.GetRandomDouble(4.0, 3.9);
632+
}
633+
#endregion
531634
}
532635
}

DominionEnterprises.Mongo/Queue.cs

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
using MongoDB.Bson;
55
using MongoDB.Driver;
66
using System.Reflection;
7+
using System.Security.Cryptography;
78

8-
[assembly: AssemblyVersion("1.0.3.*")]
9+
[assembly: AssemblyVersion("1.1.0.*")]
910

1011
namespace DominionEnterprises.Mongo
1112
{
@@ -152,7 +153,7 @@ public BsonDocument Get(QueryDocument query, TimeSpan resetRunning, TimeSpan wai
152153
}
153154

154155
/// <summary>
155-
/// Get a non running message from queue
156+
/// Get a non running message from queue with an approxiate wait.
156157
/// </summary>
157158
/// <param name="query">query where top level fields do not contain operators. Lower level fields can however. eg: valid {a: {$gt: 1}, "b.c": 3}, invalid {$and: [{...}, {...}]}</param>
158159
/// <param name="resetRunning">duration before this message is considered abandoned and will be given with another call to Get()</param>
@@ -162,7 +163,23 @@ public BsonDocument Get(QueryDocument query, TimeSpan resetRunning, TimeSpan wai
162163
/// <exception cref="ArgumentNullException">query is null</exception>
163164
public BsonDocument Get(QueryDocument query, TimeSpan resetRunning, TimeSpan wait, TimeSpan poll)
164165
{
165-
if (query == null) throw new ArgumentNullException("query");
166+
return Get(query, resetRunning, wait, poll, true);
167+
}
168+
169+
/// <summary>
170+
/// Get a non running message from queue
171+
/// </summary>
172+
/// <param name="query">query where top level fields do not contain operators. Lower level fields can however. eg: valid {a: {$gt: 1}, "b.c": 3}, invalid {$and: [{...}, {...}]}</param>
173+
/// <param name="resetRunning">duration before this message is considered abandoned and will be given with another call to Get()</param>
174+
/// <param name="wait">duration to keep polling before returning null</param>
175+
/// <param name="poll">duration between poll attempts</param>
176+
/// <param name="approximateWait">whether to fluctuate the wait time randomly by +-10 percent. This ensures Get() calls seperate in time when multiple Queues are used in loops started at the same time</param>
177+
/// <returns>message or null</returns>
178+
/// <exception cref="ArgumentNullException">query is null</exception>
179+
public BsonDocument Get(QueryDocument query, TimeSpan resetRunning, TimeSpan wait, TimeSpan poll, bool approximateWait)
180+
{
181+
if (query == null)
182+
throw new ArgumentNullException ("query");
166183

167184
//reset stuck messages
168185
collection.Update(
@@ -194,10 +211,17 @@ public BsonDocument Get(QueryDocument query, TimeSpan resetRunning, TimeSpan wai
194211
var end = DateTime.UtcNow;
195212
try
196213
{
214+
if (approximateWait)
215+
//fluctuate randomly by 10 percent
216+
wait += TimeSpan.FromMilliseconds(wait.TotalMilliseconds * GetRandomDouble(-0.1, 0.1));
217+
197218
end += wait;
198219
}
199-
catch (ArgumentOutOfRangeException)
220+
catch (Exception e)
200221
{
222+
if (!(e is OverflowException) && !(e is ArgumentOutOfRangeException))
223+
throw e;//cant cover
224+
201225
end = wait > TimeSpan.Zero ? DateTime.MaxValue : DateTime.MinValue;
202226
}
203227

@@ -462,5 +486,26 @@ private void EnsureIndex(IndexKeysDocument index)
462486

463487
throw new Exception("couldnt create index after 5 attempts");
464488
}
489+
490+
/// <summary>
491+
/// Gets a random double between min and max using RNGCryptoServiceProvider
492+
/// </summary>
493+
/// <returns>
494+
/// random double.
495+
/// </returns>
496+
public static double GetRandomDouble(double min, double max)
497+
{
498+
if (Double.IsNaN(min)) throw new ArgumentException("min cannot be NaN");
499+
if (Double.IsNaN(max)) throw new ArgumentException("max cannot be NaN");
500+
if (max < min) throw new ArgumentException("max cannot be less than min");
501+
502+
var buffer = new byte[8];
503+
new RNGCryptoServiceProvider().GetBytes(buffer);
504+
var randomULong = BitConverter.ToUInt64(buffer, 0);
505+
506+
var fraction = (double)randomULong / (double)ulong.MaxValue;
507+
var fractionOfNewRange = fraction * (max - min);
508+
return min + fractionOfNewRange;
509+
}
465510
}
466511
}

travisCoverageConfig.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
],
55
"methodBodyExcludes": [
66
{
7-
"method": "MongoDB.Bson.BsonDocument DominionEnterprises.Mongo.Queue::Get(MongoDB.Driver.QueryDocument,System.TimeSpan,System.TimeSpan,System.TimeSpan)",
8-
"lines": ["poll = TimeSpan.FromMilliseconds(int.MaxValue);"]
7+
"method": "MongoDB.Bson.BsonDocument DominionEnterprises.Mongo.Queue::Get(MongoDB.Driver.QueryDocument,System.TimeSpan,System.TimeSpan,System.TimeSpan,System.Boolean)",
8+
"lines": ["poll = TimeSpan.FromMilliseconds(int.MaxValue);", "throw e;//cant cover"]
99
}
1010
]
1111
}

0 commit comments

Comments
 (0)