Question: Why is `cursor.setVolatile(nextValue)` (StoreLoad fence) needed in SingleProducerSequencer?
Author: neoremindCreated Jan 12, 2026Updated Jan 12, 2026
Context
In SingleProducerSequencer, there's a specific pattern where cursor.setVolatile(nextValue) is called with a comment indicating it provides a StoreLoad fence:
if (wrapPoint > cachedGatingSequence || cachedGatingSequence > nextValue)
{
cursor.setVolatile(nextValue); // StoreLoad fence
long minSequence = Util.getMinimumSequence(gatingSequences, nextValue);
this.cachedValue = minSequence;
if (wrapPoint > minSequence)
{
return false;
}
}This occurs in both next() and hasAvailableCapacity() methods.
Question
- Why is the
StoreLoad fencespecifically needed here? What memory ordering problem does it solve?
Because getMinimumSequence() already ensures fresh reads.
// In Util.getMinimumSequence():
public static long getMinimumSequence(final Sequence[] sequences, final long minimum) {
for (int i = 0, n = sequences.length; i < n; i++) {
long value = sequences[i].get(); // ← Each get() has acquire fence!
minimumSequence = Math.min(minimumSequence, value);
}
return minimumSequence;
}The operations in the if clause are logically independent. The producer's cursor update and the subsequent consumer sequence reads serve different purposes and don't have obvious ordering dependency.
- Is the operation necessary? The
setVolatilewithStoreLoad fenceprovides a full memory barrier to ensure ordering and visibility of the producer's cursor sequence in thenextmethod. However, this early cursor update occurs before the producer has published the sequence - consumers don't need to see this intermediate cursor value yet. I understand this logic got triggered rarely only when ring buffer goes one full iteration because of "snapshot"ed gating consumer sequence, it needs to update the tracking of the slowest consumer's sequence in order to unblock advancing producer's cursor, but the nuance here is what caught me attention and led me think deeper.
Source: LMAX-Exchange/disruptor