All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.datakernel.csp.queue.ChannelBuffer Maven / Gradle / Ivy

Go to download

Communicating sequential process via channels, similar to Golang's channels. A channel could be imagine as a pipe which connects some processes.

The newest version!
/*
 * 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; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy