/*
* Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com
* The software in this package is published under the terms of the CPAL v1.0
* license, a copy of which has been included with this distribution in the
* LICENSE.txt file.
*/
package org.mule.runtime.core.processor.strategy;
import static java.util.Objects.requireNonNull;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.mule.runtime.core.processor.strategy.AbstractStreamProcessingStrategyFactory.AbstractStreamProcessingStrategy.WaitStrategy.LITE_BLOCKING;
import static org.mule.runtime.core.processor.strategy.AbstractStreamProcessingStrategyFactory.AbstractStreamProcessingStrategy.WaitStrategy.valueOf;
import static reactor.util.concurrent.QueueSupplier.SMALL_BUFFER_SIZE;
import static reactor.util.concurrent.QueueSupplier.isPowerOfTwo;
import static reactor.util.concurrent.WaitStrategy.blocking;
import static reactor.util.concurrent.WaitStrategy.busySpin;
import static reactor.util.concurrent.WaitStrategy.liteBlocking;
import static reactor.util.concurrent.WaitStrategy.parking;
import static reactor.util.concurrent.WaitStrategy.phasedOffLiteLock;
import static reactor.util.concurrent.WaitStrategy.sleeping;
import static reactor.util.concurrent.WaitStrategy.yielding;
import org.mule.runtime.api.scheduler.Scheduler;
import org.mule.runtime.core.api.Event;
import org.mule.runtime.core.api.construct.FlowConstruct;
import org.mule.runtime.core.api.processor.ReactiveProcessor;
import org.mule.runtime.core.api.processor.Sink;
import org.mule.runtime.core.api.processor.strategy.ProcessingStrategy;
import org.mule.runtime.core.api.processor.strategy.ProcessingStrategyFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
import reactor.core.Disposable;
import reactor.core.publisher.WorkQueueProcessor;
/**
* Abstract {@link ProcessingStrategyFactory} to be used by implementations that de-multiplex incoming messages using a
* ring-buffer which can then be subscribed to n times.
* <p/>
* Processing strategies created with this factory are not suitable for transactional flows and will fail if used with an active
* transaction.
*
* @since 4.0
*/
abstract class AbstractStreamProcessingStrategyFactory extends AbstractProcessingStrategyFactory {
public static final int DEFAULT_BUFFER_SIZE = SMALL_BUFFER_SIZE;
public static final int DEFAULT_SUBSCRIBER_COUNT = 1;
public static final String DEFAULT_WAIT_STRATEGY = LITE_BLOCKING.name();
protected static String RING_BUFFER_SCHEDULER_NAME_SUFFIX = ".ring-buffer";
private int bufferSize = DEFAULT_BUFFER_SIZE;
private int subscriberCount = DEFAULT_SUBSCRIBER_COUNT;
private String waitStrategy = DEFAULT_WAIT_STRATEGY;
/**
* Configure the size of the ring-buffer size used to buffer and de-multiplexes events from multiple source threads. This value
* must be a power-of two.
* <p/>
* Ring buffers typically use a power of two because it means that the rollover at the end of the buffer can be achieved using a
* bit mask rather than having to explicitly compare the head/tail pointer with the end of the buffer.
*
* @param bufferSize buffer size to use.
*/
public void setBufferSize(int bufferSize) {
if (!isPowerOfTwo(bufferSize)) {
throw new IllegalArgumentException("bufferSize must be a power of 2 : " + bufferSize);
}
this.bufferSize = bufferSize;
}
/**
* Configure the number of ring-buffer subscribers.
*
* @param subscriberCount
*/
public void setSubscriberCount(int subscriberCount) {
this.subscriberCount = subscriberCount;
}
/**
* Configure the wait strategy used to wait for new events on ring-buffer.
*
* @param waitStrategy
*/
public void setWaitStrategy(String waitStrategy) {
this.waitStrategy = waitStrategy;
}
protected int getBufferSize() {
return bufferSize;
}
protected int getSubscriberCount() {
return subscriberCount;
}
protected String getWaitStrategy() {
return waitStrategy;
}
/**
* Abstract {@link ProcessingStrategy} to be used by implementations that de-multiplex incoming messages using a ring-buffer
* which can then be subscribed to n times.
* <p/>
* This processing strategy is not suitable for transactional flows and will fail if used with an active transaction.
*
* @since 4.0
*/
protected abstract static class AbstractStreamProcessingStrategy extends AbstractProcessingStrategy {
final protected Supplier<Scheduler> ringBufferSchedulerSupplier;
final protected int bufferSize;
final protected int subscribers;
final protected WaitStrategy waitStrategy;
final protected int maxConcurrency;
public AbstractStreamProcessingStrategy(Supplier<Scheduler> ringBufferSchedulerSupplier, int bufferSize, int subscribers,
String waitStrategy,
int maxConcurrency) {
this.subscribers = requireNonNull(subscribers);
this.waitStrategy = valueOf(waitStrategy);
this.bufferSize = requireNonNull(bufferSize);
this.ringBufferSchedulerSupplier = requireNonNull(ringBufferSchedulerSupplier);
this.maxConcurrency = requireNonNull(maxConcurrency);
}
@Override
public Sink createSink(FlowConstruct flowConstruct, ReactiveProcessor function) {
WorkQueueProcessor<Event> processor =
WorkQueueProcessor.share(ringBufferSchedulerSupplier.get(), bufferSize, waitStrategy.getReactorWaitStrategy(), false);
List<Disposable> disposables = new ArrayList<>();
for (int i = 0; i < (maxConcurrency < subscribers ? maxConcurrency : subscribers); i++) {
disposables.add(processor.transform(function).subscribe());
}
disposables.add(() -> processor.shutdown());
return new ReactorSink(processor.connectSink(), () -> disposables.forEach(disposable -> disposable.dispose()),
createOnEventConsumer());
}
protected enum WaitStrategy {
BLOCKING(blocking()),
LITE_BLOCKING(liteBlocking()),
SLEEPING(sleeping()),
BUSY_SPIN(busySpin()),
YIELDING(yielding()),
PARKING(parking()),
PHASED(phasedOffLiteLock(200, 100, MILLISECONDS));
private reactor.util.concurrent.WaitStrategy reactorWaitStrategy;
WaitStrategy(reactor.util.concurrent.WaitStrategy reactorWaitStrategy) {
this.reactorWaitStrategy = reactorWaitStrategy;
}
reactor.util.concurrent.WaitStrategy getReactorWaitStrategy() {
return reactorWaitStrategy;
}
}
}
}