/*
* 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.channels;
import static org.jctools.util.JvmInfo.CACHE_LINE_SIZE;
import static org.jctools.util.UnsafeAccess.UNSAFE;
import static org.jctools.util.UnsafeDirectByteBuffer.alignedSlice;
import static org.jctools.util.UnsafeDirectByteBuffer.allocateAlignedByteBuffer;
import java.nio.ByteBuffer;
import org.jctools.channels.proxy.ProxyChannelRingBuffer;
import org.jctools.util.JvmInfo;
import org.jctools.util.Pow2;
import org.jctools.util.UnsafeDirectByteBuffer;
import org.jctools.util.UnsafeRefArrayAccess;
/**
* Channel protocol:
* - Fixed message size
* - 'null' indicator in message preceding byte (potentially use same for type mapping in future)
* - Use FF algorithm relying on indicator to support in place detection of next element existence
*/
public abstract class OffHeapFixedMessageSizeRingBuffer extends ProxyChannelRingBuffer {
public static final int READ_RELEASE_INDICATOR = 0;
public static final int READ_ACQUIRE_INDICATOR = 1;
public static final int WRITE_RELEASE_INDICATOR = 2;
public static final int WRITE_ACQUIRE_INDICATOR = 3;
public static final byte MESSAGE_INDICATOR_SIZE = 4;
public static final int HEADER_SIZE = 4 * JvmInfo.CACHE_LINE_SIZE;
private final ByteBuffer buffy;
protected final long bufferAddress;
protected final long consumerIndexAddress;
protected final long producerIndexAddress;
protected final long mask;
protected final int messageSize;
protected final Object[] references;
protected final int referenceMessageSize;
public static int getRequiredBufferSize(final int capacity, final int messageSize) {
int alignedMessageSize = (int) Pow2.align(messageSize + MESSAGE_INDICATOR_SIZE, MESSAGE_INDICATOR_SIZE);
return HEADER_SIZE + (Pow2.roundToPowerOfTwo(capacity) * alignedMessageSize);
}
protected static Object[] createReferenceArray(final int capacity, int referenceMessageSize) {
if (referenceMessageSize > 0) {
return new Object[getRequiredArraySize(capacity, referenceMessageSize)];
} else {
return null;
}
}
public static int getRequiredArraySize(final int capacity, final int primitiveMessageSize) {
return Pow2.roundToPowerOfTwo(capacity) * primitiveMessageSize;
}
public OffHeapFixedMessageSizeRingBuffer(final int capacity, final int primitiveMessageSize, int referenceMessageSize) {
this(allocateAlignedByteBuffer(getRequiredBufferSize(capacity, primitiveMessageSize), CACHE_LINE_SIZE),
Pow2.roundToPowerOfTwo(capacity),
true,
true,
true,
primitiveMessageSize,
createReferenceArray(capacity, referenceMessageSize),
referenceMessageSize);
}
/**
* This is to be used for an IPC queue with the direct buffer used being a memory mapped file.
*
* @param buff
* @param capacity in messages, actual capacity will be
* @param primitiveMessageSize
*/
protected OffHeapFixedMessageSizeRingBuffer(final ByteBuffer buff,
final int capacity,
final boolean isProducer,
final boolean isConsumer,
final boolean initialize,
final int primitiveMessageSize,
final Object[] references,
final int referenceMessageSize) {
if (references != null && references.length < referenceMessageSize) {
throw new IllegalArgumentException("Reference array of length " + references.length
+ " is insufficient to store a single message of size " + referenceMessageSize);
}
int actualCapacity = Pow2.roundToPowerOfTwo(capacity);
// message size is aligned to indicator size, this ensure atomic writes of indicator
this.messageSize = (int) Pow2.align(primitiveMessageSize + MESSAGE_INDICATOR_SIZE, MESSAGE_INDICATOR_SIZE);
this.buffy = alignedSlice(HEADER_SIZE + (actualCapacity * (this.messageSize)), CACHE_LINE_SIZE, buff);
long alignedAddress = UnsafeDirectByteBuffer.getAddress(buffy);
if (alignedAddress % JvmInfo.CACHE_LINE_SIZE != 0) {
throw new IllegalStateException("buffer is expected to be cache line aligned by now");
}
// Layout of the RingBuffer (assuming 64b cache line):
// consumerIndex(8b), pad(56b) |
// pad(64b) |
// producerIndex(8b), pad(56b) |
// pad(64b) |
// buffer (capacity * messageSize)
this.consumerIndexAddress = alignedAddress;
this.producerIndexAddress = this.consumerIndexAddress + 2 * JvmInfo.CACHE_LINE_SIZE;
this.bufferAddress = alignedAddress + HEADER_SIZE;
this.mask = actualCapacity - 1;
this.references = references;
this.referenceMessageSize = referenceMessageSize;
// producer owns tail and headCache
if (isProducer && initialize) {
soProducerIndex(0);
// mark all messages as READY
for (int i = 0; i < actualCapacity; i++) {
final long offset = offsetForIndex(i);
readReleaseState(offset);
}
}
// consumer owns head
if (isConsumer && initialize) {
soConsumerIndex(0);
}
}
public final int capacity() {
return (int) (mask + 1);
}
public final int size() {
return (int) (lvProducerIndex() - lvConsumerIndex());
}
public final boolean isEmpty() {
return lvProducerIndex() == lvConsumerIndex();
}
protected final boolean isReadReleased(long offset) {
return UNSAFE.getIntVolatile(null, offset) == READ_RELEASE_INDICATOR;
}
protected final void writeReleaseState(long offset) {
UNSAFE.putOrderedInt(null, offset, WRITE_RELEASE_INDICATOR);
}
protected final void readReleaseState(long offset) {
UNSAFE.putOrderedInt(null, offset, READ_RELEASE_INDICATOR);
}
protected final void writeAcquireState(long offset) {
UNSAFE.putOrderedInt(null, offset, WRITE_ACQUIRE_INDICATOR);
}
protected final void readAcquireState(long offset) {
UNSAFE.putOrderedInt(null, offset, READ_ACQUIRE_INDICATOR);
}
protected final long offsetForIndex(long currentHead) {
return offsetForIndex(bufferAddress, mask, messageSize, currentHead);
}
protected static long offsetForIndex(long bufferAddress, long mask, int messageSize, long currentHead) {
return bufferAddress + ((currentHead & mask) * messageSize);
}
protected final long lpConsumerIndex() {
return UNSAFE.getLong(null, consumerIndexAddress);
}
protected final long lvConsumerIndex() {
return UNSAFE.getLongVolatile(null, consumerIndexAddress);
}
protected final void soConsumerIndex(final long value) {
UNSAFE.putOrderedLong(null, consumerIndexAddress, value);
}
protected final long lpProducerIndex() {
return UNSAFE.getLong(null, producerIndexAddress);
}
protected final long lvProducerIndex() {
return UNSAFE.getLongVolatile(null, producerIndexAddress);
}
protected final void soProducerIndex(final long value) {
UNSAFE.putOrderedLong(null, producerIndexAddress, value);
}
protected final long arrayIndexForCursor(long currentHead) {
return arrayIndexForCursor(mask, referenceMessageSize, currentHead);
}
protected static long arrayIndexForCursor(long mask,
int referenceMessageSize,
long currentHead) {
return (currentHead & mask) * referenceMessageSize;
}
protected long consumerReferenceArrayIndex() {
final long consumerIndex = lpConsumerIndex();
return arrayIndexForCursor(consumerIndex);
}
protected long producerReferenceArrayIndex() {
final long producerIndex = lpProducerIndex();
return arrayIndexForCursor(producerIndex);
}
/**
* Write a reference to the given position
* @param offset index into the reference array
* @param reference
*/
protected void writeReference(long offset, Object reference) {
assert referenceMessageSize != 0 : "References are not in use";
// Is there a way to compute the element offset once and just
// arithmetic?
UnsafeRefArrayAccess.spElement(references, UnsafeRefArrayAccess.calcElementOffset(offset), reference);
}
/**
* Read a reference at the given position
* @param offset index into the reference array
* @return
*/
protected Object readReference(long offset) {
assert referenceMessageSize != 0 : "References are not in use";
// Is there a way to compute the element offset once and just
// arithmetic?
return UnsafeRefArrayAccess.lpElement(references, UnsafeRefArrayAccess.calcElementOffset(offset));
}
/**
* @return a base address for a message acquired to be read, or EOF if none is available
*/
protected abstract long readAcquire();
/**
* @param offset the base address of a message that we are done reading and can be overwritten now
*/
protected abstract void readRelease(long offset);
/**
* @return a base address for a message acquired to be written, or EOF if none is available
*/
protected abstract long writeAcquire();
/**
* @param offset the base address of a message that we are done writing and can be read now
*/
protected abstract void writeRelease(long offset);
}