/**
* 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.MPMCQueue}.
*
*/
public final class GenericMPMCQueue<T> implements Channel<T>, ReceivePort<T> {
private static final int BASE = UNSAFE.ARRAY_OBJECT_BASE_OFFSET;
private static final int SCALE = UNSAFE.ARRAY_OBJECT_INDEX_SCALE;
private static final int LONG_BASE = UNSAFE.ARRAY_LONG_BASE_OFFSET;
private static final int LONG_SCALE = UNSAFE.ARRAY_LONG_INDEX_SCALE;
// private static final int SIZESHIFT_BUFFERSLOT = 4;//2*SIZE_LONG_TYPE
// private static final int SIZE_LONG_TYPE = 8;
private final long addressRaw;
private final long addressMPMCQueue;
private final int nBufferSlots;
private final int bufferSlotMask;
private final long addrWriteCursor;
private final long addrReadCursor;
private final Object[] slots;
private final long[] slotSeqs;
// //1 boolean(byte) per slot,true is un-pub-ed, false is pub-ed
// private final long addrMarkBuffer;
/**
* @param nBufferSlots the size of internal buffer in slot unit
*/
public GenericMPMCQueue(int nBufferSlots) {
contract(()->nBufferSlots>=2);
contract(()->Integer.bitCount(nBufferSlots)==1);
this.nBufferSlots = nBufferSlots;
this.bufferSlotMask = nBufferSlots - 1;
//========================================================
addressRaw = systemAllocateMemory(SIZE_CACHE_LINE_PADDING*4 + SIZE_CACHE_LINE);
addressMPMCQueue = nextCacheLineAlignedAddress(addressRaw);
contract(() -> isCacheLineAligned(addressMPMCQueue));
this.addrWriteCursor = addressMPMCQueue + SIZE_CACHE_LINE_PADDING;
contract(() -> isCacheLineAligned(addrWriteCursor));
this.addrReadCursor = addrWriteCursor + SIZE_CACHE_LINE_PADDING;
contract(() -> isCacheLineAligned(addrReadCursor));
slots = new Object[nBufferSlots];
slotSeqs = new long[nBufferSlots];
for (int i = 0; i < nBufferSlots; i++) {
UNSAFE.putLongVolatile(slotSeqs, LONG_BASE+LONG_SCALE*i, i);
}
UNSAFE.putLong(addrWriteCursor, 0L);
UNSAFE.putLong(addrReadCursor, 0L);
}
public void send(T value) {
while (!offer(value)){
Thread.yield();//TODO
}
}
public boolean trySend(T value) {
return offer(value);
}
public T tryReceive() {
return poll();
}
public T receive() {
T v;
while ( null==(v=poll()) ) {
Thread.yield();//TODO
}
return v;
}
public boolean isReceivable() {
throw
new UnsupportedOperationException("isReceivable is not available now");
}
public boolean notReceivable() {
throw
new UnsupportedOperationException("notReceivable is not available now");
}
/**
* To add one long value to current {@link z.channel.GenericMPMCQueue}.
* The added long value will stored in the {@link z.channel.GenericMPMCQueue}
* in FIFO order.
* <p>
* @return - true if successfully added to the currrent
* {@link z.channel.GenericMPMCQueue},
* or false if currrent {@link z.channel.GenericMPMCQueue}
* is full.
*/
public boolean offer(T value) {
long writeCursor, seqOffset;
writeCursor = UNSAFE.getLongVolatile(null,addrWriteCursor);
for (;;) {
seqOffset = LONG_BASE+LONG_SCALE*(writeCursor & bufferSlotMask);
long seq = UNSAFE.getLongVolatile(slotSeqs, seqOffset);
long dif = seq - writeCursor;
if (dif == 0) {
if (UNSAFE.compareAndSwapLong(null,
addrWriteCursor, writeCursor, writeCursor + 1))
break;
} else if (dif < 0) {
return false;
} else {
writeCursor = UNSAFE.getLongVolatile(null,addrWriteCursor);
}
}
UNSAFE.putOrderedObject(slots,
BASE+SCALE*(writeCursor & bufferSlotMask), value);
UNSAFE.putLongVolatile(slotSeqs, seqOffset, writeCursor + 1);
return true;
}
/**
* To Retrieves and removes one long value from current
* {@link z.channel.GenericMPMCQueue}.
*
* This long value is sent before in FIFO order.
* <p>
* @return - one long value if currrent {@link z.channel.GenericMPMCQueue}
* holds at least one value,
* or null if currrent {@link z.channel.GenericMPMCQueue}
* is empty. Please use the null to check your result.
*/
public T poll() {
long readCursor, seqOffset;
readCursor = UNSAFE.getLongVolatile(null,addrReadCursor);
for (;;) {
seqOffset = LONG_BASE+LONG_SCALE*(readCursor & bufferSlotMask);
long seq = UNSAFE.getLongVolatile(slotSeqs, seqOffset);
long dif = seq - (readCursor + 1);
if (dif == 0) {
if (UNSAFE.compareAndSwapLong(null,
addrReadCursor, readCursor, readCursor + 1))
break;
} else if (dif < 0) {
return null;
} else {
readCursor = UNSAFE.getLongVolatile(null, addrReadCursor);
}
}
T value = (T)UNSAFE.getObjectVolatile(slots,
BASE+SCALE*(readCursor & bufferSlotMask));
UNSAFE.putLongVolatile(slotSeqs, seqOffset, readCursor + nBufferSlots);
return value;
}
@Override
public void finalize() {
systemFreeMemory(addressRaw);
}
}