All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jctools.queues.MpmcUnboundedXaddArrayQueue Maven / Gradle / Ivy

There is a newer version: 1.36.0
Show newest version
/*
 * 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;



/**
 * An MPMC array queue which grows unbounded in linked chunks.
* Differently from {@link MpmcArrayQueue} it is designed to provide a better scaling when more * producers are concurrently offering.
* Users should be aware that {@link #poll()} could spin while awaiting a new element to be available: * to avoid this behaviour {@link #relaxedPoll()} should be used instead, accounting for the semantic differences * between the twos. * * @author https://github.com/franz1981 */ public class MpmcUnboundedXaddArrayQueue extends MpUnboundedXaddArrayQueue, E> { /** * @param chunkSize The buffer size to be used in each chunk of this queue * @param maxPooledChunks The maximum number of reused chunks kept around to avoid allocation, chunks are pre-allocated */ public MpmcUnboundedXaddArrayQueue(int chunkSize, int maxPooledChunks) { super(chunkSize, maxPooledChunks); } public MpmcUnboundedXaddArrayQueue(int chunkSize) { this(chunkSize, 2); } @Override final MpmcUnboundedXaddChunk newChunk(long index, MpmcUnboundedXaddChunk prev, int chunkSize, boolean pooled) { return new MpmcUnboundedXaddChunk(index, prev, chunkSize, pooled); } @Override public boolean offer(E e) { if (null == e) { throw new NullPointerException(); } final int chunkMask = this.chunkMask; final int chunkShift = this.chunkShift; final long pIndex = getAndIncrementProducerIndex(); final int piChunkOffset = (int) (pIndex & chunkMask); final long piChunkIndex = pIndex >> chunkShift; MpmcUnboundedXaddChunk pChunk = lvProducerChunk(); if (pChunk.lvIndex() != piChunkIndex) { // Other producers may have advanced the producer chunk as we claimed a slot in a prev chunk, or we may have // now stepped into a brand new chunk which needs appending. pChunk = producerChunkForIndex(pChunk, piChunkIndex); } final boolean isPooled = pChunk.isPooled(); if (isPooled) { // wait any previous consumer to finish its job pChunk.spinForElement(piChunkOffset, true); } pChunk.soElement(piChunkOffset, e); if (isPooled) { pChunk.soSequence(piChunkOffset, piChunkIndex); } return true; } @Override public E poll() { final int chunkMask = this.chunkMask; final int chunkShift = this.chunkShift; long cIndex; MpmcUnboundedXaddChunk cChunk; int ciChunkOffset; boolean isFirstElementOfNewChunk; boolean pooled = false; E e = null; MpmcUnboundedXaddChunk next = null; long pIndex = -1; // start with bogus value, hope we don't need it long ciChunkIndex; while (true) { isFirstElementOfNewChunk = false; cIndex = this.lvConsumerIndex(); // chunk is in sync with the index, and is safe to mutate after CAS of index (because we pre-verify it // matched the indicate ciChunkIndex) cChunk = this.lvConsumerChunk(); ciChunkOffset = (int) (cIndex & chunkMask); ciChunkIndex = cIndex >> chunkShift; final long ccChunkIndex = cChunk.lvIndex(); if (ciChunkOffset == 0 && cIndex != 0) { if (ciChunkIndex - ccChunkIndex != 1) { continue; } isFirstElementOfNewChunk = true; next = cChunk.lvNext(); // next could have been modified by another racing consumer, but: // - if null: it still needs to check q empty + casConsumerIndex // - if !null: it will fail on casConsumerIndex if (next == null) { if (cIndex >= pIndex && // test against cached pIndex cIndex == (pIndex = lvProducerIndex())) // update pIndex if we must { // strict empty check, this ensures [Queue.poll() == null iff isEmpty()] return null; } // we will go ahead with the CAS and have the winning consumer spin for the next buffer } // not empty: can attempt the cas (and transition to next chunk if successful) if (casConsumerIndex(cIndex, cIndex + 1)) { break; } continue; } if (ccChunkIndex > ciChunkIndex) { //stale view of the world continue; } // mid chunk elements assert !isFirstElementOfNewChunk && ccChunkIndex <= ciChunkIndex; pooled = cChunk.isPooled(); if (ccChunkIndex == ciChunkIndex) { if (pooled) { // Pooled chunks need a stronger guarantee than just element null checking in case of a stale view // on a reused entry where a racing consumer has grabbed the slot but not yet null-ed it out and a // producer has not yet set it to the new value. final long sequence = cChunk.lvSequence(ciChunkOffset); if (sequence == ciChunkIndex) { if (casConsumerIndex(cIndex, cIndex + 1)) { break; } continue; } if (sequence > ciChunkIndex) { //stale view of the world continue; } // sequence < ciChunkIndex: element yet to be set? } else { e = cChunk.lvElement(ciChunkOffset); if (e != null) { if (casConsumerIndex(cIndex, cIndex + 1)) { break; } continue; } // e == null: element yet to be set? } } // ccChunkIndex < ciChunkIndex || e == null || sequence < ciChunkIndex: if (cIndex >= pIndex && // test against cached pIndex cIndex == (pIndex = lvProducerIndex())) // update pIndex if we must { // strict empty check, this ensures [Queue.poll() == null iff isEmpty()] return null; } } // if we are the isFirstElementOfNewChunk we need to get the consumer chunk if (isFirstElementOfNewChunk) { e = switchToNextConsumerChunkAndPoll(cChunk, next, ciChunkIndex); } else { if (pooled) { e = cChunk.lvElement(ciChunkOffset); } assert !cChunk.isPooled() || (cChunk.isPooled() && cChunk.lvSequence(ciChunkOffset) == ciChunkIndex); cChunk.soElement(ciChunkOffset, null); } return e; } private E switchToNextConsumerChunkAndPoll( MpmcUnboundedXaddChunk cChunk, MpmcUnboundedXaddChunk next, long expectedChunkIndex) { if (next == null) { final long ccChunkIndex = expectedChunkIndex - 1; assert cChunk.lvIndex() == ccChunkIndex; if (lvProducerChunkIndex() == ccChunkIndex) { // no need to help too much here or the consumer latency will be hurt next = appendNextChunks(cChunk, ccChunkIndex, 1); } } while (next == null) { next = cChunk.lvNext(); } // we can freely spin awaiting producer, because we are the only one in charge to // rotate the consumer buffer and use next final E e = next.spinForElement(0, false); final boolean pooled = next.isPooled(); if (pooled) { next.spinForSequence(0, expectedChunkIndex); } next.soElement(0, null); moveToNextConsumerChunk(cChunk, next); return e; } @Override public E peek() { final int chunkMask = this.chunkMask; final int chunkShift = this.chunkShift; long cIndex; E e; do { e = null; cIndex = this.lvConsumerIndex(); MpmcUnboundedXaddChunk cChunk = this.lvConsumerChunk(); final int ciChunkOffset = (int) (cIndex & chunkMask); final long ciChunkIndex = cIndex >> chunkShift; final boolean firstElementOfNewChunk = ciChunkOffset == 0 && cIndex != 0; if (firstElementOfNewChunk) { final long expectedChunkIndex = ciChunkIndex - 1; if (expectedChunkIndex != cChunk.lvIndex()) { continue; } final MpmcUnboundedXaddChunk next = cChunk.lvNext(); if (next == null) { continue; } cChunk = next; } if (cChunk.isPooled()) { if (cChunk.lvSequence(ciChunkOffset) != ciChunkIndex) { continue; } } else { if (cChunk.lvIndex() != ciChunkIndex) { continue; } } e = cChunk.lvElement(ciChunkOffset); } // checking again vs consumerIndex changes is necessary to verify that e is still valid while ((e == null && cIndex != lvProducerIndex()) || (e != null && cIndex != lvConsumerIndex())); return e; } @Override public E relaxedPoll() { final int chunkMask = this.chunkMask; final int chunkShift = this.chunkShift; final long cIndex = this.lvConsumerIndex(); final MpmcUnboundedXaddChunk cChunk = this.lvConsumerChunk(); final int ciChunkOffset = (int) (cIndex & chunkMask); final long ciChunkIndex = cIndex >> chunkShift; final boolean firstElementOfNewChunk = ciChunkOffset == 0 && cIndex != 0; if (firstElementOfNewChunk) { final long expectedChunkIndex = ciChunkIndex - 1; final MpmcUnboundedXaddChunk next; final long ccChunkIndex = cChunk.lvIndex(); if (expectedChunkIndex != ccChunkIndex || (next = cChunk.lvNext()) == null) { return null; } E e = null; final boolean pooled = next.isPooled(); if (pooled) { if (next.lvSequence(0) != ciChunkIndex) { return null; } } else { e = next.lvElement(0); if (e == null) { return null; } } if (!casConsumerIndex(cIndex, cIndex + 1)) { return null; } if (pooled) { e = next.lvElement(0); } assert e != null; next.soElement(0, null); moveToNextConsumerChunk(cChunk, next); return e; } else { final boolean pooled = cChunk.isPooled(); E e = null; if (pooled) { final long sequence = cChunk.lvSequence(ciChunkOffset); if (sequence != ciChunkIndex) { return null; } } else { final long ccChunkIndex = cChunk.lvIndex(); if (ccChunkIndex != ciChunkIndex || (e = cChunk.lvElement(ciChunkOffset)) == null) { return null; } } if (!casConsumerIndex(cIndex, cIndex + 1)) { return null; } if (pooled) { e = cChunk.lvElement(ciChunkOffset); assert e != null; } assert !pooled || (pooled && cChunk.lvSequence(ciChunkOffset) == ciChunkIndex); cChunk.soElement(ciChunkOffset, null); return e; } } @Override public E relaxedPeek() { final int chunkMask = this.chunkMask; final int chunkShift = this.chunkShift; final long cIndex = this.lvConsumerIndex(); final int ciChunkOffset = (int) (cIndex & chunkMask); final long ciChunkIndex = cIndex >> chunkShift; MpmcUnboundedXaddChunk consumerBuffer = this.lvConsumerChunk(); final int chunkSize = chunkMask + 1; final boolean firstElementOfNewChunk = ciChunkOffset == 0 && cIndex >= chunkSize; if (firstElementOfNewChunk) { final long expectedChunkIndex = ciChunkIndex - 1; if (expectedChunkIndex != consumerBuffer.lvIndex()) { return null; } final MpmcUnboundedXaddChunk next = consumerBuffer.lvNext(); if (next == null) { return null; } consumerBuffer = next; } if (consumerBuffer.isPooled()) { if (consumerBuffer.lvSequence(ciChunkOffset) != ciChunkIndex) { return null; } } else { if (consumerBuffer.lvIndex() != ciChunkIndex) { return null; } } final E e = consumerBuffer.lvElement(ciChunkOffset); // checking again vs consumerIndex changes is necessary to verify that e is still valid if (cIndex != lvConsumerIndex()) { return null; } return e; } @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 int chunkShift = this.chunkShift; final int chunkMask = this.chunkMask; long producerSeq = getAndAddProducerIndex(limit); MpmcUnboundedXaddChunk producerBuffer = null; for (int i = 0; i < limit; i++) { final int pOffset = (int) (producerSeq & chunkMask); long chunkIndex = producerSeq >> chunkShift; if (producerBuffer == null || producerBuffer.lvIndex() != chunkIndex) { producerBuffer = producerChunkForIndex(producerBuffer, chunkIndex); if (producerBuffer.isPooled()) { chunkIndex = producerBuffer.lvIndex(); } } if (producerBuffer.isPooled()) { while (producerBuffer.lvElement(pOffset) != null) { } } producerBuffer.soElement(pOffset, s.get()); if (producerBuffer.isPooled()) { producerBuffer.soSequence(pOffset, chunkIndex); } producerSeq++; } return limit; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy