/*
* 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;
import static org.jctools.util.UnsafeAccess.UNSAFE;
import org.jctools.queues.MessagePassingQueue.Consumer;
import org.jctools.queues.MessagePassingQueue.ExitCondition;
import org.jctools.queues.MessagePassingQueue.Supplier;
import org.jctools.queues.MessagePassingQueue.WaitStrategy;
import org.jctools.util.UnsafeRefArrayAccess;
abstract class MpscSequencedArrayQueueL1Pad<E> extends ConcurrentSequencedCircularArrayQueue<E> {
long p10, p11, p12, p13, p14, p15, p16;
long p30, p31, p32, p33, p34, p35, p36, p37;
public MpscSequencedArrayQueueL1Pad(int capacity) {
super(capacity);
}
}
abstract class MpscSequencedArrayQueueProducerField<E> extends MpscSequencedArrayQueueL1Pad<E> {
private final static long P_INDEX_OFFSET;
static {
try {
P_INDEX_OFFSET =
UNSAFE.objectFieldOffset(MpscSequencedArrayQueueProducerField.class.getDeclaredField("producerIndex"));
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
}
private volatile long producerIndex;
public MpscSequencedArrayQueueProducerField(int capacity) {
super(capacity);
}
public final long lvProducerIndex() {
return producerIndex;
}
protected final boolean casProducerIndex(long expect, long newValue) {
return UNSAFE.compareAndSwapLong(this, P_INDEX_OFFSET, expect, newValue);
}
}
abstract class MpscSequencedArrayQueueL2Pad<E> extends MpscSequencedArrayQueueProducerField<E> {
long p20, p21, p22, p23, p24, p25, p26;
long p30, p31, p32, p33, p34, p35, p36, p37;
public MpscSequencedArrayQueueL2Pad(int capacity) {
super(capacity);
}
}
abstract class MpscSequencedArrayQueueConsumerField<E> extends MpscSequencedArrayQueueL2Pad<E> {
private final static long C_INDEX_OFFSET;
static {
try {
C_INDEX_OFFSET =
UNSAFE.objectFieldOffset(MpscSequencedArrayQueueConsumerField.class.getDeclaredField("consumerIndex"));
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
}
protected long consumerIndex;
public MpscSequencedArrayQueueConsumerField(int capacity) {
super(capacity);
}
public final long lvConsumerIndex() {
return UNSAFE.getLongVolatile(this, C_INDEX_OFFSET);
}
protected final long lpConsumerIndex() {
return consumerIndex;
}
protected final void soConsumerIndex(long v) {
UNSAFE.putOrderedLong(this, C_INDEX_OFFSET, v);
}
}
/**
* A Multi-Producer-Single-Consumer queue based on same algorithm used for {@link MpmcArrayQueue} but with the
* appropriate weakening of constraints on offer. The trade off does not seem worth while compared to the simpler
* {@link MpscArrayQueue}.
*
* @param <E> type of the element stored in the {@link java.util.Queue}
*/
public class MpscSequencedArrayQueue<E> extends MpscSequencedArrayQueueConsumerField<E> {
long p40, p41, p42, p43, p44, p45, p46;
long p30, p31, p32, p33, p34, p35, p36, p37;
public MpscSequencedArrayQueue(final int capacity) {
super(Math.max(2, capacity));
}
@Override
public boolean offer(final E e) {
if (null == e) {
throw new NullPointerException("Null is not a valid element");
}
// local load of field to avoid repeated loads after volatile reads
final long[] lSequenceBuffer = sequenceBuffer;
long currentProducerIndex;
long seqOffset;
while (true) {
currentProducerIndex = lvProducerIndex(); // LoadLoad
seqOffset = calcSequenceOffset(currentProducerIndex);
final long seq = lvSequence(lSequenceBuffer, seqOffset); // LoadLoad
final long delta = seq - currentProducerIndex;
if (delta == 0) {
// this is expected if we see this first time around
if (casProducerIndex(currentProducerIndex, currentProducerIndex + 1)) {
// Successful CAS: full barrier
break;
}
// failed cas, retry 1
} else if (delta < 0) {
// poll has not moved this value forward
return false;
}
// another producer has moved the sequence by one, retry 2
}
// on 64bit(no compressed oops) JVM this is the same as seqOffset
final long elementOffset = calcElementOffset(currentProducerIndex);
UnsafeRefArrayAccess.spElement(buffer, elementOffset, e);
// increment sequence by 1, the value expected by consumer
// (seeing this value from a producer will lead to retry 2)
soSequence(lSequenceBuffer, seqOffset, currentProducerIndex + 1); // StoreStore
return true;
}
@Override
public E poll() {
// local load of field to avoid repeated loads after volatile reads
final long[] lSequenceBuffer = sequenceBuffer;
long consumerIndex = lvConsumerIndex();// LoadLoad
final long seqOffset = calcSequenceOffset(consumerIndex);
final long seq = lvSequence(lSequenceBuffer, seqOffset);// LoadLoad
final long delta = seq - (consumerIndex + 1);
if (delta < 0) {
// queue is empty
return null;
}
// on 64bit(no compressed oops) JVM this is the same as seqOffset
final long offset = calcElementOffset(consumerIndex);
final E e = UnsafeRefArrayAccess.lpElement(buffer, offset);
UnsafeRefArrayAccess.spElement(buffer, offset, null);
// Move sequence ahead by capacity, preparing it for next offer
// (seeing this value from a consumer will lead to retry 2)
soSequence(lSequenceBuffer, seqOffset, consumerIndex + mask + 1);// StoreStore
soConsumerIndex(consumerIndex+1);
return e;
}
@Override
public E peek() {
return UnsafeRefArrayAccess.lpElement(buffer, calcElementOffset(lvConsumerIndex()));
}
@Override
public boolean relaxedOffer(E message) {
return offer(message);
}
@Override
public E relaxedPoll() {
return poll();
}
@Override
public E relaxedPeek() {
return peek();
}
@Override
public int drain(Consumer<E> c) {
final int limit = capacity();
return drain(c,limit);
}
@Override
public int fill(Supplier<E> s) {
throw new UnsupportedOperationException();
}
@Override
public int drain(Consumer<E> c, int limit) {
for (int i=0;i<limit;i++) {
E e = relaxedPoll();
if(e==null){
return i;
}
c.accept(e);
}
return limit;
}
@Override
public int fill(Supplier<E> s, int limit) {
throw new UnsupportedOperationException();
}
@Override
public void drain(Consumer<E> c,
WaitStrategy wait,
ExitCondition exit) {
int idleCounter = 0;
while (exit.keepRunning()) {
E e = relaxedPoll();
if(e==null){
idleCounter = wait.idle(idleCounter);
continue;
}
idleCounter = 0;
c.accept(e);
}
}
@Override
public void fill(Supplier<E> s,
WaitStrategy wait,
ExitCondition exit) {
int idleCounter = 0;
while (exit.keepRunning()) {
E e = s.get();
while (!relaxedOffer(e)) {
idleCounter = wait.idle(idleCounter);
continue;
}
idleCounter = 0;
}
}
}