/*
* 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.alt;
import static org.jctools.queues.alt.SpscArrayConcurrentQueue.OFFER_BATCH_SIZE;
import static org.jctools.util.UnsafeAccess.UNSAFE;
import org.jctools.util.UnsafeAccess;
abstract class ProducerFields<E> extends ConcurrentCircularArray<E> {
protected static final long TAIL_OFFSET;
static {
try {
TAIL_OFFSET = UnsafeAccess.UNSAFE.objectFieldOffset(ProducerFields.class
.getDeclaredField("producerIndex"));
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
}
protected long producerIndex;
protected long batchTail;
public ProducerFields(ConcurrentCircularArray<E> c) {
super(c);
}
}
final class Producer<E> extends ProducerFields<E> implements ConcurrentQueueProducer<E> {
long p00, p01, p02, p03, p04, p05, p06, p07;
long p10, p11, p12, p13, p14, p15, p16, p17;
public Producer(ConcurrentCircularArray<E> c) {
super(c);
}
@Override
public boolean offer(final E e) {
if (null == e) {
throw new NullPointerException("Null is not a valid element");
}
final E[] lb = buffer;
final long mask = this.mask;
final long pIndex = producerIndex;
if (pIndex >= batchTail) {
if (null == lvElement(lb, calcOffset(pIndex + OFFER_BATCH_SIZE, mask))) {
batchTail = pIndex + OFFER_BATCH_SIZE;
} else if (null != lvElement(lb, calcOffset(pIndex, mask))) {
return false;
}
}
soElement(lb, calcOffset(pIndex, mask), e);
soProducerIndex(pIndex + 1);
return true;
}
long getProducerIndexForSize() {
return Math.max(lvProducerIndex(), producerIndex);
}
private long lvProducerIndex() {
return UNSAFE.getLongVolatile(this, TAIL_OFFSET);
}
private void soProducerIndex(long newHead) {
UNSAFE.putOrderedLong(this, TAIL_OFFSET, newHead);
}
@Override
public boolean weakOffer(E e) {
return offer(e);
}
@Override
public int produce(ProducerFunction<E> p, int batchSize) {
final E[] lb = buffer;
final long mask = this.mask;
long pIndex = producerIndex;
final long tIndex = pIndex + batchSize - 1;
if (tIndex >= batchTail) {
if (null == lvElement(lb, calcOffset(tIndex + OFFER_BATCH_SIZE, mask))) {
batchTail = tIndex + OFFER_BATCH_SIZE;
} else {
for(int i=0;i<batchSize;i++) {
if (null != lvElement(lb, calcOffset(pIndex, mask)))
return i;
soElement(lb, calcOffset(pIndex, mask), p.produce());
soProducerIndex(++pIndex);
}
return batchSize;
}
}
soProducerIndex(pIndex + batchSize);
for(int i=0;i<batchSize;i++) {
soElement(lb, calcOffset(pIndex++, mask), p.produce());
}
return batchSize;
}
}
abstract class ConsumerFields<E> extends ConcurrentCircularArray<E> {
protected static final long HEAD_OFFSET;
static {
try {
HEAD_OFFSET = UnsafeAccess.UNSAFE
.objectFieldOffset(ConsumerFields.class.getDeclaredField("head"));
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
}
protected long head = 0;
public ConsumerFields(ConcurrentCircularArray<E> c) {
super(c);
}
}
final class Consumer<E> extends ConsumerFields<E> implements ConcurrentQueueConsumer<E> {
long p00, p01, p02, p03, p04, p05, p06, p07;
long p10, p11, p12, p13, p14, p15, p16, p17;
Consumer(ConcurrentCircularArray<E> c) {
super(c);
}
@Override
public E poll() {
final long head = this.head;
final long offset = calcOffset(head);
final E[] lb = buffer;
final E e = lvElement(lb, offset);
if (null == e) {
return null;
}
soElement(lb, offset, null);
soHead(head + 1);
return e;
}
@Override
public E peek() {
return lpElement(calcOffset(head));
}
@Override
public void clear() {
while (null != poll())
;
}
long getHeadForSize() {
return Math.max(lvHead(), head);
}
private long lvHead() {
return UNSAFE.getLongVolatile(this, HEAD_OFFSET);
}
private void soHead(long newHead) {
UNSAFE.putOrderedLong(this, HEAD_OFFSET, newHead);
}
@Override
public int consume(ConsumerFunction<E> c, int batch) {
final E[] lb = buffer;
long currHead = head;
long offset = calcOffset(currHead);
E e = lvElement(lb, offset);
int i = 0;
for (; i < batch && null != e; i++) {
soElement(lb, offset, null);
soHead(++currHead);
c.consume(e); // NOTE: we've committed to consuming the batch, no check.
offset = calcOffset(currHead);
e = lvElement(lb, offset);
}
return i;
}
@Override
public E weakPoll() {
return poll();
}
@Override
public E weakPeek() {
return peek();
}
}
abstract class SpscArrayConcurrentQueueColdFields<E> extends ConcurrentCircularArray<E> {
protected final Consumer<E> consumer;
protected final Producer<E> producer;
public SpscArrayConcurrentQueueColdFields(int capacity) {
super(capacity);
consumer = new Consumer<E>(this);
producer = new Producer<E>(this);
}
}
public final class SpscArrayConcurrentQueue<E> extends SpscArrayConcurrentQueueColdFields<E> implements
ConcurrentQueue<E> {
// Layout field/data offsets are runtime constants
protected static final int OFFER_BATCH_SIZE = Integer.getInteger("offer.batch.size", 4096);
// post pad queue fields
long p00, p01, p02, p03, p04, p05, p06, p07;
long p10, p11, p12, p13, p14, p15, p16, p17;
public SpscArrayConcurrentQueue(final int capacity) {
super(Math.max(capacity, 2 * OFFER_BATCH_SIZE));
}
@Override
public int size() {
long headForSize = consumer.getHeadForSize();
long tailForSize = producer.getProducerIndexForSize();
return (int) (tailForSize - headForSize);
}
@Override
public int capacity() {
return (int) mask + 1;
}
@Override
public ConcurrentQueueConsumer<E> consumer() {
return consumer;
}
@Override
public ConcurrentQueueProducer<E> producer() {
return producer;
}
}