/*
* 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 org.jctools.util.JvmInfo;
import org.jctools.util.Pow2;
import org.jctools.util.UnsafeAccess;
import org.jctools.util.UnsafeDirectByteBuffer;
import java.nio.ByteBuffer;
import java.util.AbstractQueue;
import java.util.Iterator;
import static org.jctools.util.UnsafeDirectByteBuffer.*;
public final class SpscOffHeapIntQueue extends AbstractQueue<Integer> {
public final static byte PRODUCER = 1;
public final static byte CONSUMER = 2;
public static final int INT_ELEMENT_SCALE = 2 + Integer.getInteger("sparse.shift", 0);
// 24b,8b,8b,24b | pad | 24b,8b,8b,24b | pad
private final ByteBuffer buffy;
private final long headAddress;
private final long tailCacheAddress;
private final long tailAddress;
private final long headCacheAddress;
private final int capacity;
private final int mask;
private final long arrayBase;
public SpscOffHeapIntQueue(final int capacity) {
this(allocateAlignedByteBuffer(
getRequiredBufferSize(capacity),
JvmInfo.CACHE_LINE_SIZE),
Pow2.roundToPowerOfTwo(capacity),(byte)(PRODUCER | CONSUMER));
}
public static int getRequiredBufferSize(final int capacity) {
return 4 * JvmInfo.CACHE_LINE_SIZE + (Pow2.roundToPowerOfTwo(capacity) << INT_ELEMENT_SCALE);
}
/**
* This is to be used for an IPC queue with the direct buffer used being a memory
* mapped file.
*
* @param buff
* @param capacity
* @param viewMask
*/
public SpscOffHeapIntQueue(final ByteBuffer buff,
final int capacity, byte viewMask) {
this.capacity = Pow2.roundToPowerOfTwo(capacity);
buffy = alignedSlice(4 * JvmInfo.CACHE_LINE_SIZE + (this.capacity << INT_ELEMENT_SCALE),
JvmInfo.CACHE_LINE_SIZE, buff);
long alignedAddress = UnsafeDirectByteBuffer.getAddress(buffy);
headAddress = alignedAddress;
tailCacheAddress = headAddress + 8;
tailAddress = headAddress + 2 * JvmInfo.CACHE_LINE_SIZE;
headCacheAddress = tailAddress + 8;
arrayBase = alignedAddress + 4 * JvmInfo.CACHE_LINE_SIZE;
// producer owns tail and headCache
if((viewMask & PRODUCER) == PRODUCER){
setHeadCache(0);
setTail(0);
}
// consumer owns head and tailCache
if((viewMask & CONSUMER) == CONSUMER){
setTailCache(0);
setHead(0);
}
mask = this.capacity - 1;
}
public boolean offer(final Integer e) {
if (null == e) {
throw new NullPointerException("Null is not a valid element");
}
final long currentTail = getTailPlain();
final long wrapPoint = currentTail - capacity;
if (getHeadCache() <= wrapPoint) {
setHeadCache(getHead());
if (getHeadCache() <= wrapPoint) {
return false;
}
}
long offset = calcElementOffset(currentTail);
UnsafeAccess.UNSAFE.putInt(offset, e);
setTail(currentTail + 1);
return true;
}
public boolean offerInt(final int e) {
final long currentTail = getTailPlain();
final long wrapPoint = currentTail - capacity;
if (getHeadCache() <= wrapPoint) {
setHeadCache(getHead());
if (getHeadCache() <= wrapPoint) {
return false;
}
}
long offset = calcElementOffset(currentTail);
UnsafeAccess.UNSAFE.putInt(offset, e);
setTail(currentTail + 1);
return true;
}
public Integer poll() {
int i = pollInt();
if(i == Integer.MIN_VALUE) {
return null;
}
return i;
}
public int pollInt() {
final long currentHead = getHeadPlain();
if (currentHead >= getTailCache()) {
setTailCache(getTail());
if (currentHead >= getTailCache()) {
return Integer.MIN_VALUE;
}
}
final long offset = calcElementOffset(currentHead);
final int e = UnsafeAccess.UNSAFE.getInt(offset);
setHead(currentHead + 1);
return e;
}
private long calcElementOffset(final long currentHead) {
return arrayBase + ((currentHead & mask) << INT_ELEMENT_SCALE);
}
public Integer peek() {
int i = peekInt();
if(i == Integer.MIN_VALUE) {
return null;
}
return i;
}
public int peekInt() {
final long currentHead = getHeadPlain();
if (currentHead >= getTailCache()) {
setTailCache(getTail());
if (currentHead >= getTailCache()) {
return Integer.MIN_VALUE;
}
}
final long offset = calcElementOffset(currentHead);
return UnsafeAccess.UNSAFE.getInt(offset);
}
public int size() {
return (int) (getTail() - getHead());
}
public boolean isEmpty() {
return getTail() == getHead();
}
public Iterator<Integer> iterator() {
throw new UnsupportedOperationException();
}
private long getHeadPlain() {
return UnsafeAccess.UNSAFE.getLong(null, headAddress);
}
private long getHead() {
return UnsafeAccess.UNSAFE.getLongVolatile(null, headAddress);
}
private void setHead(final long value) {
UnsafeAccess.UNSAFE.putOrderedLong(null, headAddress, value);
}
private long getTailPlain() {
return UnsafeAccess.UNSAFE.getLong(null, tailAddress);
}
private long getTail() {
return UnsafeAccess.UNSAFE.getLongVolatile(null, tailAddress);
}
private void setTail(final long value) {
UnsafeAccess.UNSAFE.putOrderedLong(null, tailAddress, value);
}
private long getHeadCache() {
return UnsafeAccess.UNSAFE.getLong(null, headCacheAddress);
}
private void setHeadCache(final long value) {
UnsafeAccess.UNSAFE.putLong(headCacheAddress, value);
}
private long getTailCache() {
return UnsafeAccess.UNSAFE.getLong(null, tailCacheAddress);
}
private void setTailCache(final long value) {
UnsafeAccess.UNSAFE.putLong(tailCacheAddress, value);
}
}