/**
* Copyright 2016 Yahoo Inc.
*
* 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.yahoo.pulsar.common.util.collections;
import java.util.AbstractQueue;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import io.netty.util.internal.MathUtil;
/**
* This implements a {@link BlockingQueue} backed by an array with no fixed capacity.
*
* When the capacity is reached, data will be moved to a bigger array.
*
*/
public class GrowableArrayBlockingQueue<T> extends AbstractQueue<T> implements BlockingQueue<T> {
private final ReentrantLock headLock = new ReentrantLock();
private final PaddedInt headIndex = new PaddedInt();
private final PaddedInt tailIndex = new PaddedInt();
private final ReentrantLock tailLock = new ReentrantLock();
private final Condition isNotEmpty = headLock.newCondition();
private T[] data;
private static final AtomicIntegerFieldUpdater<GrowableArrayBlockingQueue> SIZE_UPDATER =
AtomicIntegerFieldUpdater.newUpdater(GrowableArrayBlockingQueue.class, "size");
private volatile int size = 0;
public GrowableArrayBlockingQueue() {
this(64);
}
@SuppressWarnings("unchecked")
public GrowableArrayBlockingQueue(int initialCapacity) {
headIndex.value = 0;
tailIndex.value = 0;
int capacity = MathUtil.findNextPositivePowerOfTwo(initialCapacity);
data = (T[]) new Object[capacity];
}
@Override
public T remove() {
T item = poll();
if (item == null) {
throw new NoSuchElementException();
}
return item;
}
@Override
public T poll() {
headLock.lock();
try {
if (SIZE_UPDATER.get(this) > 0) {
T item = data[headIndex.value];
headIndex.value = (headIndex.value + 1) & (data.length - 1);
SIZE_UPDATER.decrementAndGet(this);
return item;
} else {
return null;
}
} finally {
headLock.unlock();
}
}
@Override
public T element() {
T item = peek();
if (item == null) {
throw new NoSuchElementException();
}
return item;
}
@Override
public T peek() {
headLock.lock();
try {
if (SIZE_UPDATER.get(this) > 0) {
return data[headIndex.value];
} else {
return null;
}
} finally {
headLock.unlock();
}
}
@Override
public boolean offer(T e) {
// Queue is unbounded and it will never reject new items
put(e);
return true;
}
@Override
public void put(T e) {
tailLock.lock();
boolean wasEmpty = false;
try {
if (SIZE_UPDATER.get(this) == data.length) {
expandArray();
}
data[tailIndex.value] = e;
tailIndex.value = (tailIndex.value + 1) & (data.length - 1);
if (SIZE_UPDATER.getAndIncrement(this) == 0) {
wasEmpty = true;
}
} finally {
tailLock.unlock();
}
if (wasEmpty) {
headLock.lock();
try {
isNotEmpty.signal();
} finally {
headLock.unlock();
}
}
}
@Override
public boolean add(T e) {
put(e);
return true;
}
@Override
public boolean offer(T e, long timeout, TimeUnit unit) {
// Queue is unbounded and it will never reject new items
put(e);
return true;
}
@Override
public T take() throws InterruptedException {
headLock.lockInterruptibly();
try {
while (SIZE_UPDATER.get(this) == 0) {
isNotEmpty.await();
}
T item = data[headIndex.value];
data[headIndex.value] = null;
headIndex.value = (headIndex.value + 1) & (data.length - 1);
if (SIZE_UPDATER.decrementAndGet(this) > 0) {
// There are still entries to consume
isNotEmpty.signal();
}
return item;
} finally {
headLock.unlock();
}
}
@Override
public T poll(long timeout, TimeUnit unit) throws InterruptedException {
headLock.lockInterruptibly();
try {
long timeoutNanos = unit.toNanos(timeout);
while (SIZE_UPDATER.get(this) == 0) {
if (timeoutNanos <= 0) {
return null;
}
timeoutNanos = isNotEmpty.awaitNanos(timeoutNanos);
}
T item = data[headIndex.value];
data[headIndex.value] = null;
headIndex.value = (headIndex.value + 1) & (data.length - 1);
if (SIZE_UPDATER.decrementAndGet(this) > 0) {
// There are still entries to consume
isNotEmpty.signal();
}
return item;
} finally {
headLock.unlock();
}
}
@Override
public int remainingCapacity() {
return Integer.MAX_VALUE;
}
@Override
public int drainTo(Collection<? super T> c) {
return drainTo(c, Integer.MAX_VALUE);
}
@Override
public int drainTo(Collection<? super T> c, int maxElements) {
headLock.lock();
try {
int drainedItems = 0;
int size = SIZE_UPDATER.get(this);
while (size > 0 && drainedItems < maxElements) {
T item = data[headIndex.value];
data[headIndex.value] = null;
c.add(item);
headIndex.value = (headIndex.value + 1) & (data.length - 1);
--size;
++drainedItems;
}
if (SIZE_UPDATER.addAndGet(this, -drainedItems) > 0) {
// There are still entries to consume
isNotEmpty.signal();
}
return drainedItems;
} finally {
headLock.unlock();
}
}
@Override
public void clear() {
headLock.lock();
try {
int size = SIZE_UPDATER.get(this);
for (int i = 0; i < size; i++) {
data[headIndex.value] = null;
headIndex.value = (headIndex.value + 1) & (data.length - 1);
}
if (SIZE_UPDATER.addAndGet(this, -size) > 0) {
// There are still entries to consume
isNotEmpty.signal();
}
} finally {
headLock.unlock();
}
}
@Override
public int size() {
return SIZE_UPDATER.get(this);
}
@Override
public Iterator<T> iterator() {
throw new UnsupportedOperationException();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
tailLock.lock();
headLock.lock();
try {
int headIndex = this.headIndex.value;
int size = SIZE_UPDATER.get(this);
sb.append('[');
for (int i = 0; i < size; i++) {
T item = data[headIndex];
if (i > 0) {
sb.append(", ");
}
sb.append(item);
headIndex = (headIndex + 1) & (data.length - 1);
}
sb.append(']');
} finally {
headLock.unlock();
tailLock.unlock();
}
return sb.toString();
}
@SuppressWarnings("unchecked")
private void expandArray() {
// We already hold the tailLock
headLock.lock();
try {
int size = SIZE_UPDATER.get(this);
int newCapacity = data.length * 2;
T[] newData = (T[]) new Object[newCapacity];
int oldHeadIndex = headIndex.value;
int newTailIndex = 0;
for (int i = 0; i < size; i++) {
newData[newTailIndex++] = data[oldHeadIndex];
oldHeadIndex = (oldHeadIndex + 1) & (data.length - 1);
}
data = newData;
headIndex.value = 0;
tailIndex.value = size;
} finally {
headLock.unlock();
}
}
final static class PaddedInt {
private int value;
// Padding to avoid false sharing
public volatile int pi1 = 1;
public volatile long p1 = 1L, p2 = 2L, p3 = 3L, p4 = 4L, p5 = 5L, p6 = 6L;
public long exposeToAvoidOptimization() {
return pi1 + p1 + p2 + p3 + p4 + p5 + p6;
}
}
}