Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright (c) 2011-2019 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
*/
package com.arangodb.shaded.vertx.core.streams.impl;
import com.arangodb.shaded.netty.util.concurrent.FastThreadLocalThread;
import com.arangodb.shaded.vertx.core.Context;
import com.arangodb.shaded.vertx.core.Handler;
import com.arangodb.shaded.vertx.core.Vertx;
import com.arangodb.shaded.vertx.core.impl.ContextInternal;
import java.util.ArrayDeque;
import java.util.Collection;
/**
* A buffer that transfers elements to an handler with back-pressure.
*
* The buffer is softly bounded, i.e the producer can {@link #write} any number of elements and shall
* cooperate with the buffer to not overload it.
*
*
Writing to the buffer
* When the producer writes an element to the buffer, the boolean value returned by the {@link #write} method indicates
* whether it can continue safely adding more elements or stop.
*
* The producer can set a {@link #drainHandler} to be signaled when it can resume writing again. When a {@code write}
* returns {@code false}, the drain handler will be called when the buffer becomes writable again. Note that subsequent
* call to {@code write} will not prevent the drain handler to be called.
*
*
Reading from the buffer
* The consumer should set an {@link #handler} to consume elements.
*
*
Buffer mode
* The buffer is either in flowing or fetch mode.
*
* Initially the buffer is in flowing mode.
*
When the buffer is in flowing mode, elements are delivered to the {@code handler}.
*
When the buffer is in fetch mode, only the number of requested elements will be delivered to the {@code handler}.
*
* The mode can be changed with the {@link #pause()}, {@link #resume()} and {@link #fetch} methods:
*
*
Calling {@link #resume()} sets the flowing mode
*
Calling {@link #pause()} sets the fetch mode and resets the demand to {@code 0}
*
Calling {@link #fetch(long)} requests a specific amount of elements and adds it to the actual demand
*
*
*
Concurrency
*
* To avoid data races, write methods must be called from the context thread associated with the buffer, when that's
* not the case, an {@code IllegalStateException} is thrown.
*
* Other methods can be called from any thread.
*
* The handlers will always be called from a context thread.
*
* WARNING: this class is mostly useful for implementing the {@link com.arangodb.shaded.vertx.core.streams.ReadStream}
* and has little or no use within a regular application.
*
* @author Julien Viet
*/
public class InboundBuffer {
/**
* A reusable sentinel for signaling the end of a stream.
*/
public static final Object END_SENTINEL = new Object();
private final ContextInternal context;
private ArrayDeque pending;
private final long highWaterMark;
private long demand;
private Handler handler;
private boolean overflow;
private Handler drainHandler;
private Handler emptyHandler;
private Handler exceptionHandler;
private boolean emitting;
public InboundBuffer(Context context) {
this(context, 16L);
}
public InboundBuffer(Context context, long highWaterMark) {
if (context == null) {
throw new NullPointerException("context must not be null");
}
if (highWaterMark < 0) {
throw new IllegalArgumentException("highWaterMark " + highWaterMark + " >= 0");
}
this.context = (ContextInternal) context;
this.highWaterMark = highWaterMark;
this.demand = Long.MAX_VALUE;
// empty ArrayDeque's constructor ArrayDeque allocates 16 elements; let's delay the allocation to be of the proper size
this.pending = null;
}
private void checkThread() {
if (!context.inThread()) {
throw new IllegalStateException("This operation must be called from a Vert.x thread");
}
}
/**
* Write an {@code element} to the buffer. The element will be delivered synchronously to the handler when
* it is possible, otherwise it will be queued for later delivery.
*
* @param element the element to add
* @return {@code false} when the producer should stop writing
*/
public boolean write(E element) {
checkThread();
Handler handler;
synchronized (this) {
if (demand == 0L || emitting) {
if (pending == null) {
pending = new ArrayDeque<>(1);
}
pending.add(element);
return checkWritable();
} else {
if (demand != Long.MAX_VALUE) {
--demand;
}
emitting = true;
handler = this.handler;
}
}
handleEvent(handler, element);
return emitPending();
}
private boolean checkWritable() {
if (demand == Long.MAX_VALUE) {
return true;
} else {
long actual = size() - demand;
boolean writable = actual < highWaterMark;
overflow |= !writable;
return writable;
}
}
/**
* Write an {@code iterable} of {@code elements}.
*
* @see #write(E)
* @param elements the elements to add
* @return {@code false} when the producer should stop writing
*/
public boolean write(Iterable elements) {
checkThread();
synchronized (this) {
final int requiredCapacity;
if (pending == null) {
if (elements instanceof Collection) {
requiredCapacity = ((Collection) elements).size();
} else {
requiredCapacity = 1;
}
pending = new ArrayDeque<>(requiredCapacity);
}
for (E element : elements) {
pending.add(element);
}
if (demand == 0L || emitting) {
return checkWritable();
} else {
emitting = true;
}
}
return emitPending();
}
private boolean emitPending() {
E element;
Handler h;
while (true) {
synchronized (this) {
int size = size();
if (demand == 0L) {
emitting = false;
boolean writable = size < highWaterMark;
overflow |= !writable;
return writable;
} else if (size == 0) {
emitting = false;
return true;
}
if (demand != Long.MAX_VALUE) {
demand--;
}
assert pending != null;
element = pending.poll();
h = this.handler;
}
handleEvent(h, element);
}
}
/**
* Drain the buffer.
*
* Calling this assumes {@code (demand > 0L && !pending.isEmpty()) == true}
*/
private void drain() {
int emitted = 0;
Handler drainHandler;
Handler emptyHandler;
while (true) {
E element;
Handler handler;
synchronized (this) {
int size = size();
if (size == 0) {
emitting = false;
if (overflow) {
overflow = false;
drainHandler = this.drainHandler;
} else {
drainHandler = null;
}
emptyHandler = emitted > 0 ? this.emptyHandler : null;
break;
} else if (demand == 0L) {
emitting = false;
return;
}
emitted++;
if (demand != Long.MAX_VALUE) {
demand--;
}
assert pending != null;
element = pending.poll();
handler = this.handler;
}
handleEvent(handler, element);
}
if (drainHandler != null) {
handleEvent(drainHandler, null);
}
if (emptyHandler != null) {
handleEvent(emptyHandler, null);
}
}
private void handleEvent(Handler handler, T element) {
if (handler != null) {
try {
handler.handle(element);
} catch (Throwable t) {
handleException(t);
}
}
}
private void handleException(Throwable err) {
Handler handler;
synchronized (this) {
if ((handler = exceptionHandler) == null) {
return;
}
}
handler.handle(err);
}
/**
* Request a specific {@code amount} of elements to be fetched, the amount is added to the actual demand.
*
* Pending elements in the buffer will be delivered asynchronously on the context to the handler.
*
* This method can be called from any thread.
*
* @return {@code true} when the buffer will be drained
*/
public boolean fetch(long amount) {
if (amount < 0L) {
throw new IllegalArgumentException();
}
synchronized (this) {
demand += amount;
if (demand < 0L) {
demand = Long.MAX_VALUE;
}
if (emitting || (isEmpty() && !overflow)) {
return false;
}
emitting = true;
}
context.runOnContext(v -> drain());
return true;
}
/**
* Read the most recent element synchronously.
*
* No handler will be called.
*
* @return the most recent element or {@code null} if no element was in the buffer
*/
public E read() {
synchronized (this) {
if (isEmpty()) {
return null;
}
return pending.poll();
}
}
/**
* Clear the buffer synchronously.
*
* No handler will be called.
*
* @return a reference to this, so the API can be used fluently
*/
public synchronized InboundBuffer clear() {
if (isEmpty()) {
return this;
}
pending.clear();
return this;
}
/**
* Pause the buffer, it sets the buffer in {@code fetch} mode and clears the actual demand.
*
* @return a reference to this, so the API can be used fluently
*/
public synchronized InboundBuffer pause() {
demand = 0L;
return this;
}
/**
* Resume the buffer, and sets the buffer in {@code flowing} mode.
*
* Pending elements in the buffer will be delivered asynchronously on the context to the handler.
*
* This method can be called from any thread.
*
* @return {@code true} when the buffer will be drained
*/
public boolean resume() {
return fetch(Long.MAX_VALUE);
}
/**
* Set an {@code handler} to be called with elements available from this buffer.
*
* @param handler the handler
* @return a reference to this, so the API can be used fluently
*/
public synchronized InboundBuffer handler(Handler handler) {
this.handler = handler;
return this;
}
/**
* Set an {@code handler} to be called when the buffer is drained and the producer can resume writing to the buffer.
*
* @param handler the handler to be called
* @return a reference to this, so the API can be used fluently
*/
public synchronized InboundBuffer drainHandler(Handler handler) {
drainHandler = handler;
return this;
}
/**
* Set an {@code handler} to be called when the buffer becomes empty.
*
* @param handler the handler to be called
* @return a reference to this, so the API can be used fluently
*/
public synchronized InboundBuffer emptyHandler(Handler handler) {
emptyHandler = handler;
return this;
}
/**
* Set an {@code handler} to be called when an exception is thrown by an handler.
*
* @param handler the handler
* @return a reference to this, so the API can be used fluently
*/
public synchronized InboundBuffer exceptionHandler(Handler handler) {
exceptionHandler = handler;
return this;
}
/**
* @return whether the buffer is empty
*/
public synchronized boolean isEmpty() {
if (pending == null) {
return true;
}
return pending.isEmpty();
}
/**
* @return whether the buffer is writable
*/
public synchronized boolean isWritable() {
return size() < highWaterMark;
}
/**
* @return whether the buffer is paused, i.e it is in {@code fetch} mode and the demand is {@code 0}.
*/
public synchronized boolean isPaused() {
return demand == 0L;
}
/**
* @return the actual number of elements in the buffer
*/
public synchronized int size() {
return pending == null ? 0 : pending.size();
}
}