|
1 | 1 | using System; |
| 2 | +using System.Data.SqlTypes; |
2 | 3 | using System.Threading; |
| 4 | +using System.Threading.Tasks; |
3 | 5 | using ReactiveDomain.Foundation; |
4 | 6 | using ReactiveDomain.Messaging.Bus; |
| 7 | +using ReactiveDomain.Util; |
5 | 8 | using Xunit; |
6 | 9 |
|
7 | 10 |
|
@@ -40,47 +43,64 @@ public static void ArraySegmentEqual<T>( |
40 | 43 | } |
41 | 44 |
|
42 | 45 | /// <summary> |
43 | | - /// Asserts the given function will return false before the timeout expires. |
44 | | - /// Repeatedly evaluates the function until false is returned or the timeout expires. |
45 | | - /// Will return immediately when the condition is false. |
46 | | - /// Evaluates the timeout every 10 msec until expired. |
47 | | - /// Will not yield the thread by default, if yielding is required to resolve deadlocks set yieldThread to true. |
| 46 | + /// Asserts the given function will return false before the timeout expires. |
| 47 | + /// Repeatedly evaluates the function until false is returned or the timeout expires. |
| 48 | + /// Will return immediately when the condition is false. |
| 49 | + /// Evaluates the condition on an exponential back off up to 250 ms until timeout. |
| 50 | + /// Will yield the thread after each evaluation, use the hint yieldThread if it is known the function should yield before evaluation. |
48 | 51 | /// </summary> |
49 | 52 | /// <param name="func">The function to evaluate.</param> |
50 | 53 | /// <param name="timeout">A timeout in milliseconds. If not specified, defaults to 1000.</param> |
51 | 54 | /// <param name="msg">A message to display if the condition is not satisfied.</param> |
52 | | - /// <param name="yieldThread">If true, the thread relinquishes the remainder of its time |
53 | | - /// slice to any thread of equal priority that is ready to run.</param> |
| 55 | + /// <param name="yieldThread">Execution hint to yield thread before evaluation</param> |
54 | 56 | public static void IsOrBecomesFalse(Func<bool> func, int? timeout = null, string msg = null, bool yieldThread = false) |
55 | 57 | { |
56 | 58 | IsOrBecomesTrue(() => !func(), timeout, msg, yieldThread); |
57 | 59 | } |
58 | 60 |
|
59 | 61 | /// <summary> |
60 | | - /// Asserts the given function will return true before the timeout expires. |
61 | | - /// Repeatedly evaluates the function until true is returned or the timeout expires. |
62 | | - /// Will return immediately when the condition is true. |
63 | | - /// Evaluates the timeout every 10 msec until expired. |
64 | | - /// Will not yield the thread by default, if yielding is required to resolve deadlocks set yieldThread to true. |
| 62 | + /// Asserts the given function will return true before the timeout expires. |
| 63 | + /// Repeatedly evaluates the function until true is returned or the timeout expires. |
| 64 | + /// Will return immediately when the condition is true. |
| 65 | + /// Evaluates the condition on an exponential back off up to 250 ms until timeout. |
| 66 | + /// Will yield the thread after each evaluation, use the hint yieldThread if it is known the function should yield before evaluation. |
65 | 67 | /// </summary> |
66 | 68 | /// <param name="func">The function to evaluate.</param> |
67 | 69 | /// <param name="timeout">A timeout in milliseconds. If not specified, defaults to 1000.</param> |
68 | 70 | /// <param name="msg">A message to display if the condition is not satisfied.</param> |
69 | | - /// <param name="yieldThread">If true, the thread relinquishes the remainder of its time |
70 | | - /// slice to any thread of equal priority that is ready to run.</param> |
| 71 | + /// <param name="yieldThread">Execution hint to yield thread before evaluation</param> |
71 | 72 | public static void IsOrBecomesTrue(Func<bool> func, int? timeout = null, string msg = null, bool yieldThread = false) |
72 | 73 | { |
73 | | - if (yieldThread) Thread.Sleep(0); |
74 | | - if (!timeout.HasValue) timeout = 1000; |
75 | | - var waitLoops = timeout / 10; |
| 74 | + if (!yieldThread && func() == true) |
| 75 | + { |
| 76 | + Assert.True(true, msg ?? ""); |
| 77 | + return; |
| 78 | + } |
| 79 | + |
76 | 80 | var result = false; |
77 | | - for (int i = 0; i < waitLoops; i++) |
| 81 | + var startTime = Environment.TickCount; //returns MS since machine start |
| 82 | + var endTime = startTime + (timeout ?? 1000); |
| 83 | + |
| 84 | + |
| 85 | + var delay = 1; |
| 86 | + while (true) |
78 | 87 | { |
79 | | - if (SpinWait.SpinUntil(func, 10)) |
| 88 | + using (var task = EvaluateAfterDelay(func, TimeSpan.FromMilliseconds(delay))) |
80 | 89 | { |
81 | | - result = true; |
82 | | - break; |
| 90 | + task.Wait(); |
| 91 | + if (task.Result == true) |
| 92 | + { |
| 93 | + result = true; |
| 94 | + break; |
| 95 | + } |
83 | 96 | } |
| 97 | + var now = Environment.TickCount; |
| 98 | + if ((endTime - now) <= 0) { break; } |
| 99 | + if (delay < 250) |
| 100 | + { |
| 101 | + delay = delay << 1; |
| 102 | + } |
| 103 | + delay = Math.Min(delay, endTime - now); |
84 | 104 | } |
85 | 105 | Assert.True(result, msg ?? ""); |
86 | 106 | } |
@@ -116,5 +136,17 @@ public static void AtLeastModelVersion(ReadModelBase readModel, int expectedVers |
116 | 136 | { |
117 | 137 | IsOrBecomesTrue(() => readModel.Version >= expectedVersion, timeout, msg, yieldThread); |
118 | 138 | } |
| 139 | + /// <summary> |
| 140 | + /// Evaluates the given function after the supplied delay. |
| 141 | + /// The current thread will yield at the start of the delay. |
| 142 | + /// </summary> |
| 143 | + /// <param name="func">The function to evaluate.</param> |
| 144 | + /// <param name="delay">A delay timeSpan.</param> |
| 145 | + /// <param name="cancellationToken">A token to cancel evaluation, TaskCanceledException will be thrown.</param> |
| 146 | + public static async Task<bool> EvaluateAfterDelay(Func<bool> func, TimeSpan delay, CancellationToken cancellationToken = default) |
| 147 | + { |
| 148 | + await Task.Delay(delay, cancellationToken); |
| 149 | + return func(); |
| 150 | + } |
119 | 151 | } |
120 | 152 | } |
0 commit comments