@@ -25,8 +25,11 @@ public class DependencyQueue<T> : IDisposable
2525 // Thing that an execution context must lock exclusively to access queue state
2626 private readonly AsyncMonitor _monitor ;
2727
28- // Whether queue state is valid
29- private bool _isValid ;
28+ // Whether the dependency graph is valid, invalid, or of unknown validity
29+ private Validity _validity ;
30+
31+ // Possible values of _validity
32+ private enum Validity { Unknown = 0 , Invalid = - 1 , Valid = + 1 }
3033
3134 /// <summary>
3235 /// Initializes a new <see cref="DependencyQueue{T}"/> instance,
@@ -40,9 +43,10 @@ public class DependencyQueue<T> : IDisposable
4043 /// </param>
4144 public DependencyQueue ( StringComparer ? comparer = null )
4245 {
43- _ready = new ( ) ;
44- _topics = new ( _comparer = comparer ?? StringComparer . Ordinal ) ;
45- _monitor = new ( ) ;
46+ _ready = new ( ) ;
47+ _topics = new ( _comparer = comparer ?? StringComparer . Ordinal ) ;
48+ _monitor = new ( ) ;
49+ _validity = Validity . Valid ;
4650 }
4751
4852 /// <summary>
@@ -165,7 +169,8 @@ internal void Enqueue(DependencyQueueEntry<T> entry)
165169 if ( entry . Requires . Count == 0 )
166170 _ready . Enqueue ( entry ) ;
167171
168- _isValid = false ;
172+ _validity = Validity . Unknown ;
173+ _monitor . PulseAll ( ) ;
169174 }
170175
171176 /// <summary>
@@ -184,6 +189,12 @@ internal void Enqueue(DependencyQueueEntry<T> entry)
184189 /// </returns>
185190 /// <remarks>
186191 /// <para>
192+ /// If <see cref="Validate"/> has not been invoked since the most
193+ /// recent modification of the queue, this method automatically
194+ /// validates the queue. If the queue is invalid, this method throws
195+ /// <see cref="InvalidDependencyQueueException"/>.
196+ /// </para>
197+ /// <para>
187198 /// This method returns only when an entry is dequeued from the queue
188199 /// or when no more entries remain to dequeue.
189200 /// </para>
@@ -207,9 +218,10 @@ internal void Enqueue(DependencyQueueEntry<T> entry)
207218 /// This method is thread-safe.
208219 /// </para>
209220 /// </remarks>
210- /// <exception cref="InvalidOperationException">
211- /// The queue state is invalid or has not been validated. Use the
212- /// <see cref="Validate"/> method and correct any errors it returns.
221+ /// <exception cref="InvalidDependencyQueueException">
222+ /// The dependency graph is invalid. The
223+ /// <see cref="InvalidDependencyQueueException.Errors"/> collection
224+ /// contains the errors found during validation.
213225 /// </exception>
214226 /// <exception cref="ObjectDisposedException">
215227 /// The queue has been disposed.
@@ -222,8 +234,7 @@ internal void Enqueue(DependencyQueueEntry<T> entry)
222234
223235 using var @lock = _monitor . Acquire ( ) ;
224236
225- if ( ! _isValid )
226- throw Errors . NotValid ( ) ;
237+ RequireValid ( ) ;
227238
228239 for ( ; ; )
229240 {
@@ -257,6 +268,12 @@ internal void Enqueue(DependencyQueueEntry<T> entry)
257268 /// </returns>
258269 /// <remarks>
259270 /// <para>
271+ /// If <see cref="Validate"/> has not been invoked since the most
272+ /// recent modification of the queue, this method automatically
273+ /// validates the queue. If the queue is invalid, this method throws
274+ /// <see cref="InvalidDependencyQueueException"/>.
275+ /// </para>
276+ /// <para>
260277 /// This method returns only when an entry is dequeued from the queue
261278 /// or when no more entries remain to dequeue.
262279 /// </para>
@@ -270,9 +287,10 @@ internal void Enqueue(DependencyQueueEntry<T> entry)
270287 /// This method is thread-safe.
271288 /// </para>
272289 /// </remarks>
273- /// <exception cref="InvalidOperationException">
274- /// The queue state is invalid or has not been validated. Use the
275- /// <see cref="Validate"/> method and correct any errors it returns.
290+ /// <exception cref="InvalidDependencyQueueException">
291+ /// The dependency graph is invalid. The
292+ /// <see cref="InvalidDependencyQueueException.Errors"/> collection
293+ /// contains the errors found during validation.
276294 /// </exception>
277295 /// <exception cref="ObjectDisposedException">
278296 /// The queue has been disposed.
@@ -302,6 +320,12 @@ internal void Enqueue(DependencyQueueEntry<T> entry)
302320 /// </returns>
303321 /// <remarks>
304322 /// <para>
323+ /// If <see cref="Validate"/> has not been invoked since the most
324+ /// recent modification of the queue, this method automatically
325+ /// validates the queue. If the queue is invalid, this method throws
326+ /// <see cref="InvalidDependencyQueueException"/>.
327+ /// </para>
328+ /// <para>
305329 /// This method returns only when an entry is dequeued from the queue
306330 /// or when no more entries remain to dequeue.
307331 /// </para>
@@ -325,9 +349,10 @@ internal void Enqueue(DependencyQueueEntry<T> entry)
325349 /// This method is thread-safe.
326350 /// </para>
327351 /// </remarks>
328- /// <exception cref="InvalidOperationException">
329- /// The queue state is invalid or has not been validated. Use the
330- /// <see cref="Validate"/> method and correct any errors it returns.
352+ /// <exception cref="InvalidDependencyQueueException">
353+ /// The dependency graph is invalid. The
354+ /// <see cref="InvalidDependencyQueueException.Errors"/> collection
355+ /// contains the errors found during validation.
331356 /// </exception>
332357 /// <exception cref="ObjectDisposedException">
333358 /// The queue has been disposed.
@@ -342,8 +367,7 @@ internal void Enqueue(DependencyQueueEntry<T> entry)
342367
343368 using var @lock = await _monitor . AcquireAsync ( cancellation ) ;
344369
345- if ( ! _isValid )
346- throw Errors . NotValid ( ) ;
370+ RequireValid ( ) ;
347371
348372 for ( ; ; )
349373 {
@@ -465,7 +489,7 @@ public void Clear()
465489
466490 _ready . Clear ( ) ;
467491 _topics . Clear ( ) ;
468- _isValid = true ;
492+ _validity = Validity . Valid ;
469493
470494 _monitor . PulseAll ( ) ;
471495 }
@@ -490,10 +514,27 @@ private DependencyQueueTopic<T> GetOrAddTopic(string name)
490514 /// </remarks>
491515 public IReadOnlyList < DependencyQueueError > Validate ( )
492516 {
493- var errors = new List < DependencyQueueError > ( ) ;
494-
495517 using var @lock = _monitor . Acquire ( ) ;
496518
519+ return ValidateCore ( ) ;
520+ }
521+
522+ private void RequireValid ( )
523+ {
524+ if ( _validity is Validity . Valid )
525+ return ;
526+
527+ var errors = ValidateCore ( ) ;
528+
529+ if ( errors . Count is 0 )
530+ return ;
531+
532+ throw Errors . QueueInvalid ( errors ) ;
533+ }
534+
535+ private IReadOnlyList < DependencyQueueError > ValidateCore ( )
536+ {
537+ var errors = new List < DependencyQueueError > ( ) ;
497538 var visited = new Dictionary < string , bool > ( _topics . Count , _comparer ) ;
498539
499540 foreach ( var topic in _topics . Values )
@@ -504,7 +545,9 @@ public IReadOnlyList<DependencyQueueError> Validate()
504545 DetectCycles ( null , topic , visited , errors ) ;
505546 }
506547
507- _isValid = errors . Count == 0 ;
548+ _validity = errors . Count is 0
549+ ? Validity . Valid
550+ : Validity . Invalid ;
508551
509552 return errors ;
510553 }
0 commit comments