/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jctools.queues.atomic;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReferenceArray;
import org.jctools.queues.QueueProgressIndicators;
/**
* A single-producer multiple-consumer AtomicReferenceArray-backed queue.
* @author akarnokd
* @param <E>
*/
public final class SpmcAtomicArrayQueue<E> extends AtomicReferenceArrayQueue<E> implements QueueProgressIndicators{
private final AtomicLong consumerIndex;
private final AtomicLong producerIndex;
private final AtomicLong producerIndexCache;
public SpmcAtomicArrayQueue(int capacity) {
super(capacity);
this.consumerIndex = new AtomicLong();
this.producerIndex = new AtomicLong();
this.producerIndexCache = new AtomicLong();
}
@Override
public boolean offer(final E e) {
if (null == e) {
throw new NullPointerException();
}
final AtomicReferenceArray<E> buffer = this.buffer;
final int mask = this.mask;
final long currProducerIndex = lvProducerIndex();
final int offset = calcElementOffset(currProducerIndex, mask);
if (null != lvElement(buffer, offset)) {
long size = currProducerIndex - lvConsumerIndex();
if(size > mask) {
return false;
}
else {
// spin wait for slot to clear, buggers wait freedom
while(null != lvElement(buffer, offset));
}
}
spElement(buffer, offset, e);
// single producer, so store ordered is valid. It is also required to correctly publish the element
// and for the consumers to pick up the tail value.
soTail(currProducerIndex + 1);
return true;
}
@Override
public E poll() {
long currentConsumerIndex;
final long currProducerIndexCache = lvProducerIndexCache();
do {
currentConsumerIndex = lvConsumerIndex();
if (currentConsumerIndex >= currProducerIndexCache) {
long currProducerIndex = lvProducerIndex();
if (currentConsumerIndex >= currProducerIndex) {
return null;
} else {
svProducerIndexCache(currProducerIndex);
}
}
} while (!casHead(currentConsumerIndex, currentConsumerIndex + 1));
// consumers are gated on latest visible tail, and so can't see a null value in the queue or overtake
// and wrap to hit same location.
final int offset = calcElementOffset(currentConsumerIndex);
final AtomicReferenceArray<E> lb = buffer;
// load plain, element happens before it's index becomes visible
final E e = lpElement(lb, offset);
// store ordered, make sure nulling out is visible. Producer is waiting for this value.
soElement(lb, offset, null);
return e;
}
@Override
public E peek() {
final int mask = this.mask;
final long currProducerIndexCache = lvProducerIndexCache();
long currentConsumerIndex;
E e;
do {
currentConsumerIndex = lvConsumerIndex();
if (currentConsumerIndex >= currProducerIndexCache) {
long currProducerIndex = lvProducerIndex();
if (currentConsumerIndex >= currProducerIndex) {
return null;
} else {
svProducerIndexCache(currProducerIndex);
}
}
} while (null == (e = lvElement(calcElementOffset(currentConsumerIndex, mask))));
return e;
}
@Override
public int size() {
/*
* It is possible for a thread to be interrupted or reschedule between the read of the producer and consumer
* indices, therefore protection is required to ensure size is within valid range. In the event of concurrent
* polls/offers to this method the size is OVER estimated as we read consumer index BEFORE the producer index.
*/
long after = lvConsumerIndex();
while (true) {
final long before = after;
final long currentProducerIndex = lvProducerIndex();
after = lvConsumerIndex();
if (before == after) {
return (int) (currentProducerIndex - after);
}
}
}
@Override
public boolean isEmpty() {
// Order matters!
// Loading consumer before producer allows for producer increments after consumer index is read.
// This ensures the correctness of this method at least for the consumer thread. Other threads POV is not really
// something we can fix here.
return (lvConsumerIndex() == lvProducerIndex());
}
@Override
public long currentProducerIndex() {
return lvProducerIndex();
}
@Override
public long currentConsumerIndex() {
return lvConsumerIndex();
}
protected final long lvProducerIndexCache() {
return producerIndexCache.get();
}
protected final void svProducerIndexCache(long v) {
producerIndexCache.set(v);
}
protected final long lvConsumerIndex() {
return consumerIndex.get();
}
protected final boolean casHead(long expect, long newValue) {
return consumerIndex.compareAndSet(expect, newValue);
}
protected final long lvProducerIndex() {
return producerIndex.get();
}
protected final void soTail(long v) {
producerIndex.lazySet(v);
}
}