
io.datakernel.csp.queue.ChannelBuffer Maven / Gradle / Ivy
Show all versions of datakernel-csp Show documentation
/*
* Copyright (C) 2015-2018 SoftIndex LLC.
*
* 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 io.datakernel.csp.queue;
import io.datakernel.promise.Promise;
import io.datakernel.promise.SettablePromise;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static io.datakernel.common.Recyclable.tryRecycle;
/**
* Represents a queue of elements which you can {@code put} and {@code take}.
* In order to mark if an object is pending put or take to/from the queue,
* there are corresponding {@code put} and {@code take} {@link SettablePromise}s.
*
* @param the type of values that are stored in the buffer
*/
public final class ChannelBuffer implements ChannelQueue {
private Exception exception;
private Object[] elements;
private int tail;
private int head;
private final int bufferMinSize;
private final int bufferMaxSize;
@Nullable
private SettablePromise put;
@Nullable
private SettablePromise take;
/**
* @see #ChannelBuffer(int, int)
*/
public ChannelBuffer(int bufferSize) {
this(0, bufferSize);
}
/**
* Creates a ChannelBuffer with the buffer size of the next highest
* power of 2 (for example, if {@code bufferMaxSize = 113}, a buffer
* of 128 elements will be created).
*
* @param bufferMinSize a minimal amount of elements in the buffer
* @param bufferMaxSize a max amount of elements in the buffer
*/
public ChannelBuffer(int bufferMinSize, int bufferMaxSize) {
this.bufferMinSize = bufferMinSize + 1;
this.bufferMaxSize = bufferMaxSize;
//This code computes the next highest power of 2 for a 32-bit integer bufferMaxSize
//If x is greater than 2^31, IllegalArgumentException will be thrown
int x = bufferMaxSize;
if (x > 0) {
x--;
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
x |= x >> 8;
x |= x >> 16;
x++;
if (x < 0) {
throw new IllegalArgumentException("Integer overflow");
}
this.elements = new Object[x];
} else {
this.elements = new Object[1];
}
}
/**
* Checks if this buffer is saturated by comparing
* its current size with {@code bufferMaxSize}.
*
* @return {@code true} if this buffer size is greater
* than {@code bufferMaxSize}, otherwise returns {@code false}
*/
public boolean isSaturated() {
return size() > bufferMaxSize;
}
/**
* Checks if this buffer will be saturated if at
* least one more element will be added, by comparing
* its current size with {@code bufferMaxSize}.
*
* @return {@code true} if this buffer size is
* bigger or equal to the {@code bufferMaxSize},
* otherwise returns {@code false}
*/
public boolean willBeSaturated() {
return size() >= bufferMaxSize;
}
/**
* Checks if this buffer has less
* elements than {@code bufferMinSize}.
*
* @return {@code true} if this buffer size is
* smaller than {@code bufferMinSize},
* otherwise returns {@code false}
*/
public boolean isExhausted() {
return size() < bufferMinSize;
}
/**
* Checks if this buffer will have less elements
* than {@code bufferMinSize}, if at least one
* more element will be taken, by comparing its
* current size with {@code bufferMinSize}.
*
* @return {@code true} if this buffer size is
* smaller or equal to the {@code bufferMinSize},
* otherwise returns {@code false}
*/
public boolean willBeExhausted() {
return size() <= bufferMinSize;
}
public boolean isPendingPut() {
return put != null;
}
public boolean isPendingTake() {
return take != null;
}
/**
* Returns amount of elements in this buffer.
*/
public int size() {
return (tail - head) & (elements.length - 1);
}
/**
* Checks if this buffer contains elements.
*
* @return {@code true} if {@code tail}
* and {@code head} indexes are equal,
* otherwise {@code false}
*/
public boolean isEmpty() {
return tail == head;
}
/**
* Adds provided item to the buffer and resets current {@code take}.
*/
public void add(@Nullable T item) throws Exception {
if (exception == null) {
if (take != null) {
assert isEmpty();
SettablePromise take = this.take;
this.take = null;
take.set(item);
if (exception != null) throw exception;
return;
}
doAdd(item);
} else {
tryRecycle(item);
throw exception;
}
}
private void doAdd(@Nullable T value) {
elements[tail] = value;
tail = (tail + 1) & (elements.length - 1);
if (tail == head) {
doubleCapacity();
}
}
private void doubleCapacity() {
assert head == tail;
int r = elements.length - head;
Object[] newElements = new Object[elements.length << 1];
System.arraycopy(elements, head, newElements, 0, r);
System.arraycopy(elements, 0, newElements, r, head);
elements = newElements;
head = 0;
tail = elements.length;
}
/**
* Returns the element of {@code head} index of
* the buffer if it is not empty, otherwise returns
* {@code null}. Increases the value of {@code head}.
*
* If the buffer will have less elements than {@code bufferMinSize}
* after this poll and {@code put} promise is not {@code null},
* {@code put} will be set {@code null} after the poll.
*
* If current {@code exception} is not {@code null},
* it will be thrown.
*
* @return element of this buffer at {@code head}
* index if the buffer is not empty, otherwise {@code null}
* @throws Exception if current {@code exception}
* is not {@code null}
*/
@Nullable
public T poll() throws Exception {
if (exception != null) throw exception;
if (put != null && willBeExhausted()) {
T item = doPoll();
SettablePromise put = this.put;
this.put = null;
put.set(null);
return item;
}
return !isEmpty() ? doPoll() : null;
}
private T doPoll() {
assert head != tail;
@SuppressWarnings("unchecked")
T result = (T) elements[head];
elements[head] = null; // Must null out slot
head = (head + 1) & (elements.length - 1);
return result;
}
/**
* Puts {@code value} in this buffer at {@code tail}
* index and increases {@code tail} value. If after the
* operation {@code tail} will be equal to {@code head},
* the buffer capacity will be doubled.
*
* Current {@code put} must be {@code null}. If
* current {@code exception} is not {@code null},
* a promise of this exception will be returned and
* the {@code value} will be recycled.
*
* If this {@code take} is not {@code null}, the value
* will be set directly to the {@code set}, without
* adding to the buffer.
*
* @param value a value passed to the buffer
* @return promise of {@code null} or {@code exception}
* as a marker of completion
*/
@Override
public Promise put(@Nullable T value) {
assert put == null;
if (exception == null) {
if (take != null) {
assert isEmpty();
SettablePromise take = this.take;
this.take = null;
take.set(value);
return Promise.complete();
}
doAdd(value);
if (isSaturated()) {
put = new SettablePromise<>();
return put;
} else {
return Promise.complete();
}
} else {
tryRecycle(value);
return Promise.ofException(exception);
}
}
/**
* Returns a promise of the {@code head} index of
* the {@code buffer} if it is not empty.
*
* If this buffer will be exhausted after this
* take and {@code put} promise is not {@code null},
* {@code put} will be set {@code null} after the poll.
*
* Current {@code take} must be {@code null}. If
* current {@code exception} is not {@code null},
* a promise of this exception will be returned.
*
* @return promise of element taken from the buffer
*/
@Override
public Promise take() {
assert take == null;
if (exception == null) {
if (put != null && willBeExhausted()) {
assert !isEmpty();
T item = doPoll();
SettablePromise put = this.put;
this.put = null;
put.set(null);
return Promise.of(item);
}
if (!isEmpty()) {
return Promise.of(doPoll());
}
take = new SettablePromise<>();
return take;
} else {
return Promise.ofException(exception);
}
}
/**
* Closes the buffer if this {@code exception} is not
* {@code null}. Recycles all elements of the buffer and
* sets {@code elements}, {@code put} and {@code take} to
* {@code null}.
*
* @param e exception that is used to close buffer with
*/
@Override
public void close(@NotNull Throwable e) {
if (exception != null) return;
exception = e instanceof Exception ? (Exception) e : new RuntimeException(e);
if (put != null) {
put.setException(e);
put = null;
}
if (take != null) {
take.setException(e);
take = null;
}
for (int i = head; i != tail; i = (i + 1) & (elements.length - 1)) {
tryRecycle(elements[i]);
}
//noinspection AssignmentToNull - resource release
elements = null;
}
@Nullable
public Throwable getException() {
return exception;
}
}