/** * 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.*; /** * {@link LongHyperLoop} is an Inter-Thread-Communication(ITC) mechanism. * <P> * Note:<p> * now, the LongHyperLoop aims to support Landz's Asyncor async programming * model. For performance, the current implementations of LongHyperLoop use * off-heap memory via s.m.Unsafe, so it is a little "heavy", take care * when you rush with "new/discard" to LongHyperLoop. * <p> see more * */ public class LongHyperLoop { //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 static final int SIZE_SHIFT_BUFFERSLOT = 3; private static final int SIZE_LONG_TYPE = 8; 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; /** * create a LongHyperLoop with 512 slots in its internal buffer * */ public LongHyperLoop() { this(512); } /** * 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 LongHyperLoop(int nBufferSlots) { contract(()->nBufferSlots>=8); contract(()->Integer.bitCount(nBufferSlots)==1); this.nBufferSlots = nBufferSlots; this.bufferSlotMask = nBufferSlots - 1; //======================================================== int bufferSize = nBufferSlots<<SIZE_SHIFT_BUFFERSLOT; int requestedSize = bufferSize + 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)); this.addrBuffer = addressHyperLoop + SIZE_HYPERLOOP_BASE; //assumed ReadCursors gives enough space //in fact, the follow is not strictly guaranteed but still hoped it kept contract(() -> isCacheLineAligned(addrBuffer)); UNSAFE.putLong(addrWriteCursor, 0L); UNSAFE.putLong(addrMinReadCursor, 0L); UNSAFE.putInt(addrReadCursorCount,0); } /** * TODO: overload a long array version? * @param value * @return */ public boolean send(long 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.putLong( addrBuffer + ((writeCursor & bufferSlotMask) << SIZE_SHIFT_BUFFERSLOT), value); UNSAFE.putLong(addrWriteCursor, writeCursor + 1); UNSAFE.storeFence(); return true; } public void sendTo(long 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 } //TODO: UNSAFE.putLong( addrBuffer + ((writeCursor & bufferSlotMask) << SIZE_SHIFT_BUFFERSLOT), value); UNSAFE.putLong(addrWriteCursor, writeCursor + 1); UNSAFE.storeFence(); } @Override public void finalize() { systemFreeMemory(addressRaw); } public OutPort createOutPort() { return this.new OutPort(); } /** * 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 { private final long addrReadCursor; private 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 #receive()} and {@link #notReceivable()} are another kind * style of combined APIs.<p> * Contrast to {@link #received()}, * you should first use {@link #notReceivable()} to ensure that you can * do the following {@link #receive()}, otherwise you may get an * {@link IllegalStateException} when nothing could be received. * * @return */ public long receive() { // UNSAFE.loadFence(); long readCursor = UNSAFE.getLong(addrReadCursor); if (readCursor==UNSAFE.getLong(addrWriteCursor)) { throw new IllegalStateException("nothing to receive."); } //TODO: getLong, getLongVolatile, getAddress seems... long value = UNSAFE.getLongVolatile(null, addrBuffer + ((readCursor & bufferSlotMask)<<SIZE_SHIFT_BUFFERSLOT)); UNSAFE.putLong(addrReadCursor, readCursor+1); UNSAFE.storeFence(); return value; } /** * Contrast to {@link #notReceivable()} + {@link #receive()}, 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 #receive()} with * Landz off-heap APIs to achieve more controllable * and low latency batch behavior. * */ public long received() { long readCursor = UNSAFE.getLong(addrReadCursor); while (readCursor==UNSAFE.getLongVolatile(null, addrWriteCursor)) { Thread.yield(); } long value = UNSAFE.getLong( addrBuffer + ((readCursor & bufferSlotMask)<<SIZE_SHIFT_BUFFERSLOT)); UNSAFE.putLong(addrReadCursor, readCursor+1); UNSAFE.storeFence(); return value; } public void receiveAll() { long readCursor = UNSAFE.getLong(addrReadCursor); while (readCursor==UNSAFE.getLongVolatile(null, addrWriteCursor)) { // Thread.yield(); } long value = UNSAFE.getLong( addrBuffer + ((readCursor & bufferSlotMask)<<SIZE_SHIFT_BUFFERSLOT)); UNSAFE.putLong(addrReadCursor, readCursor+1); UNSAFE.storeFence(); // return value; } } // @FunctionalInterface // public static interface WaitStrategy { // public void waitToReceive(); // } }