/**
* Copyright 2013, Landz and its contributors. All rights reserved.
*
* 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 z.channel;
import static z.util.Contracts.contract;
import static z.util.Unsafes.*;
/**
* Generic variant of {@link z.channel.LongHyperLoop}
*/
public final class GenericHyperLoop<T> implements BroadcastChannel<T> {
private static final int BASE = UNSAFE.ARRAY_OBJECT_BASE_OFFSET;
private static final int SCALE = UNSAFE.ARRAY_OBJECT_INDEX_SCALE;
//TODO: add a config option for SIZE_HYPERLOOP_BASE?
/**
* NOTE: you should reserve 128*(4+number of consumers of LongHyperLoop),
* otherwise you may crash your JVM.
* <p>
* contract:
* 1. now keep it a multiple of 4096
*/
private static final int SIZE_HYPERLOOP_BASE = 4096;//only for 28 consumers?
private final long addressRaw;
private final long addressHyperLoop;
private final int nBufferSlots;
private final int bufferSlotMask;
private final long addrWriteCursor;
private final long addrMinReadCursor;
// private final long addrBuffer;
private final long addrReadCursorCount;//int type
private long addrReadCursors;
private final Object[] slots;
/**
* create a LongHyperLoop with 512 slots in its internal buffer
*
*/
public GenericHyperLoop() {
this(1024);
}
/**
* create a LongHyperLoop.
* <p>
* contract: <p>
* 1. nBufferSlots >=8 <p>
* 2. nBufferSlots is a power of 2, and
* better if you can make nBufferSlots*8 a multiple of 4096
*
* <p>
*
* @param nBufferSlots the size of internal buffer in slot unit
*/
public GenericHyperLoop(int nBufferSlots) {
contract(()->nBufferSlots>=8);
contract(()->Integer.bitCount(nBufferSlots)==1);
this.nBufferSlots = nBufferSlots;
this.bufferSlotMask = nBufferSlots - 1;
//========================================================
int requestedSize = SIZE_HYPERLOOP_BASE;
addressRaw = systemAllocateMemory(requestedSize + SIZE_PAGE);
addressHyperLoop = nextPageAlignedAddress(addressRaw);
contract(() -> isPageAligned(addressHyperLoop));
this.addrWriteCursor = addressHyperLoop
+ SIZE_CACHE_LINE_PADDING;
contract(() -> isCacheLineAligned(addrWriteCursor));
this.addrMinReadCursor = addrWriteCursor + SIZE_CACHE_LINE_PADDING;
contract(() -> isCacheLineAligned(addrMinReadCursor));
this.addrReadCursorCount = addrMinReadCursor + SIZE_CACHE_LINE_PADDING;
contract(() -> isCacheLineAligned(addrReadCursorCount));
this.addrReadCursors = addrReadCursorCount;
contract(() -> isCacheLineAligned(addrReadCursors));
//================slots
slots = new Object[nBufferSlots];
UNSAFE.putLong(addrWriteCursor, 0L);
UNSAFE.putLong(addrMinReadCursor, 0L);
UNSAFE.putInt(addrReadCursorCount,0);
}
/**
* TODO: overload a long array version?
* Send out an Object of type T to the {@link GenericHyperLoop}
*
* @param value - the value to be sent out to the {@link GenericHyperLoop}
* @return - true if sent out successfully, or false if failed
*/
public boolean trySend(T value) {
long minReadCursor = UNSAFE.getLongVolatile(null,addrMinReadCursor);
long writeCursor = UNSAFE.getLong(addrWriteCursor);
if (writeCursor == (minReadCursor+nBufferSlots)) {
// assume readCursorCount>0
minReadCursor = UNSAFE.getLongVolatile(null, addrReadCursors);
for (int i = 1; i < UNSAFE.getInt(addrReadCursorCount); i++) {
long readCursor = UNSAFE.getLongVolatile(null, addrReadCursors
- i*SIZE_CACHE_LINE_PADDING);
minReadCursor = minReadCursor > readCursor ? readCursor : minReadCursor;
}
UNSAFE.putLong(addrMinReadCursor,minReadCursor);
return false;
}
UNSAFE.putOrderedObject(slots,
BASE+SCALE*(writeCursor & bufferSlotMask),value);
UNSAFE.putOrderedLong(null,addrWriteCursor, writeCursor + 1);
return true;
}
/**
* variant of {@link #trySend(Object)}. <p>
* It will block until the value has been sent out.
*/
public void send(T value) {
long minReadCursor = UNSAFE.getLongVolatile(null,addrMinReadCursor);
long writeCursor = UNSAFE.getLong(addrWriteCursor);
while (writeCursor == (minReadCursor+nBufferSlots)) {
// assume readCursorCount>0
minReadCursor = UNSAFE.getLongVolatile(null, addrReadCursors);
for (int i = 1; i < UNSAFE.getInt(addrReadCursorCount); i++) {
long readCursor = UNSAFE.getLongVolatile(null, addrReadCursors
- i*SIZE_CACHE_LINE_PADDING);
minReadCursor = minReadCursor > readCursor ? readCursor : minReadCursor;
}
UNSAFE.putLong(addrMinReadCursor,minReadCursor);
Thread.yield();//harm latency but welcome to throughput
}
UNSAFE.putObject(slots,
BASE+SCALE*(writeCursor & bufferSlotMask),value);
UNSAFE.putLong(addrWriteCursor, writeCursor + 1);
UNSAFE.storeFence();
}
@Override
public void finalize() {
systemFreeMemory(addressRaw);
}
public z.channel.ReceivePort<T> createReceivePort() {
return this.new OutPort<T>();
}
/**
* Note:
* it is always hoped the values sent into LongHyperLoop could be consumed ASAP.
*
* TODO: need to handle the removal of OutPort dynamically
*/
public final class OutPort<T> implements z.channel.ReceivePort<T> {
private final long addrReadCursor;
public OutPort() {
synchronized(OutPort.class) {
addrReadCursors += SIZE_CACHE_LINE_PADDING;
this.addrReadCursor = addrReadCursors;
//TODO: clear the readCursor
UNSAFE.putLong(addrReadCursor,0L);
UNSAFE.putInt(addrReadCursorCount,
UNSAFE.getInt(addrReadCursorCount) + 1);
}
}
public boolean isReceivable() {
return UNSAFE.getLong(addrReadCursor) !=
UNSAFE.getLongVolatile(null, addrWriteCursor);
}
public boolean notReceivable() {
return UNSAFE.getLong(addrReadCursor) ==
UNSAFE.getLongVolatile(null, addrWriteCursor);
}
//TODO: unchecked except to checked?
/**
* this {@link #tryReceive()} and {@link #notReceivable()} are another kind
* style of combined APIs.<p>
* Contrast to {@link #receive()},
* you should first use {@link #notReceivable()} to ensure that you can
* do the following {@link #tryReceive()}, otherwise you may get an
* {@link IllegalStateException} when nothing could be received.
*
* @return
*/
public T tryReceive() {
long readCursor = UNSAFE.getLong(addrReadCursor);
if (readCursor==UNSAFE.getLong(addrWriteCursor)) {
throw new IllegalStateException("nothing to receive.");
}
//NOTE: we need a load fence to the element of slots, not slots itself
T value = (T)UNSAFE.getObjectVolatile(slots,
BASE + SCALE * (readCursor & bufferSlotMask));
UNSAFE.putLong(addrReadCursor, readCursor+1);
UNSAFE.storeFence();
return value;
}
/**
* Contrast to {@link #notReceivable()} + {@link #tryReceive()}, this call
* will wait(block) until something can be received.<p>
* So this call gives more higher throughput but harms latency.<p>
* NOTE: This method is just for lazy men.<p>
* You can use {@link #notReceivable()} + {@link #tryReceive()} with
* Landz off-heap APIs to achieve more controllable
* and low latency batch behavior.
*
*/
public T receive() {
long readCursor = UNSAFE.getLong(addrReadCursor);
while (readCursor==UNSAFE.getLongVolatile(null, addrWriteCursor)) {
Thread.yield();
}
T value = (T)UNSAFE.getObjectVolatile(slots,
BASE + SCALE * (readCursor & bufferSlotMask));
UNSAFE.putLong(addrReadCursor, readCursor+1);
UNSAFE.storeFence();
return value;
}
}
}