Skip to content

Commit 9ef2ead

Browse files
committed
Merge pull request #13 from gaillard/master
approximate wait param and loop exit.
2 parents 9a9f0d1 + 7561140 commit 9ef2ead

File tree

3 files changed

+168
-8
lines changed

3 files changed

+168
-8
lines changed

DominionEnterprises.Mongo.Tests/QueueTests.cs

Lines changed: 114 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,12 +280,44 @@ 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.FromMilliseconds(201), false);
284284

285285
var end = DateTime.Now;
286286

287287
Assert.IsTrue(end - start >= TimeSpan.FromMilliseconds(200));
288288
Assert.IsTrue(end - start < TimeSpan.FromMilliseconds(400));
289+
290+
start = DateTime.Now;
291+
292+
queue.Get(new QueryDocument(), TimeSpan.MaxValue, TimeSpan.FromMilliseconds(200), TimeSpan.MinValue, false);
293+
294+
end = DateTime.Now;
295+
296+
Assert.IsTrue(end - start >= TimeSpan.FromMilliseconds(200));
297+
Assert.IsTrue(end - start < TimeSpan.FromMilliseconds(400));
298+
}
299+
300+
[Test]
301+
public void GetApproximateWait()
302+
{
303+
var min = double.MaxValue;
304+
var max = double.MinValue;
305+
for (var i = 0; i < 10; ++i)
306+
{
307+
var start = DateTime.Now;
308+
309+
queue.Get(new QueryDocument(), TimeSpan.MaxValue, TimeSpan.FromMilliseconds(100), TimeSpan.MinValue, true);
310+
311+
var time = (DateTime.Now - start).TotalMilliseconds;
312+
Assert.IsTrue(time >= 80.0);//minux 0.1 of 100
313+
Assert.IsTrue(time < 200.0);
314+
315+
min = Math.Min(min, time);
316+
max = Math.Max(max, time);
317+
}
318+
319+
Assert.IsTrue(min < 100.0);
320+
Assert.IsTrue(max > 100.0);
289321
}
290322

291323
[Test]
@@ -528,5 +560,85 @@ public void SendWithNullMessage()
528560
queue.Send(null);
529561
}
530562
#endregion
563+
564+
#region GetRandomDouble
565+
[Test]
566+
public void GetRandomDoubleFromZeroToOne()
567+
{
568+
var count = 1000;
569+
var sum = 0.0;
570+
for (var i = 0; i < count; ++i)
571+
{
572+
var randomDouble = Queue.GetRandomDouble(0.0, 1.0);
573+
sum += randomDouble;
574+
Assert.IsTrue(randomDouble <= 1.0);
575+
Assert.IsTrue(randomDouble >= 0.0);
576+
}
577+
578+
var average = sum / (double)count;
579+
580+
Assert.IsTrue(average >= 0.45);
581+
Assert.IsTrue(average <= 0.55);
582+
}
583+
584+
[Test]
585+
public void GetRandomDoubleFromNegativeOneToPositiveOne()
586+
{
587+
var count = 1000;
588+
var sum = 0.0;
589+
for (var i = 0; i < count; ++i)
590+
{
591+
var randomDouble = Queue.GetRandomDouble(-1.0, 1.0);
592+
sum += randomDouble;
593+
Assert.IsTrue(randomDouble <= 1.0);
594+
Assert.IsTrue(randomDouble >= -1.0);
595+
}
596+
597+
var average = sum / (double)count;
598+
599+
Assert.IsTrue(average >= -0.05);
600+
Assert.IsTrue(average <= 0.05);
601+
}
602+
603+
[Test]
604+
public void GetRandomDoubleFromThreeToFour()
605+
{
606+
var count = 1000;
607+
var sum = 0.0;
608+
for (var i = 0; i < count; ++i)
609+
{
610+
var randomDouble = Queue.GetRandomDouble(3.0, 4.0);
611+
sum += randomDouble;
612+
Assert.IsTrue(randomDouble <= 4.0);
613+
Assert.IsTrue(randomDouble >= 3.0);
614+
}
615+
616+
var average = sum / (double)count;
617+
618+
Assert.IsTrue(average >= 3.45);
619+
Assert.IsTrue(average <= 3.55);
620+
}
621+
622+
[Test]
623+
[ExpectedException(typeof(ArgumentException))]
624+
public void GetRandomDoubleWithNaNMin()
625+
{
626+
Queue.GetRandomDouble(double.NaN, 4.0);
627+
}
628+
629+
[Test]
630+
[ExpectedException(typeof(ArgumentException))]
631+
public void GetRandomDoubleWithNaNMax()
632+
{
633+
Queue.GetRandomDouble(4.0, double.NaN);
634+
}
635+
636+
[Test]
637+
[ExpectedException(typeof(ArgumentException))]
638+
public void GetRandomDoubleWithMaxLessThanMin()
639+
{
640+
Queue.GetRandomDouble(4.0, 3.9);
641+
}
642+
#endregion
531643
}
532644
}

DominionEnterprises.Mongo/Queue.cs

Lines changed: 52 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

@@ -224,6 +248,9 @@ public BsonDocument Get(QueryDocument query, TimeSpan resetRunning, TimeSpan wai
224248

225249
Thread.Sleep(poll);
226250
}
251+
252+
if (DateTime.UtcNow >= end)
253+
return null;
227254
}
228255
}
229256
#endregion
@@ -462,5 +489,26 @@ private void EnsureIndex(IndexKeysDocument index)
462489

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

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)