/*
* Copyright (c) 2011-2017 Pivotal Software Inc, All Rights Reserved.
*
* 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 reactor.core.publisher;
import java.io.Serializable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.locks.LockSupport;
import java.util.function.Consumer;
import java.util.function.LongSupplier;
import java.util.function.Supplier;
import java.util.stream.Stream;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import reactor.core.Exceptions;
import reactor.core.Scannable;
import reactor.util.concurrent.QueueSupplier;
import reactor.util.concurrent.WaitStrategy;
/**
* A base processor used by executor backed processors to take care of their ExecutorService
*
* @author Stephane Maldini
*/
abstract class EventLoopProcessor<IN> extends FluxProcessor<IN, IN>
implements Runnable {
static <E> Flux<E> coldSource(RingBuffer<Slot<E>> ringBuffer, Throwable t, Throwable error,
RingBuffer.Sequence start){
Flux<E> bufferIterable = generate(start::getAsLong, (seq, sink) -> {
long s = seq + 1;
if(s > ringBuffer.getCursor()){
sink.complete();
}
else {
E d = ringBuffer.get(s).value;
if (d != null) {
sink.next(d);
}
}
return s;
});
if (error != null) {
if (t != null) {
if (t != error) {
t.addSuppressed(error);
}
return concat(bufferIterable, Flux.error(t));
}
return concat(bufferIterable, Flux.error(error));
}
return bufferIterable;
}
/**
* Create a {@link Runnable} event loop that will keep monitoring a {@link
* LongSupplier} and compare it to a {@link RingBuffer}
*
* @param upstream the {@link Subscription} to request/cancel on
* @param stopCondition {@link Runnable} evaluated in the spin loop that may throw
* @param postWaitCallback a {@link Consumer} notified with the latest sequence read
* @param readCount a {@link LongSupplier} a sequence cursor to wait on
* @param waitStrategy a {@link WaitStrategy} to trade off cpu cycle for latency
* @param errorSubscriber an error subscriber if request/cancel fails
* @param prefetch the target prefetch size
*
* @return a {@link Runnable} loop to execute to start the requesting loop
*/
static Runnable createRequestTask(Subscription upstream,
Runnable stopCondition,
Consumer<Long> postWaitCallback,
LongSupplier readCount,
WaitStrategy waitStrategy,
Subscriber<?> errorSubscriber,
int prefetch) {
return new RequestTask(upstream,
stopCondition,
postWaitCallback,
readCount,
waitStrategy,
errorSubscriber,
prefetch);
}
/**
* Create a new single producer RingBuffer using the default wait strategy {@link
* WaitStrategy#busySpin()}. <p>See {@code MultiProducerRingBuffer}.
*
* @param <E> the element type
* @param bufferSize number of elements to create within the ring buffer.
*
* @return the new RingBuffer instance
*/
@SuppressWarnings("unchecked")
static <E> RingBuffer<Slot<E>> createSingleProducer(int bufferSize) {
return RingBuffer.createSingleProducer(EMITTED,
bufferSize,
WaitStrategy.busySpin());
}
/**
* Spin CPU until the request {@link LongSupplier} is populated at least once by a
* strict positive value. To relieve the spin loop, the read sequence itself will be
* used against so it will wake up only when a signal is emitted upstream or other
* stopping condition including terminal signals thrown by the {@link
* RingBuffer.Reader} waiting barrier.
*
* @param pendingRequest the {@link LongSupplier} request to observe
* @param barrier {@link RingBuffer.Reader} to wait on
* @param isRunning {@link AtomicBoolean} calling loop running state
* @param nextSequence {@link LongSupplier} ring buffer read cursor
* @param waiter an optional extra spin observer for the wait strategy in {@link
* RingBuffer.Reader}
*
* @return true if a request has been received, false in any other case.
*/
static boolean waitRequestOrTerminalEvent(LongSupplier pendingRequest,
RingBuffer.Reader barrier,
AtomicBoolean isRunning,
LongSupplier nextSequence,
Runnable waiter) {
try {
long waitedSequence;
while (pendingRequest.getAsLong() <= 0L) {
//pause until first request
waitedSequence = nextSequence.getAsLong() + 1;
waiter.run();
barrier.waitFor(waitedSequence, waiter);
if (!isRunning.get()) {
WaitStrategy.alert();
}
LockSupport.parkNanos(1L);
}
}
catch (InterruptedException ie) {
Thread.currentThread()
.interrupt();
}
catch (Exception e) {
if (!isRunning.get() || WaitStrategy.isAlert(e)) {
return false;
}
throw e;
}
return true;
}
@SuppressWarnings("rawtypes")
static final Supplier EMITTED = Slot::new;
/**
* Concurrent addition bound to Long.MAX_VALUE. Any concurrent write will "happen"
* before this operation.
*
* @param sequence current sequence to update
* @param toAdd delta to add
*/
static void addCap(RingBuffer.Sequence sequence, long toAdd) {
long u, r;
do {
r = sequence.getAsLong();
if (r == Long.MAX_VALUE) {
return;
}
u = Operators.addCap(r, toAdd);
}
while (!sequence.compareAndSet(r, u));
}
/**
* Concurrent substraction bound to 0 and Long.MAX_VALUE. Any concurrent write will
* "happen" before this operation.
*
* @param sequence current sequence to update
* @param toSub delta to sub
*
* @return value before subscription, 0 or Long.MAX_VALUE
*/
static long getAndSub(RingBuffer.Sequence sequence, long toSub) {
long r, u;
do {
r = sequence.getAsLong();
if (r == 0 || r == Long.MAX_VALUE) {
return r;
}
u = Operators.subOrZero(r, toSub);
}
while (!sequence.compareAndSet(r, u));
return r;
}
final ExecutorService executor;
final EventLoopContext contextClassLoader;
final String name;
final boolean autoCancel;
final RingBuffer<Slot<IN>> ringBuffer;
final WaitStrategy readWait = WaitStrategy.liteBlocking();
Subscription upstreamSubscription;
volatile boolean cancelled;
volatile int terminated;
volatile Throwable error;
volatile int subscriberCount;
EventLoopProcessor(
int bufferSize,
ThreadFactory threadFactory,
ExecutorService executor,
boolean autoCancel,
boolean multiproducers,
Supplier<Slot<IN>> factory,
WaitStrategy strategy) {
if (!QueueSupplier.isPowerOfTwo(bufferSize)) {
throw new IllegalArgumentException("bufferSize must be a power of 2 : " + bufferSize);
}
if (bufferSize < 1){
throw new IllegalArgumentException("bufferSize must be strictly positive, " +
"was: "+bufferSize);
}
this.autoCancel = autoCancel;
contextClassLoader = new EventLoopContext(multiproducers);
this.name = defaultName(threadFactory, getClass());
if (executor == null) {
this.executor = Executors.newCachedThreadPool(threadFactory);
}
else {
this.executor = executor;
}
if (multiproducers) {
this.ringBuffer = RingBuffer.createMultiProducer(factory,
bufferSize,
strategy,
this);
}
else {
this.ringBuffer = RingBuffer.createSingleProducer(factory,
bufferSize,
strategy,
this);
}
}
/**
* Return the number of parked elements in the emitter backlog.
*
* @return the number of parked elements in the emitter backlog.
*/
public abstract long getPending();
@Override
public Object scan(Attr key) {
switch(key){
case PARENT:
return upstreamSubscription;
}
return super.scan(key);
}
/**
* A method to extract a name from the ThreadFactory if it turns out to be a Supplier
* (in which case the supplied value string representation is used). Otherwise return
* the current class's simpleName.
*
* @param threadFactory the factory to test for a supplied name
* @param clazz
* @return the name to use in thread pools
*/
protected static String defaultName(ThreadFactory threadFactory, Class<? extends EventLoopProcessor> clazz) {
String name = threadFactory instanceof Supplier ? ((Supplier)
threadFactory).get().toString() : null;
return null != name ? name : clazz.getSimpleName();
}
/**
* A method to create a suitable default {@link ExecutorService} for use in implementors
* {@link #requestTask(Subscription)} (a {@link Executors#newCachedThreadPool() cached
* thread pool}), reusing a main name and appending {@code [request-task]} suffix.
*
* @param name the main thread name used by the processor.
* @return a default {@link ExecutorService} for requestTask.
*/
protected static ExecutorService defaultRequestTaskExecutor(String name) {
return Executors.newCachedThreadPool(r -> new Thread(r,name+"[request-task]"));
}
/**
* Determine whether this {@code Processor} can be used.
*
* @return {@literal true} if this {@code Resource} is alive and can be used, {@literal false} otherwise.
*/
final public boolean alive() {
return 0 == terminated;
}
/**
* Block until all submitted tasks have completed, then do a normal {@code EventLoopProcessor.dispose()}.
* @return if the underlying executor terminated and false if the timeout elapsed before termination
*/
public final boolean awaitAndShutdown() {
return awaitAndShutdown(-1, TimeUnit.SECONDS);
}
/**
* Block until all submitted tasks have completed, then do a normal {@code EventLoopProcessor#dispose()}.
* @param timeout the timeout value
* @param timeUnit the unit for timeout
* @return if the underlying executor terminated and false if the timeout elapsed before termination
*/
public final boolean awaitAndShutdown(long timeout, TimeUnit timeUnit) {
try {
shutdown();
return executor.awaitTermination(timeout, timeUnit);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
return false;
}
}
//FIXME store current subscribers
@Override
public Stream<? extends Scannable> inners() {
return Stream.empty();
}
/**
* Drain is a hot replication of the current buffer delivered if supported. Since it is hot there might be no
* guarantee to see a end if the buffer keeps replenishing due to concurrent producing.
*
* @return a {@link Flux} sequence possibly unbounded of incoming buffered values or empty if not supported.
*/
public Flux<IN> drain(){
return Flux.empty();
}
/**
* Shutdown this {@code Processor}, forcibly halting any work currently executing and discarding any tasks that have
* not yet been executed.
* @return a Flux instance with the remaining undelivered values
*/
final public Flux<IN> forceShutdown() {
int t = terminated;
if (t != FORCED_SHUTDOWN && TERMINATED.compareAndSet(this, t, FORCED_SHUTDOWN)) {
executor.shutdownNow();
}
return drain();
}
/**
* @return a snapshot number of available onNext before starving the resource
*/
final public long getAvailableCapacity() {
return ringBuffer.bufferSize() - ringBuffer.getPending();
}
@Override
final public Throwable getError() {
return error;
}
@Override
final public String toString() {
return "/Processors/" + name + "/" + contextClassLoader.hashCode();
}
@Override
final public int hashCode() {
return contextClassLoader.hashCode();
}
@Override
public boolean isSerialized() {
return contextClassLoader.multiproducer;
}
@Override
final public boolean isTerminated() {
return terminated > 0;
}
@Override
final public void onComplete() {
if (TERMINATED.compareAndSet(this, 0, SHUTDOWN)) {
upstreamSubscription = null;
executor.shutdown();
readWait.signalAllWhenBlocking();
doComplete();
}
}
@Override
final public void onError(Throwable t) {
if (t == null) {
throw Exceptions.argumentIsNullException();
}
if (TERMINATED.compareAndSet(this, 0, SHUTDOWN)) {
error = t;
upstreamSubscription = null;
executor.shutdown();
readWait.signalAllWhenBlocking();
doError(t);
}
}
@Override
final public void onNext(IN o) {
if (o == null) {
throw Exceptions.argumentIsNullException();
}
final long seqId = ringBuffer.next();
final Slot<IN> signal = ringBuffer.get(seqId);
signal.value = o;
ringBuffer.publish(seqId);
}
@Override
final public void onSubscribe(final Subscription s) {
if (Operators.validate(upstreamSubscription, s)) {
this.upstreamSubscription = s;
try {
if (s != Operators.emptySubscription()) {
requestTask(s);
}
}
catch (Throwable t) {
onError(Operators.onOperatorError(s, t));
}
}
}
/**
* Shutdown this active {@code Processor} such that it can no longer be used. If the resource carries any work, it
* will wait (but NOT blocking the caller) for all the remaining tasks to perform before closing the resource.
*/
public final void shutdown() {
try {
onComplete();
executor.shutdown();
specificShutdown();
}
catch (Throwable t) {
onError(Operators.onOperatorError(t));
}
}
/**
* A method to be overridden by implementors that need additional steps on {@link #shutdown()}
* after the main executor's shutdown and onComplete signal.
*/
protected void specificShutdown() {
//NO-OP
}
@Override
final public int getBufferSize() {
return ringBuffer.bufferSize();
}
final void cancel() {
cancelled = true;
if (TERMINATED.compareAndSet(this, 0, SHUTDOWN)) {
executor.shutdown();
}
readWait.signalAllWhenBlocking();
}
protected void doComplete() {
}
/**
* An async request client for ring buffer impls
*
* @author Stephane Maldini
*/
static final class RequestTask implements Runnable {
final WaitStrategy waitStrategy;
final LongSupplier readCount;
final Subscription upstream;
final Runnable spinObserver;
final Consumer<Long> postWaitCallback;
final Subscriber<?> errorSubscriber;
final int prefetch;
RequestTask(Subscription upstream,
Runnable stopCondition,
Consumer<Long> postWaitCallback,
LongSupplier readCount,
WaitStrategy waitStrategy,
Subscriber<?> errorSubscriber,
int prefetch) {
this.waitStrategy = waitStrategy;
this.readCount = readCount;
this.postWaitCallback = postWaitCallback;
this.errorSubscriber = errorSubscriber;
this.upstream = upstream;
this.spinObserver = stopCondition;
this.prefetch = prefetch;
}
@Override
public void run() {
final long bufferSize = prefetch;
final long limit = bufferSize == 1 ? bufferSize : bufferSize - Math.max(bufferSize >> 2, 1);
long cursor = -1;
try {
spinObserver.run();
upstream.request(bufferSize);
long c;
//noinspection InfiniteLoopStatement
for (; ; ) {
c = cursor + limit;
cursor = waitStrategy.waitFor(c, readCount, spinObserver);
if (postWaitCallback != null) {
postWaitCallback.accept(cursor);
}
//spinObserver.accept(null);
upstream.request(limit + (cursor - c));
}
}
catch (InterruptedException e) {
Thread.currentThread()
.interrupt();
}
catch (Throwable t) {
if(Exceptions.isCancel(t)){
upstream.cancel();
return;
}
if(WaitStrategy.isAlert(t)){
return;
}
errorSubscriber.onError(Operators.onOperatorError(t));
}
}
}
protected void requestTask(final Subscription s) {
//implementation might run a specific request task for the given subscription
}
final void decrementSubscribers() {
Subscription subscription = upstreamSubscription;
int subs = SUBSCRIBER_COUNT.decrementAndGet(this);
if (subs == 0) {
if (subscription != null && autoCancel) {
upstreamSubscription = null;
cancel();
}
}
}
@Override
public long downstreamCount() {
return subscriberCount;
}
abstract void doError(Throwable throwable);
final boolean incrementSubscribers() {
return SUBSCRIBER_COUNT.getAndIncrement(this) == 0;
}
final static class EventLoopContext extends ClassLoader {
final boolean multiproducer;
EventLoopContext(boolean multiproducer) {
super(Thread.currentThread()
.getContextClassLoader());
this.multiproducer = multiproducer;
}
}
static final int SHUTDOWN = 1;
static final int FORCED_SHUTDOWN = 2;
@SuppressWarnings("rawtypes")
static final AtomicIntegerFieldUpdater<EventLoopProcessor> SUBSCRIBER_COUNT =
AtomicIntegerFieldUpdater.newUpdater(EventLoopProcessor.class, "subscriberCount");
@SuppressWarnings("rawtypes")
final static AtomicIntegerFieldUpdater<EventLoopProcessor> TERMINATED =
AtomicIntegerFieldUpdater.newUpdater(EventLoopProcessor.class, "terminated");
/**
* A simple reusable data container.
*
* @param <T> the value type
*/
public static final class Slot<T> implements Serializable {
private static final long serialVersionUID = 5172014386416785095L;
public T value;
}
final static class EventLoopFactory
implements ThreadFactory, Supplier<String> {
/** */
static final AtomicInteger COUNT = new AtomicInteger();
final String name;
final boolean daemon;
EventLoopFactory(String name, boolean daemon) {
this.name = name;
this.daemon = daemon;
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, name + "-" + COUNT.incrementAndGet());
t.setDaemon(daemon);
return t;
}
@Override
public String get() {
return name;
}
}
}