/// <summary> /// Flip a phase in the <see cref="WriterReaderPhaser"/> instance, <see cref="FlipPhase(System.TimeSpan)"/> can only be called while holding the <see cref="ReaderLock()"/>. /// </summary> /// <param name="yieldPeriod">The amount of time to sleep in each yield if yield loop is needed.</param> /// <remarks> /// <seealso cref="FlipPhase(System.TimeSpan)"/> will return only after all writer critical sections (protected by <see cref="WriterCriticalSectionEnter()"/> and <see cref="WriterCriticalSectionExit(long)"/> that may have been in flight when the <see cref="FlipPhase(System.TimeSpan)"/> call were made had completed. /// <para> /// No actual writer critical section activity is required for <see cref="FlipPhase(System.TimeSpan)"/> to succeed. /// </para> /// <para> /// However, <see cref="FlipPhase(System.TimeSpan)"/> is lock-free with respect to calls to <see cref="WriterCriticalSectionEnter()"/> and <see cref="WriterCriticalSectionExit(long)"/>. /// It may spin-wait for for active writer critical section code to complete. /// </para> /// </remarks> public void FlipPhase(TimeSpan yieldPeriod) { if (!Monitor.IsEntered(_readerLock)) { throw new InvalidOperationException("FlipPhase can only be called while holding the reader lock"); } var isNextPhaseEven = (_startEpoch < 0); // Current phase is odd... long initialStartValue; // First, clear currently unused [next] phase end epoch (to proper initial value for phase): if (isNextPhaseEven) { initialStartValue = 0; WriterReaderPhaser.LazySet(ref _evenEndEpoch, initialStartValue); } else { initialStartValue = long.MinValue; WriterReaderPhaser.LazySet(ref _oddEndEpoch, initialStartValue); } // Next, reset start value, indicating new phase, and retain value at flip: //long startValueAtFlip = startEpochUpdater.getAndSet(this, initialStartValue); long startValueAtFlip = WriterReaderPhaser.GetAndSet(ref _startEpoch, initialStartValue); // Now, spin until previous phase end value catches up with start value at flip: bool caughtUp = false; do { if (isNextPhaseEven) { caughtUp = (_oddEndEpoch == startValueAtFlip); } else { caughtUp = (_evenEndEpoch == startValueAtFlip); } if (!caughtUp) { //TODO: Revist this and check if a SpinWiat is actually preferable here? -LC if (yieldPeriod == TimeSpan.Zero) { Task.Yield().GetAwaiter().GetResult(); } else { //Thread.Sleep(yieldPeriod); Task.Delay(yieldPeriod).GetAwaiter().GetResult(); } } } while (!caughtUp); }
/// <summary> /// Indicate exit from a critical section containing a write operation. /// </summary> /// <param name="criticalValueAtEnter">the opaque value (token) returned from the matching <see cref="WriterCriticalSectionEnter()"/> call.</param> /// <remarks> /// This call is wait-free on architectures that support wait free atomic increment operations, and is lock-free on architectures that do not. /// <para> /// <see cref="WriterCriticalSectionExit(long)"/> must be matched with a preceding <see cref="WriterCriticalSectionEnter"/> call, and must be provided with the matching <see cref="WriterCriticalSectionEnter"/> call's return value, in order for CriticalSectionPhaser synchronization to function properly. /// </para> /// </remarks> public void WriterCriticalSectionExit(long criticalValueAtEnter) { if (criticalValueAtEnter < 0) { WriterReaderPhaser.GetAndIncrement(ref _oddEndEpoch); } else { WriterReaderPhaser.GetAndIncrement(ref _evenEndEpoch); } }
/// <summary> /// Indicate entry to a critical section containing a write operation. /// </summary> /// <returns> /// an (opaque) value associated with the critical section entry, /// which MUST be provided to the matching <see cref="WriterCriticalSectionExit"/> call. /// </returns> /// <remarks> /// <para> /// This call is wait-free on architectures that support wait free atomic increment operations, /// and is lock-free on architectures that do not. /// </para> /// <para> /// <see cref="WriterCriticalSectionEnter"/> must be matched with a subsequent <see cref="WriterCriticalSectionExit"/> /// in order for CriticalSectionPhaser synchronization to function properly. /// </para> /// <para> /// The <seealso cref="IDisposable"/> pattern could have been used but was not due to the high allocation count it would have incurred. /// </para> /// </remarks> public long WriterCriticalSectionEnter() { return(WriterReaderPhaser.GetAndIncrement(ref _startEpoch)); }