com.couchbase.client.core.deps.org.HdrHistogram.WriterReaderPhaser Maven / Gradle / Ivy
Show all versions of core-io Show documentation
/**
* Written by Gil Tene of Azul Systems, and released to the public domain,
* as explained at http://creativecommons.org/publicdomain/zero/1.0/
*/
package com.couchbase.client.core.deps.org.HdrHistogram;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
import java.util.concurrent.locks.ReentrantLock;
/**
* {@link WriterReaderPhaser} provides an asymmetric means for
* synchronizing the execution of wait-free "writer" critical sections against
* a "reader phase flip" that needs to make sure no writer critical sections
* that were active at the beginning of the flip are still active after the
* flip is done. Multiple writers and multiple readers are supported.
*
* Using a {@link WriterReaderPhaser} for coordination, writers can continously
* perform wait-free/lock-free updates to common data structures, while readers
* can get hold of atomic and inactive snapshots without stalling writers.
*
* While a {@link WriterReaderPhaser} can be useful in multiple scenarios, a
* specific and common use case is that of safely managing "double buffered"
* data stream access in which writers can proceed without being blocked, while
* readers gain access to stable and unchanging buffer samples.
* {@link WriterReaderPhaser} "writers" are wait free (on architectures that support
* wait free atomic increment operations), "readers" block for other
* "readers", and "readers" are only blocked by "writers" whose critical section
* was entered before the reader's
* {@link WriterReaderPhaser#flipPhase()} attempt.
*
Assumptions and Guarantees
*
* When used to protect an actively recording data structure, the assumptions on
* how readers and writers act are:
*
* - There are two sets of data structures ("active" and "inactive")
* - Writing is done to the perceived active version (as perceived by the
* writer), and only within critical sections delineated by
* {@link WriterReaderPhaser#writerCriticalSectionEnter} and
* {@link WriterReaderPhaser#writerCriticalSectionExit writerCriticalSectionExit()}.
*
* - Only readers switch the perceived roles of the active and inactive data
* structures. They do so only while under {@link WriterReaderPhaser#readerLock()}
* protection and only before calling {@link WriterReaderPhaser#flipPhase()}.
* - Writers do not remain in their critical sections indefinitely.
* - Only writers perform {@link WriterReaderPhaser#writerCriticalSectionEnter}
* and
* {@link WriterReaderPhaser#writerCriticalSectionExit writerCriticalSectionExit()}.
*
* - Readers do not hold onto readerLock indefinitely.
* - Only readers perform {@link WriterReaderPhaser#readerLock()} and
* {@link WriterReaderPhaser#readerUnlock()}.
* - Only readers perform {@link WriterReaderPhaser#flipPhase()} operations,
* and only while holding the readerLock.
*
*
* When the above assumptions are met, {@link WriterReaderPhaser} guarantees
* that the inactive data structures are not being modified by any writers while
* being read while under readerLock() protection after a
* {@link WriterReaderPhaser#flipPhase()}() operation.
*
* The following progress guarantees are provided to writers and readers that
* adhere to the above stated assumptions:
*
* - Writers operations
* ({@link WriterReaderPhaser#writerCriticalSectionEnter writerCriticalSectionEnter}
* and {@link WriterReaderPhaser#writerCriticalSectionExit writerCriticalSectionExit})
* are wait free on architectures that
* support wait-free atomic increment operations (they remain lock-free [but not
* wait-free] on architectures that do not support wait-free atomic increment
* operations)
* - {@link WriterReaderPhaser#flipPhase()} operations are guaranteed to
* make forward progress, and will only be blocked by writers whose critical sections
* were entered prior to the start of the reader's flipPhase operation, and have not
* yet exited their critical sections.
* - {@link WriterReaderPhaser#readerLock()} only blocks for other
* readers that are holding the readerLock.
*
*
* Example use
* Imagine a simple use case where a histogram (which is basically a large set of
* rapidly updated counters) is being modified by writers, and a reader needs to gain
* access to stable interval samples of the histogram for reporting or other analysis
* purposes.
*
* final WriterReaderPhaser recordingPhaser = new WriterReaderPhaser();
*
* volatile Histogram activeHistogram;
* Histogram inactiveHistogram;
* ...
*
* A writer may record values the histogram:
*
* // Wait-free recording:
* long criticalValueAtEnter = recordingPhaser.writerCriticalSectionEnter();
* try {
* activeHistogram.recordValue(value);
* } finally {
* recordingPhaser.writerCriticalSectionExit(criticalValueAtEnter);
* }
*
* A reader gains access to a stable histogram of values recorded during an interval,
* and reports on it:
*
* try {
* recordingPhaser.readerLock();
*
* inactiveHistogram.reset();
*
* // Swap active and inactive histograms:
* final Histogram tempHistogram = inactiveHistogram;
* inactiveHistogram = activeHistogram;
* activeHistogram = tempHistogram;
*
* recordingPhaser.flipPhase();
* // At this point, inactiveHistogram content is guaranteed to be stable
*
* logHistogram(inactiveHistogram);
*
* } finally {
* recordingPhaser.readerUnlock();
* }
*
*/
/*
* High level design: There are even and odd epochs; the epoch flips for each
* reader. Any number of writers can be in the same epoch (odd or even), but
* after a completed phase flip no writers will be still in the old epoch
* (and therefor are known to not be updating or observing the old, inactive
* data structure). Writers can always proceed at full speed in what they
* percieve to be the current (odd or even) epoch. The epoch flip is fast (a
* single atomic op).
*/
public class WriterReaderPhaser {
private volatile long startEpoch = 0;
private volatile long evenEndEpoch = 0;
private volatile long oddEndEpoch = Long.MIN_VALUE;
private final ReentrantLock readerLock = new ReentrantLock();
private static final AtomicLongFieldUpdater startEpochUpdater =
AtomicLongFieldUpdater.newUpdater(WriterReaderPhaser.class, "startEpoch");
private static final AtomicLongFieldUpdater evenEndEpochUpdater =
AtomicLongFieldUpdater.newUpdater(WriterReaderPhaser.class, "evenEndEpoch");
private static final AtomicLongFieldUpdater oddEndEpochUpdater =
AtomicLongFieldUpdater.newUpdater(WriterReaderPhaser.class, "oddEndEpoch");
/**
* Indicate entry to a critical section containing a write operation.
*
* This call is wait-free on architectures that support wait free atomic increment operations,
* and is lock-free on architectures that do not.
*
* {@code writerCriticalSectionEnter()} must be matched with a subsequent
* {@link WriterReaderPhaser#writerCriticalSectionExit(long)} in order for CriticalSectionPhaser
* synchronization to function properly.
*
* @return an (opaque) value associated with the critical section entry, which MUST be provided
* to the matching {@link WriterReaderPhaser#writerCriticalSectionExit} call.
*/
public long writerCriticalSectionEnter() {
return startEpochUpdater.getAndIncrement(this);
}
/**
* Indicate exit from a critical section containing a write operation.
*
* This call is wait-free on architectures that support wait free atomic increment operations,
* and is lock-free on architectures that do not.
*
* {@code writerCriticalSectionExit(long)} must be matched with a preceding
* {@link WriterReaderPhaser#writerCriticalSectionEnter()} call, and must be provided with the
* matching {@link WriterReaderPhaser#writerCriticalSectionEnter()} call's return value, in
* order for CriticalSectionPhaser synchronization to function properly.
*
* @param criticalValueAtEnter the (opaque) value returned from the matching
* {@link WriterReaderPhaser#writerCriticalSectionEnter()} call.
*/
public void writerCriticalSectionExit(long criticalValueAtEnter) {
(criticalValueAtEnter < 0 ? oddEndEpochUpdater : evenEndEpochUpdater).getAndIncrement(this);
}
/**
* Enter to a critical section containing a read operation (reentrant, mutually excludes against
* {@link WriterReaderPhaser#readerLock} calls by other threads).
*
* {@link WriterReaderPhaser#readerLock} DOES NOT provide synchronization
* against {@link WriterReaderPhaser#writerCriticalSectionEnter()} calls. Use {@link WriterReaderPhaser#flipPhase()}
* to synchronize reads against writers.
*/
public void readerLock() {
readerLock.lock();
}
/**
* Exit from a critical section containing a read operation (relinquishes mutual exclusion against other
* {@link WriterReaderPhaser#readerLock} calls).
*/
public void readerUnlock() {
readerLock.unlock();
}
/**
* Flip a phase in the {@link WriterReaderPhaser} instance, {@link WriterReaderPhaser#flipPhase()}
* can only be called while holding the {@link WriterReaderPhaser#readerLock() readerLock}.
* {@link WriterReaderPhaser#flipPhase()} will return only after all writer critical sections (protected by
* {@link WriterReaderPhaser#writerCriticalSectionEnter() writerCriticalSectionEnter} and
* {@link WriterReaderPhaser#writerCriticalSectionExit writerCriticalSectionEnter}) that may have been
* in flight when the {@link WriterReaderPhaser#flipPhase()} call were made had completed.
*
* No actual writer critical section activity is required for {@link WriterReaderPhaser#flipPhase()} to
* succeed.
*
* However, {@link WriterReaderPhaser#flipPhase()} is lock-free with respect to calls to
* {@link WriterReaderPhaser#writerCriticalSectionEnter()} and
* {@link WriterReaderPhaser#writerCriticalSectionExit writerCriticalSectionExit()}. It may spin-wait
* or for active writer critical section code to complete.
*
* @param yieldTimeNsec The amount of time (in nanoseconds) to sleep in each yield if yield loop is needed.
*/
public void flipPhase(long yieldTimeNsec) {
if (!readerLock.isHeldByCurrentThread()) {
throw new IllegalStateException("flipPhase() can only be called while holding the readerLock()");
}
// Read the volatile 'startEpoch' exactly once
boolean nextPhaseIsEven = (startEpoch < 0); // Current phase is odd...
// First, clear currently unused [next] phase end epoch (to proper initial value for phase):
long initialStartValue = nextPhaseIsEven ? 0 : Long.MIN_VALUE;
(nextPhaseIsEven ? evenEndEpochUpdater : oddEndEpochUpdater).lazySet(this, initialStartValue);
// Next, reset start value, indicating new phase, and retain value at flip:
long startValueAtFlip = startEpochUpdater.getAndSet(this, initialStartValue);
// Now, spin until previous phase end value catches up with start value at flip:
while((nextPhaseIsEven ? oddEndEpoch : evenEndEpoch) != startValueAtFlip) {
if (yieldTimeNsec == 0) {
Thread.yield();
} else {
try {
TimeUnit.NANOSECONDS.sleep(yieldTimeNsec);
} catch (InterruptedException ex) {
// nothing to do here, we just woke up earlier that expected.
}
}
}
}
/**
* Flip a phase in the {@link WriterReaderPhaser} instance, {@code flipPhase()}
* can only be called while holding the {@link WriterReaderPhaser#readerLock() readerLock}.
* {@code flipPhase()} will return only after all writer critical sections (protected by
* {@link WriterReaderPhaser#writerCriticalSectionEnter() writerCriticalSectionEnter} and
* {@link WriterReaderPhaser#writerCriticalSectionExit writerCriticalSectionEnter}) that may have been
* in flight when the {@code flipPhase()} call were made had completed.
*
* No actual writer critical section activity is required for {@code flipPhase()} to
* succeed.
*
* However, {@code flipPhase()} is lock-free with respect to calls to
* {@link WriterReaderPhaser#writerCriticalSectionEnter()} and
* {@link WriterReaderPhaser#writerCriticalSectionExit writerCriticalSectionExit()}. It may spin-wait
* or for active writer critical section code to complete.
*/
public void flipPhase() {
flipPhase(0);
}
}