/*
* 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.spsc;
import static org.jctools.util.JvmInfo.CACHE_LINE_SIZE;
import static org.jctools.util.UnsafeAccess.UNSAFE;
import static org.jctools.util.UnsafeDirectByteBuffer.allocateAlignedByteBuffer;
import java.nio.ByteBuffer;
import org.jctools.channels.OffHeapFixedMessageSizeRingBuffer;
import org.jctools.util.Pow2;
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 class SpscOffHeapFixedSizeRingBuffer extends OffHeapFixedMessageSizeRingBuffer {
private static final Integer MAX_LOOK_AHEAD_STEP = Integer.getInteger("jctools.spsc.max.lookahead.step",
4096);
public static final long EOF = 0;
private final int lookAheadStep;
private final long producerLookAheadCacheAddress;
public static int getLookaheadStep(final int capacity) {
return Math.min(capacity / 4, MAX_LOOK_AHEAD_STEP);
}
public SpscOffHeapFixedSizeRingBuffer(final int capacity, final int messageSize, final int referenceMessageSize) {
this(allocateAlignedByteBuffer(getRequiredBufferSize(capacity, messageSize), CACHE_LINE_SIZE),
Pow2.roundToPowerOfTwo(capacity),
true,
true,
true,
messageSize,
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 messageSize
*/
protected SpscOffHeapFixedSizeRingBuffer(final ByteBuffer buff,
final int capacity,
final boolean isProducer,
final boolean isConsumer,
final boolean initialize,
final int messageSize,
final Object[] references,
final int referenceMessageSize) {
super(buff,
capacity,
isProducer,
isConsumer,
initialize,
messageSize,
references,
referenceMessageSize);
this.lookAheadStep = getLookaheadStep(capacity);
// Layout of the RingBuffer (assuming 64b cache line):
// consumerIndex(8b), pad(56b) |
// pad(64b) |
// producerIndex(8b), producerLookAheadCache(8b), pad(48b) |
// pad(64b) |
// buffer (capacity * messageSize)
this.producerLookAheadCacheAddress = this.producerIndexAddress + 8;
// producer owns tail and headCache
if (isProducer && initialize) {
spLookAheadCache(0);
}
}
@Override
protected final long writeAcquire() {
final long producerIndex = lpProducerIndex();
final long producerLookAhead = lpLookAheadCache();
final long producerOffset = offsetForIndex(bufferAddress, mask, messageSize, producerIndex);
// verify next lookAheadStep messages are clear to write
if (producerIndex >= producerLookAhead) {
final long nextLookAhead = producerIndex + lookAheadStep;
if (isReadReleased(offsetForIndex(nextLookAhead))) {
spLookAheadCache(nextLookAhead);
}
// OK, can't look ahead, but maybe just next item is ready?
else if (!isReadReleased(producerOffset)) {
return EOF;
}
}
soProducerIndex(producerIndex + 1); // StoreStore
// writeAcquireState(producerOffset);
// return offset for current producer index
return producerOffset;
}
@Override
protected final void writeRelease(long offset) {
writeReleaseState(offset);
}
@Override
protected final void writeRelease(long offset, int type) {
assert type != 0;
UNSAFE.putOrderedInt(null, offset, type);
}
@Override
protected final long readAcquire() {
final long consumerIndex = lpConsumerIndex();
final long consumerOffset = offsetForIndex(consumerIndex);
if (isReadReleased(consumerOffset)) {
return EOF;
}
soConsumerIndex(consumerIndex + 1); // StoreStore
// readAcquireState(consumerOffset);
return consumerOffset;
}
@Override
protected final void readRelease(long offset) {
readReleaseState(offset);
}
private long lpLookAheadCache() {
return UNSAFE.getLong(null, producerLookAheadCacheAddress);
}
private void spLookAheadCache(final long value) {
UNSAFE.putLong(producerLookAheadCacheAddress, value);
}
}