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

com.arangodb.shaded.vertx.core.streams.impl.InboundBuffer Maven / Gradle / Ivy

There is a newer version: 7.8.0
Show newest version
/*
 * 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(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy