/*
* Copyright (c) 2015-2016, Christoph Engelbert (aka noctarius) and
* 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 com.noctarius.tengi.spi.ringbuffer.impl;
import com.noctarius.tengi.core.impl.UnsafeUtil;
import com.noctarius.tengi.spi.ringbuffer.RingBuffer;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
import java.util.ConcurrentModificationException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class NonBlockingRingBuffer<E>
implements RingBuffer<E> {
private static final long ARRAY_BASE = UnsafeUtil.OBJECT_ARRAY_BASE;
private static final long ARRAY_SHIFT = UnsafeUtil.OBJECT_ARRAY_SHIFT;
private static final Unsafe UNSAFE = UnsafeUtil.UNSAFE;
private static final long READER_OFFSET;
private static final long WRITER_OFFSET;
static {
try {
Field field = NonBlockingRingBuffer.class.getDeclaredField("readerIndex");
field.setAccessible(true);
READER_OFFSET = UNSAFE.objectFieldOffset(field);
field = NonBlockingRingBuffer.class.getDeclaredField("writerIndex");
field.setAccessible(true);
WRITER_OFFSET = UNSAFE.objectFieldOffset(field);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private final Lock readerLock = new ReentrantLock();
private final Condition readCondition = readerLock.newCondition();
private final Lock writerLock = new ReentrantLock();
private final Condition writeCondition = writerLock.newCondition();
private final int capacity;
private final Object[] elements;
// Only updated through sun.misc.Unsafe
private volatile int readerIndex = 0;
private volatile int writerIndex = 0;
public NonBlockingRingBuffer(int capacity) {
this.capacity = capacity;
this.elements = new Object[capacity];
}
@Override
public int capacity() {
return capacity;
}
@Override
public boolean write(E element) {
while (true) {
int writerIndex = this.writerIndex;
int readerIndex = this.readerIndex;
int writerSlot = slot(writerIndex);
int readerSlot = slot(readerIndex);
if (!slotAvailable(writerIndex, readerIndex, writerSlot, readerSlot)) {
return false;
}
if (UNSAFE.compareAndSwapInt(this, WRITER_OFFSET, writerIndex, writerIndex + 1)) {
long arrayOffset = offset(writerSlot);
if (!UNSAFE.compareAndSwapObject(elements, arrayOffset, null, element)) {
throw new ConcurrentModificationException("Illegal concurrent ringbuffer update");
}
notifyWaitingReaders();
return true;
}
}
}
private boolean slotAvailable(int writerIndex, int readerIndex, int writerSlot, int readerSlot) {
if (writerIndex == 0 && readerIndex == 0) {
return true;
}
if (writerIndex == capacity && readerIndex == 0) {
return false;
}
if (writerIndex > readerIndex && writerSlot == readerSlot) {
return false;
}
return true;
}
@Override
public boolean write(E element, long timeout, TimeUnit timeUnit)
throws InterruptedException {
long deadline = System.nanoTime() + timeUnit.toNanos(timeout);
while (true) {
if (write(element)) {
return true;
}
if (deadlineReached(deadline)) {
return false;
}
writerLock.lock();
try {
writeCondition.await(remainingDeadline(deadline), TimeUnit.NANOSECONDS);
} finally {
writerLock.unlock();
}
if (deadlineReached(deadline)) {
return false;
}
}
}
@Override
public E read() {
while (true) {
int writerIndex = this.writerIndex;
int readerIndex = this.readerIndex;
if (readerIndex == -1 && writerIndex == 0) {
return null;
}
if (readerIndex >= writerIndex) {
return null;
}
int position = readerIndex == -1 ? 0 : readerIndex;
if (UNSAFE.compareAndSwapInt(this, READER_OFFSET, readerIndex, position + 1)) {
int readerSlot = slot(position);
long arrayOffset = offset(readerSlot);
E element = (E) UNSAFE.getObjectVolatile(elements, arrayOffset);
if (element == null) {
// Try until object is available
do {
element = (E) UNSAFE.getObjectVolatile(elements, arrayOffset);
} while (element == null);
}
if (!UNSAFE.compareAndSwapObject(elements, arrayOffset, element, null)) {
throw new ConcurrentModificationException("Illegal concurrent ringbuffer update");
}
notifyWaitingWriters();
return element;
}
}
}
@Override
public E read(long timeout, TimeUnit timeUnit)
throws InterruptedException {
long deadline = System.nanoTime() + timeUnit.toNanos(timeout);
while (true) {
E element = read();
if (element != null) {
return element;
}
if (deadlineReached(deadline)) {
return null;
}
readerLock.lock();
try {
readCondition.await(remainingDeadline(deadline), TimeUnit.NANOSECONDS);
} finally {
readerLock.unlock();
}
if (deadlineReached(deadline)) {
return null;
}
}
}
@Override
public void clear() {
}
private long offset(long index) {
return (index << ARRAY_SHIFT) + ARRAY_BASE;
}
private int slot(int index) {
return index % capacity;
}
long remainingDeadline(long deadline) {
return System.nanoTime() - deadline;
}
boolean deadlineReached(long deadline) {
return System.nanoTime() - deadline > 0;
}
private void notifyWaitingWriters() {
writerLock.lock();
try {
writeCondition.signal();
} finally {
writerLock.unlock();
}
}
private void notifyWaitingReaders() {
readerLock.lock();
try {
readCondition.signal();
} finally {
readerLock.unlock();
}
}
}