org.jctools.queues.MpscBlockingConsumerArrayQueue Maven / Gradle / Ivy
/*
* 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 java.util.AbstractQueue;
import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
import org.jctools.queues.IndexedQueueSizeUtil.IndexedQueue;
import org.jctools.util.Pow2;
import org.jctools.util.RangeUtil;
import static org.jctools.queues.LinkedArrayQueueUtil.modifiedCalcCircularRefElementOffset;
import static org.jctools.util.UnsafeAccess.UNSAFE;
import static org.jctools.util.UnsafeAccess.fieldOffset;
import static org.jctools.util.UnsafeRefArrayAccess.*;
@SuppressWarnings("unused")
abstract class MpscBlockingConsumerArrayQueuePad1 extends AbstractQueue implements IndexedQueue
{
byte b000,b001,b002,b003,b004,b005,b006,b007;// 8b
byte b010,b011,b012,b013,b014,b015,b016,b017;// 16b
byte b020,b021,b022,b023,b024,b025,b026,b027;// 24b
byte b030,b031,b032,b033,b034,b035,b036,b037;// 32b
byte b040,b041,b042,b043,b044,b045,b046,b047;// 40b
byte b050,b051,b052,b053,b054,b055,b056,b057;// 48b
byte b060,b061,b062,b063,b064,b065,b066,b067;// 56b
byte b070,b071,b072,b073,b074,b075,b076,b077;// 64b
byte b100,b101,b102,b103,b104,b105,b106,b107;// 72b
byte b110,b111,b112,b113,b114,b115,b116,b117;// 80b
byte b120,b121,b122,b123,b124,b125,b126,b127;// 88b
byte b130,b131,b132,b133,b134,b135,b136,b137;// 96b
byte b140,b141,b142,b143,b144,b145,b146,b147;//104b
byte b150,b151,b152,b153,b154,b155,b156,b157;//112b
byte b160,b161,b162,b163,b164,b165,b166,b167;//120b
byte b170,b171,b172,b173,b174,b175,b176,b177;//128b
}
// $gen:ordered-fields
abstract class MpscBlockingConsumerArrayQueueColdProducerFields extends MpscBlockingConsumerArrayQueuePad1
{
private final static long P_LIMIT_OFFSET = fieldOffset(MpscBlockingConsumerArrayQueueColdProducerFields.class,"producerLimit");
private volatile long producerLimit;
protected final long producerMask;
protected final E[] producerBuffer;
MpscBlockingConsumerArrayQueueColdProducerFields(long producerMask, E[] producerBuffer)
{
this.producerMask = producerMask;
this.producerBuffer = producerBuffer;
}
final long lvProducerLimit()
{
return producerLimit;
}
final boolean casProducerLimit(long expect, long newValue)
{
return UNSAFE.compareAndSwapLong(this, P_LIMIT_OFFSET, expect, newValue);
}
final void soProducerLimit(long newValue)
{
UNSAFE.putOrderedLong(this, P_LIMIT_OFFSET, newValue);
}
}
@SuppressWarnings("unused")
abstract class MpscBlockingConsumerArrayQueuePad2 extends MpscBlockingConsumerArrayQueueColdProducerFields
{
byte b000,b001,b002,b003,b004,b005,b006,b007;// 8b
byte b010,b011,b012,b013,b014,b015,b016,b017;// 16b
byte b020,b021,b022,b023,b024,b025,b026,b027;// 24b
byte b030,b031,b032,b033,b034,b035,b036,b037;// 32b
byte b040,b041,b042,b043,b044,b045,b046,b047;// 40b
byte b050,b051,b052,b053,b054,b055,b056,b057;// 48b
byte b060,b061,b062,b063,b064,b065,b066,b067;// 56b
// byte b070,b071,b072,b073,b074,b075,b076,b077;// 64b
MpscBlockingConsumerArrayQueuePad2(long mask, E[] buffer)
{
super(mask, buffer);
}
}
// $gen:ordered-fields
abstract class MpscBlockingConsumerArrayQueueProducerFields extends MpscBlockingConsumerArrayQueuePad2
{
private final static long P_INDEX_OFFSET = fieldOffset(MpscBlockingConsumerArrayQueueProducerFields.class, "producerIndex");
private volatile long producerIndex;
MpscBlockingConsumerArrayQueueProducerFields(long mask, E[] buffer)
{
super(mask, buffer);
}
@Override
public final long lvProducerIndex()
{
return producerIndex;
}
final void soProducerIndex(long newValue)
{
UNSAFE.putOrderedLong(this, P_INDEX_OFFSET, newValue);
}
final boolean casProducerIndex(long expect, long newValue)
{
return UNSAFE.compareAndSwapLong(this, P_INDEX_OFFSET, expect, newValue);
}
}
@SuppressWarnings("unused")
abstract class MpscBlockingConsumerArrayQueuePad3 extends MpscBlockingConsumerArrayQueueProducerFields
{
byte b000,b001,b002,b003,b004,b005,b006,b007;// 8b
byte b010,b011,b012,b013,b014,b015,b016,b017;// 16b
byte b020,b021,b022,b023,b024,b025,b026,b027;// 24b
byte b030,b031,b032,b033,b034,b035,b036,b037;// 32b
byte b040,b041,b042,b043,b044,b045,b046,b047;// 40b
byte b050,b051,b052,b053,b054,b055,b056,b057;// 48b
byte b060,b061,b062,b063,b064,b065,b066,b067;// 56b
byte b070,b071,b072,b073,b074,b075,b076,b077;// 64b
byte b100,b101,b102,b103,b104,b105,b106,b107;// 72b
byte b110,b111,b112,b113,b114,b115,b116,b117;// 80b
byte b120,b121,b122,b123,b124,b125,b126,b127;// 88b
byte b130,b131,b132,b133,b134,b135,b136,b137;// 96b
byte b140,b141,b142,b143,b144,b145,b146,b147;//104b
byte b150,b151,b152,b153,b154,b155,b156,b157;//112b
byte b160,b161,b162,b163,b164,b165,b166,b167;//120b
byte b170,b171,b172,b173,b174,b175,b176,b177;//128b
MpscBlockingConsumerArrayQueuePad3(long mask, E[] buffer)
{
super(mask, buffer);
}
}
// $gen:ordered-fields
abstract class MpscBlockingConsumerArrayQueueConsumerFields extends MpscBlockingConsumerArrayQueuePad3
{
private final static long C_INDEX_OFFSET = fieldOffset(MpscBlockingConsumerArrayQueueConsumerFields.class,"consumerIndex");
private final static long BLOCKED_OFFSET = fieldOffset(MpscBlockingConsumerArrayQueueConsumerFields.class,"blocked");
private volatile long consumerIndex;
protected final long consumerMask;
private volatile Thread blocked;
protected final E[] consumerBuffer;
MpscBlockingConsumerArrayQueueConsumerFields(long mask, E[] buffer)
{
super(mask, buffer);
consumerMask = mask;
consumerBuffer = buffer;
}
@Override
public final long lvConsumerIndex()
{
return consumerIndex;
}
final long lpConsumerIndex()
{
return UNSAFE.getLong(this, C_INDEX_OFFSET);
}
final void soConsumerIndex(long newValue)
{
UNSAFE.putOrderedLong(this, C_INDEX_OFFSET, newValue);
}
final Thread lvBlocked()
{
return blocked;
}
/**
* This field should only be written to from the consumer thread. It is set before parking the consumer and nulled
* when the consumer is unblocked. The value is read by producer thread to unpark the consumer.
*
* @param thread the consumer thread which is blocked waiting for the producers
*/
final void soBlocked(Thread thread)
{
UNSAFE.putOrderedObject(this, BLOCKED_OFFSET, thread);
}
}
/**
* This is a partial implementation of the {@link java.util.concurrent.BlockingQueue} on the consumer side only on top
* of the mechanics described in {@link BaseMpscLinkedArrayQueue}, but with the reservation bit used for blocking rather
* than resizing in this instance.
*/
@SuppressWarnings("unused")
public class MpscBlockingConsumerArrayQueue extends MpscBlockingConsumerArrayQueueConsumerFields
implements MessagePassingQueue, QueueProgressIndicators, BlockingQueue
{
byte b000,b001,b002,b003,b004,b005,b006,b007;// 8b
byte b010,b011,b012,b013,b014,b015,b016,b017;// 16b
byte b020,b021,b022,b023,b024,b025,b026,b027;// 24b
byte b030,b031,b032,b033,b034,b035,b036,b037;// 32b
byte b040,b041,b042,b043,b044,b045,b046,b047;// 40b
byte b050,b051,b052,b053,b054,b055,b056,b057;// 48b
byte b060,b061,b062,b063,b064,b065,b066,b067;// 56b
byte b070,b071,b072,b073,b074,b075,b076,b077;// 64b
byte b100,b101,b102,b103,b104,b105,b106,b107;// 72b
byte b110,b111,b112,b113,b114,b115,b116,b117;// 80b
byte b120,b121,b122,b123,b124,b125,b126,b127;// 88b
byte b130,b131,b132,b133,b134,b135,b136,b137;// 96b
byte b140,b141,b142,b143,b144,b145,b146,b147;//104b
byte b150,b151,b152,b153,b154,b155,b156,b157;//112b
byte b160,b161,b162,b163,b164,b165,b166,b167;//120b
byte b170,b171,b172,b173,b174,b175,b176,b177;//128b
public MpscBlockingConsumerArrayQueue(final int capacity)
{
// leave lower bit of mask clear
super((Pow2.roundToPowerOfTwo(capacity) - 1) << 1, (E[]) allocateRefArray(Pow2.roundToPowerOfTwo(capacity)));
RangeUtil.checkGreaterThanOrEqual(capacity, 1, "capacity");
soProducerLimit((Pow2.roundToPowerOfTwo(capacity) - 1) << 1); // we know it's all empty to start with
}
@Override
public final Iterator iterator()
{
throw new UnsupportedOperationException();
}
@Override
public final int size()
{
return IndexedQueueSizeUtil.size(this, IndexedQueueSizeUtil.IGNORE_PARITY_DIVISOR);
}
@Override
public final boolean isEmpty()
{
// Order matters!
// Loading consumer before producer allows for producer increments after consumer index is read.
// This ensures this method is conservative in it's estimate. Note that as this is an MPMC there is
// nothing we can do to make this an exact method.
return ((this.lvConsumerIndex()/2) == (this.lvProducerIndex()/2));
}
@Override
public String toString()
{
return this.getClass().getName();
}
/**
* {@link #offer} if {@link #size()} is less than threshold.
*
* @param e the object to offer onto the queue, not null
* @param threshold the maximum allowable size
* @return true if the offer is successful, false if queue size exceeds threshold
* @since 3.0.1
*/
public boolean offerIfBelowThreshold(final E e, int threshold)
{
if (null == e)
{
throw new NullPointerException();
}
final long mask = this.producerMask;
final long capacity = mask + 2;
threshold = threshold << 1;
final E[] buffer = this.producerBuffer;
long pIndex;
while (true)
{
pIndex = lvProducerIndex();
// lower bit is indicative of blocked consumer
if ((pIndex & 1) == 1)
{
if (offerAndWakeup(buffer, mask, pIndex, e)) {
return true;
}
continue;
}
// pIndex is even (lower bit is 0) -> actual index is (pIndex >> 1), consumer is awake
final long producerLimit = lvProducerLimit();
// Use producer limit to save a read of the more rapidly mutated consumer index.
// Assumption: queue is usually empty or near empty
// available is also << 1
final long available = producerLimit - pIndex;
// sizeEstimate <= size
final long sizeEstimate = capacity - available;
if (sizeEstimate >= threshold ||
// producerLimit check allows for threshold >= capacity
producerLimit <= pIndex)
{
if (!recalculateProducerLimit(pIndex, producerLimit, lvConsumerIndex(), capacity, threshold))
{
return false;
}
}
// Claim the index
if (casProducerIndex(pIndex, pIndex + 2))
{
break;
}
}
final long offset = modifiedCalcCircularRefElementOffset(pIndex, mask);
// INDEX visible before ELEMENT
soRefElement(buffer, offset, e); // release element e
return true;
}
@Override
public boolean offer(final E e)
{
if (null == e)
{
throw new NullPointerException();
}
final long mask = this.producerMask;
final E[] buffer = this.producerBuffer;
long pIndex;
while (true)
{
pIndex = lvProducerIndex();
// lower bit is indicative of blocked consumer
if ((pIndex & 1) == 1)
{
if (offerAndWakeup(buffer, mask, pIndex, e))
return true;
continue;
}
// pIndex is even (lower bit is 0) -> actual index is (pIndex >> 1), consumer is awake
final long producerLimit = lvProducerLimit();
// Use producer limit to save a read of the more rapidly mutated consumer index.
// Assumption: queue is usually empty or near empty
if (producerLimit <= pIndex)
{
if (!recalculateProducerLimit(mask, pIndex, producerLimit))
{
return false;
}
}
// Claim the index
if (casProducerIndex(pIndex, pIndex + 2))
{
break;
}
}
final long offset = modifiedCalcCircularRefElementOffset(pIndex, mask);
// INDEX visible before ELEMENT
soRefElement(buffer, offset, e); // release element e
return true;
}
@Override
public void put(E e) throws InterruptedException
{
if (!offer(e))
throw new UnsupportedOperationException();
}
@Override
public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException
{
if (offer(e))
return true;
throw new UnsupportedOperationException();
}
private boolean offerAndWakeup(E[] buffer, long mask, long pIndex, E e)
{
final long offset = modifiedCalcCircularRefElementOffset(pIndex, mask);
final Thread consumerThread = lvBlocked();
// We could see a null here through a race with the consumer not yet storing the reference. Just retry.
if (consumerThread == null)
{
return false;
}
// Claim the slot and the responsibility of unparking
if(!casProducerIndex(pIndex, pIndex + 1))
{
return false;
}
soRefElement(buffer, offset, e);
LockSupport.unpark(consumerThread);
return true;
}
private boolean recalculateProducerLimit(long mask, long pIndex, long producerLimit)
{
return recalculateProducerLimit(pIndex, producerLimit, lvConsumerIndex(), mask + 2, mask + 2);
}
private boolean recalculateProducerLimit(long pIndex, long producerLimit, long cIndex, long bufferCapacity, long threshold)
{
// try to update the limit with our new found knowledge on cIndex
if (cIndex + bufferCapacity > pIndex)
{
casProducerLimit(producerLimit, cIndex + bufferCapacity);
}
// full and cannot grow, or hit threshold
long size = pIndex - cIndex;
return size < threshold && size < bufferCapacity;
}
/**
* {@inheritDoc}
*
* This implementation is correct for single consumer thread use only.
*/
@Override
public E take() throws InterruptedException
{
final E[] buffer = consumerBuffer;
final long mask = consumerMask;
final long cIndex = lpConsumerIndex();
final long offset = modifiedCalcCircularRefElementOffset(cIndex, mask);
E e = lvRefElement(buffer, offset);
if (e == null)
{
return parkUntilNext(buffer, cIndex, offset, Long.MAX_VALUE);
}
soRefElement(buffer, offset, null); // release element null
soConsumerIndex(cIndex + 2); // release cIndex
return e;
}
/**
* {@inheritDoc}
*
* This implementation is correct for single consumer thread use only.
*/
@Override
public E poll(long timeout, TimeUnit unit) throws InterruptedException
{
final E[] buffer = consumerBuffer;
final long mask = consumerMask;
final long cIndex = lpConsumerIndex();
final long offset = modifiedCalcCircularRefElementOffset(cIndex, mask);
E e = lvRefElement(buffer, offset);
if (e == null)
{
long timeoutNs = unit.toNanos(timeout);
if (timeoutNs <= 0)
{
return null;
}
return parkUntilNext(buffer, cIndex, offset, timeoutNs);
}
soRefElement(buffer, offset, null); // release element null
soConsumerIndex(cIndex + 2); // release cIndex
return e;
}
private E parkUntilNext(E[] buffer, long cIndex, long offset, long timeoutNs) throws InterruptedException {
E e;
final long pIndex = lvProducerIndex();
if (cIndex == pIndex && // queue is empty
casProducerIndex(pIndex, pIndex + 1)) // we announce ourselves as parked by setting parity
{
// producers only try a wakeup when both the index and the blocked thread are visible, otherwise they spin
soBlocked(Thread.currentThread());
// ignore deadline when it's forever
final long deadlineNs = timeoutNs == Long.MAX_VALUE ? 0 : System.nanoTime() + timeoutNs;
try
{
while (true)
{
LockSupport.parkNanos(this, timeoutNs);
if (Thread.interrupted())
{
casProducerIndex(pIndex + 1, pIndex);
throw new InterruptedException();
}
if ((lvProducerIndex() & 1) == 0) {
break;
}
// ignore deadline when it's forever
timeoutNs = timeoutNs == Long.MAX_VALUE ? Long.MAX_VALUE : deadlineNs - System.nanoTime();
if (timeoutNs <= 0)
{
if (casProducerIndex(pIndex + 1, pIndex))
{
// ran out of time and the producer has not moved the index
return null;
}
break; // just in the nick of time
}
}
}
finally
{
soBlocked(null);
}
}
// producer index is visible before element, so if we wake up between the index moving and the element
// store we could see a null.
e = spinWaitForElement(buffer, offset);
soRefElement(buffer, offset, null); // release element null
soConsumerIndex(cIndex + 2); // release cIndex
return e;
}
@Override
public int remainingCapacity()
{
return capacity() - size();
}
@Override
public int drainTo(Collection super E> c)
{
throw new UnsupportedOperationException();
}
@Override
public int drainTo(Collection super E> c, int maxElements)
{
throw new UnsupportedOperationException();
}
/**
* {@inheritDoc}
*
* This implementation is correct for single consumer thread use only.
*/
@Override
public E poll()
{
final E[] buffer = consumerBuffer;
final long mask = consumerMask;
final long index = lpConsumerIndex();
final long offset = modifiedCalcCircularRefElementOffset(index, mask);
E e = lvRefElement(buffer, offset);
if (e == null)
{
// consumer can't see the odd producer index
if (index != lvProducerIndex())
{
// poll() == null iff queue is empty, null element is not strong enough indicator, so we must
// check the producer index. If the queue is indeed not empty we spin until element is
// visible.
e = spinWaitForElement(buffer, offset);
}
else
{
return null;
}
}
soRefElement(buffer, offset, null); // release element null
soConsumerIndex(index + 2); // release cIndex
return e;
}
private static E spinWaitForElement(E[] buffer, long offset)
{
E e;
do
{
e = lvRefElement(buffer, offset);
}
while (e == null);
return e;
}
/**
* {@inheritDoc}
*
* This implementation is correct for single consumer thread use only.
*/
@Override
public E peek()
{
final E[] buffer = consumerBuffer;
final long mask = consumerMask;
final long index = lpConsumerIndex();
final long offset = modifiedCalcCircularRefElementOffset(index, mask);
E e = lvRefElement(buffer, offset);
if (e == null && index != lvProducerIndex())
{
// peek() == null iff queue is empty, null element is not strong enough indicator, so we must
// check the producer index. If the queue is indeed not empty we spin until element is visible.
e = spinWaitForElement(buffer, offset);
}
return e;
}
@Override
public long currentProducerIndex()
{
return lvProducerIndex() / 2;
}
@Override
public long currentConsumerIndex()
{
return lvConsumerIndex() / 2;
}
@Override
public int capacity()
{
return (int) ((consumerMask + 2) >> 1);
}
@Override
public boolean relaxedOffer(E e)
{
return offer(e);
}
@Override
public E relaxedPoll()
{
final E[] buffer = consumerBuffer;
final long index = lpConsumerIndex();
final long mask = consumerMask;
final long offset = modifiedCalcCircularRefElementOffset(index, mask);
E e = lvRefElement(buffer, offset);
if (e == null)
{
return null;
}
soRefElement(buffer, offset, null);
soConsumerIndex(index + 2);
return e;
}
@Override
public E relaxedPeek()
{
final E[] buffer = consumerBuffer;
final long index = lpConsumerIndex();
final long mask = consumerMask;
final long offset = modifiedCalcCircularRefElementOffset(index, mask);
return lvRefElement(buffer, offset);
}
@Override
public int fill(Supplier s, int limit)
{
if (null == s)
throw new IllegalArgumentException("supplier is null");
if (limit < 0)
throw new IllegalArgumentException("limit is negative:" + limit);
if (limit == 0)
return 0;
final long mask = this.producerMask;
long pIndex;
int claimedSlots;
Thread blockedConsumer = null;
long batchLimit = 0;
final long shiftedBatchSize = 2L * limit;
while (true)
{
pIndex = lvProducerIndex();
long producerLimit = lvProducerLimit();
// lower bit is indicative of blocked consumer
if ((pIndex & 1) == 1)
{
// observe the blocked thread for the pIndex
blockedConsumer = lvBlocked();
if (blockedConsumer == null)
continue;// racing, retry
if(!casProducerIndex(pIndex, pIndex + 1))
{
blockedConsumer = null;
continue;
}
// We have observed the blocked thread for the pIndex(lv index, lv thread, cas index).
// We've claimed pIndex, now we need to wake up consumer and set the element
batchLimit = pIndex + 1;
pIndex = pIndex - 1;
break;
}
// pIndex is even (lower bit is 0) -> actual index is (pIndex >> 1), consumer is awake
// we want 'limit' slots, but will settle for whatever is visible to 'producerLimit'
batchLimit = Math.min(producerLimit, pIndex + shiftedBatchSize); // -> producerLimit >= batchLimit
// Use producer limit to save a read of the more rapidly mutated consumer index.
// Assumption: queue is usually empty or near empty
if (pIndex >= producerLimit)
{
if (!recalculateProducerLimit(mask, pIndex, producerLimit))
{
return 0;
}
batchLimit = Math.min(lvProducerLimit(), pIndex + shiftedBatchSize);
}
// Claim the index
if (casProducerIndex(pIndex, batchLimit))
{
break;
}
}
claimedSlots = (int) ((batchLimit - pIndex) / 2);
final E[] buffer = this.producerBuffer;
// first element offset might be a wakeup, so peeled from loop
for (int i = 0; i < claimedSlots; i++)
{
long offset = modifiedCalcCircularRefElementOffset(pIndex + 2L * i, mask);
soRefElement(buffer, offset, s.get());
}
if (blockedConsumer != null)
{
// no point unblocking an unrelated blocked thread, things have obviously moved on
if (lvBlocked() == blockedConsumer) {
LockSupport.unpark(blockedConsumer);
}
}
return claimedSlots;
}
/**
* Remove up to limit elements from the queue and hand to consume, waiting up to the specified wait time if
* necessary for an element to become available.
*
* There's no strong commitment to the queue being empty at the end of it.
* This implementation is correct for single consumer thread use only.
*
* WARNING: Explicit assumptions are made with regards to {@link Consumer#accept} make sure you have read
* and understood these before using this method.
*
* @return the number of polled elements
* @throws InterruptedException if interrupted while waiting
* @throws IllegalArgumentException c is {@code null}
* @throws IllegalArgumentException if limit is negative
*/
public int drain(Consumer c, final int limit, long timeout, TimeUnit unit) throws InterruptedException {
if (limit == 0) {
return 0;
}
final int drained = drain(c, limit);
if (drained != 0) {
return drained;
}
final E e = poll(timeout, unit);
if (e == null)
return 0;
c.accept(e);
return 1 + drain(c, limit - 1);
}
@Override
public int fill(Supplier s)
{
return MessagePassingQueueUtil.fillBounded(this, s);
}
@Override
public void fill(Supplier s, WaitStrategy wait, ExitCondition exit)
{
MessagePassingQueueUtil.fill(this, s, wait, exit);
}
@Override
public int drain(Consumer c)
{
return drain(c, capacity());
}
@Override
public int drain(final Consumer c, final int limit)
{
return MessagePassingQueueUtil.drain(this, c, limit);
}
@Override
public void drain(Consumer c, WaitStrategy w, ExitCondition exit)
{
MessagePassingQueueUtil.drain(this, c, w, exit);
}
}