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

io.reacted.streams.ReactedSubmissionPublisher Maven / Gradle / Ivy

There is a newer version: 2.1.4
Show newest version
/*
 * Copyright (c) 2020 ,  [ [email protected] ]
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree.
 */

package io.reacted.streams;

import io.reacted.core.config.reactors.ReActorConfig;
import io.reacted.core.typedsubscriptions.TypedSubscription;
import io.reacted.core.drivers.DriverCtx;
import io.reacted.core.drivers.system.RemotingDriver;
import io.reacted.core.mailboxes.BackpressuringMbox;
import io.reacted.core.mailboxes.BasicMbox;
import io.reacted.core.messages.SerializationUtils;
import io.reacted.core.messages.reactors.ReActorInit;
import io.reacted.core.messages.reactors.ReActorStop;
import io.reacted.core.reactors.ReActions;
import io.reacted.core.reactorsystem.ReActorContext;
import io.reacted.core.reactorsystem.ReActorRef;
import io.reacted.core.reactorsystem.ReActorSystem;
import io.reacted.core.utils.ObjectUtils;
import io.reacted.patterns.NonNullByDefault;
import io.reacted.streams.messages.PublisherInterrupt;
import io.reacted.streams.messages.PublisherShutdown;
import io.reacted.streams.messages.PublisherComplete;
import io.reacted.streams.messages.SubscriptionReply;
import io.reacted.streams.messages.SubscriptionRequest;
import io.reacted.streams.messages.UnsubscriptionRequest;

import javax.annotation.Nullable;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.Serializable;
import java.time.Duration;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Flow;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import static io.reacted.core.utils.ReActedUtils.ifNotDelivered;

@NonNullByDefault
public class ReactedSubmissionPublisher implements Flow.Publisher,
                                                                                  AutoCloseable, Externalizable {
    public static final Duration RELIABLE_SUBSCRIPTION = BackpressuringMbox.RELIABLE_DELIVERY_TIMEOUT;
    public static final Duration BEST_EFFORT_SUBSCRIPTION = BackpressuringMbox.BEST_EFFORT_TIMEOUT;
    private static final long FEED_GATE_OFFSET = SerializationUtils.getFieldOffset(ReactedSubmissionPublisher.class,
                                                                                   "feedGate")
                                                                   .orElseSneakyThrow();
    private static final long LOCAL_REACTOR_SYSTEM =
            SerializationUtils.getFieldOffset(ReactedSubmissionPublisher.class, "localReActorSystem")
                                                                       .orElseSneakyThrow();
    private final transient Set subscribers;
    private final transient ReActorSystem localReActorSystem;
    private final ReActorRef feedGate;

    /**
     * Creates a location oblivious data publisher with backpressure. Subscribers can slowdown
     * the producer or just drop data if they are best effort subscribers. Publisher is Serializable,
     * so it can be sent to any reactor over any gate and subscribers can simply join the stream.
     *
     * @param localReActorSystem ReActorSystem used to manage and control the data flow
     * @param feedName           Name of this feed. Feed name must be unique and if deterministic it allows
     *                           cold replay
     */
    public ReactedSubmissionPublisher(ReActorSystem localReActorSystem, String feedName) {
        this.localReActorSystem = Objects.requireNonNull(localReActorSystem);
        this.subscribers = ConcurrentHashMap.newKeySet(10);
        var feedGateConfig = ReActorConfig.newBuilder()
                                       .setReActorName(ReactedSubmissionPublisher.class.getSimpleName() + "-" +
                                                       Objects.requireNonNull(feedName))
                                       .setMailBoxProvider(ctx -> new BasicMbox())
                                       .build();
        this.feedGate = localReActorSystem.spawn(ReActions.newBuilder()
                                                          .reAct(ReActorInit.class, ReActions::noReAction)
                                                          .reAct(PublisherShutdown.class,
                                                                 (raCtx, shutdown) -> raCtx.stop())
                                                          .reAct(PublisherInterrupt.class,
                                                                 this::onInterrupt)
                                                          .reAct(ReActorStop.class, this::onStop)
                                                          .reAct(SubscriptionRequest.class,
                                                                 this::onSubscriptionRequest)
                                                          .reAct(UnsubscriptionRequest.class,
                                                                 this::onUnSubscriptionRequest)
                                                          .build(), feedGateConfig)
                                          .orElseThrow(IllegalArgumentException::new);
    }

    public ReactedSubmissionPublisher() {
        /* Required by Externalizable */
        this.subscribers = Set.of();
        this.feedGate = ReActorRef.NO_REACTOR_REF;
        //noinspection ConstantConditions
        this.localReActorSystem = null; //TODO null reactor system object
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(feedGate);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        ReActorRef feedGate = new ReActorRef();
        feedGate.readExternal(in);
        setFeedGate(feedGate).setLocalReActorSystem(RemotingDriver.getDriverCtx()
                                                                  .map(DriverCtx::getLocalReActorSystem)
                                                                  .orElseThrow());
    }

    /**
     *  Stop the publisher. All the subscribers will be able to consume all the messages already sent
     */
    @Override
    public void close() { feedGate.tell(feedGate, new PublisherShutdown()); }

    /**
     *  Stop the publisher. All the subscribers will be notified immediately of the termination
     */
    public void interrupt() { feedGate.tell(feedGate, new PublisherInterrupt()); }

    /**
     + Registers a best effort subscriber. All the updates sent to this subscriber that cannot be
     * processed will be lost. This subscriber consumption speed will not affect the producer,
     * but delivery speed to the subscriber could.
     * For the non lost updates, strict message ordering is guaranteed to be the same of submission
     * NOTE: this overload generates NON REPLAYABLE subscriptions
     *
     * @param subscriber Java Flow compliant subscriber
     * @throws NullPointerException if subscriber is null
     */
    @Override
    public void subscribe(Flow.Subscriber subscriber) {
        subscribe(subscriber, UUID.randomUUID().toString());
    }

    /**
     * Registers a best effort subscriber. All the updates sent to this subscriber that cannot be
     * processed will be lost. This subscriber consumption speed will not affect the producer,
     * but delivery speed to the subscriber could.
     * For the non lost updates, strict message ordering is guaranteed to be the same of submission
     *
     * @param subscriber     Java Flow compliant subscriber
     * @param subscriberName This name must be unique and if deterministic it allows cold replay
     * @throws NullPointerException if any of the arguments is null
     * @return A {@link CompletionStage} that is going to be complete when the subscription is complete
     */
    public CompletionStage subscribe(Flow.Subscriber subscriber, String subscriberName) {
        return subscribe(ReActedSubscription.newBuilder()
                          .setSubscriber(subscriber)
                          .setBufferSize(Flow.defaultBufferSize())
                          .setBackpressureTimeout(BEST_EFFORT_SUBSCRIPTION)
                          .setSubscriberName(subscriberName)
                          .setAsyncBackpressurer(ForkJoinPool.commonPool())
                          .setSequencer(ReActedSubscription.NO_CUSTOM_SEQUENCER)
                          .build());
    }

    /**
     * Registers a best effort subscriber. All the updates sent to this subscriber that cannot be
     * processed will be lost. This subscriber consumption speed will not affect the producer,
     * but delivery speed to the subscriber could.
     * For the non lost updates, strict message ordering is guaranteed to be the same of submission
     *
     * @param subscriber Java Flow compliant subscriber
     * @param bufferSize How many elements can be buffered in the best effort subscriber. Positive values only
     * @throws IllegalArgumentException if {@code bufferSize} is not positive
     * @throws NullPointerException if any of the arguments is null
     * @return A {@link CompletionStage} that is going to be complete when the subscription is complete
     * NOTE: this overload generates NON REPLAYABLE subscriptions
     */
    public CompletionStage subscribe(Flow.Subscriber subscriber, int bufferSize) {
        return subscribe(ReActedSubscription.newBuilder()
                          .setSubscriber(subscriber)
                          .setBufferSize(bufferSize)
                          .setBackpressureTimeout(BEST_EFFORT_SUBSCRIPTION)
                          .setAsyncBackpressurer(ForkJoinPool.commonPool())
                          .setSubscriberName(UUID.randomUUID().toString())
                          .setSequencer(ReActedSubscription.NO_CUSTOM_SEQUENCER)
                          .build());
    }

    /**
     * Registers a best effort subscriber. All the updates sent to this subscriber that cannot be
     * processed will be lost. This subscriber consumption speed will not affect the producer,
     * but delivery speed to the subscriber could.
     * For the non lost updates, strict message ordering is guaranteed to be the same of submission
     *
     * @param subscriber     Java Flow compliant subscriber
     * @param bufferSize     How many elements can be buffered in the best effort subscriber. Positive values
     *                       only
     * @param subscriberName This name must be unique and if deterministic
     *                       it allows cold replay
     * @throws IllegalArgumentException if {@code bufferSize} is not positive
     * @throws NullPointerException if any of the arguments is null
     * @return A {@link CompletionStage} that is going to be complete when the subscription is complete
     */
    public CompletionStage subscribe(Flow.Subscriber subscriber, int bufferSize,
                                           String subscriberName) {
        return subscribe(ReActedSubscription.newBuilder()
                          .setSubscriber(subscriber)
                          .setBufferSize(bufferSize)
                          .setBackpressureTimeout(BEST_EFFORT_SUBSCRIPTION)
                          .setAsyncBackpressurer(ForkJoinPool.commonPool())
                          .setSubscriberName(subscriberName)
                          .setSequencer(ReActedSubscription.NO_CUSTOM_SEQUENCER)
                          .build());
    }

    /**
     + Registers a producer slowdown subscriber. Submissions towards this subscriber will be marked as completed only
     * when the message has been actually delivered or on error, allowing the producer to slow down the production rate.
     * Strict message ordering is guaranteed to be the same of submission
     *
     * @param subscriber Java Flow compliant subscriber
     * @param backpressureErrorTimeout the subscriber will try to deliver the message for at max this amount of time
     *                                 before signaling an error
     * @param asyncBackpressurer the executor to use for async delivery, supporting creation of at least one independent
     *                           thread
     * @throws IllegalArgumentException if duration is not bigger than zero
     * @throws NullPointerException if any of the arguments is null
     * @return A {@link CompletionStage} that is going to be complete when the subscription is complete
     * NOTE: this overload generates NON REPLAYABLE subscriptions
     */
    public CompletionStage subscribe(Flow.Subscriber subscriber, Executor asyncBackpressurer,
                                           Duration backpressureErrorTimeout) {
        return subscribe(ReActedSubscription.newBuilder()
                          .setSubscriber(subscriber)
                          .setBufferSize(Flow.defaultBufferSize())
                          .setBackpressureTimeout(ObjectUtils.requiredCondition(Objects.requireNonNull(backpressureErrorTimeout),
                                                                                Predicate.not(Duration::isZero),
                                                                                IllegalArgumentException::new))
                          .setAsyncBackpressurer(asyncBackpressurer)
                          .setSubscriberName(UUID.randomUUID().toString())
                          .setSequencer(ReActedSubscription.NO_CUSTOM_SEQUENCER)
                          .build());
    }

    /**
     + Registers a producer slowdown subscriber. Submissions towards this subscriber will be marked as completed only
     * when the message has been actually delivered or on error, allowing the producer to slow down the production rate.
     * Strict message ordering is guaranteed to be the same of submission
     *
     * @param subscriber Java Flow compliant subscriber
     * @param backpressureErrorTimeout the subscriber will try to deliver the message for at max this amount of time
     *                                 before signaling an error
     * @throws IllegalArgumentException if duration is not bigger than zero
     * @throws NullPointerException if any of the arguments is null
     * @return A {@link CompletionStage} that is going to be complete when the subscription is complete
     * NOTE: this overload generates NON REPLAYABLE subscriptions
     */
    public CompletionStage subscribe(Flow.Subscriber subscriber,
                                           Duration backpressureErrorTimeout) {
        return subscribe(ReActedSubscription.newBuilder()
                          .setSubscriber(subscriber)
                          .setBufferSize(Flow.defaultBufferSize())
                          .setBackpressureTimeout(ObjectUtils.requiredCondition(Objects.requireNonNull(backpressureErrorTimeout),
                                                                                Predicate.not(Duration::isZero),
                                                                                IllegalArgumentException::new))
                          .setAsyncBackpressurer(ForkJoinPool.commonPool())
                          .setSubscriberName(UUID.randomUUID().toString())
                          .setSequencer(ReActedSubscription.NO_CUSTOM_SEQUENCER)
                          .build());
    }

    /**
     + Registers a producer slowdown subscriber. Submissions towards this subscriber will be marked as completed only
     * when the message has been actually delivered or on error, allowing the producer to slow down the production rate.
     * Strict message ordering is guaranteed to be the same of submission
     *
     * @param subscriber     Java Flow compliant subscriber
     * @param backpressureErrorTimeout the subscriber will try to deliver the message for at max this amount of time
     *                                 before signaling an error
     * @param subscriberName This name must be unique and if deterministic it allows cold replay
     * @throws IllegalArgumentException if duration is not bigger than zero
     * @throws NullPointerException if any of the arguments is null
     * @return A {@link CompletionStage} that is going to be complete when the subscription is complete
     */
    public CompletionStage subscribe(Flow.Subscriber subscriber,
                                           Duration backpressureErrorTimeout, String subscriberName) {
        return subscribe(ReActedSubscription.newBuilder()
                          .setSubscriber(subscriber)
                          .setBufferSize(Flow.defaultBufferSize())
                          .setBackpressureTimeout(ObjectUtils.requiredCondition(Objects.requireNonNull(backpressureErrorTimeout),
                                                                                Predicate.not(Duration::isZero),
                                                                                IllegalArgumentException::new))
                          .setAsyncBackpressurer(ForkJoinPool.commonPool())
                          .setSubscriberName(subscriberName)
                          .setSequencer(ReActedSubscription.NO_CUSTOM_SEQUENCER)
                          .build());
    }

    /**
     + Registers a producer slowdown subscriber. Submissions towards this subscriber will be marked as completed only
     * when the message has been actually delivered or on error, allowing the producer to slow down the production rate.
     * Strict message ordering is guaranteed to be the same of submission
     *
     * @param subscriber     Java Flow compliant subscriber
     * @param backpressureErrorTimeout the subscriber will try to deliver the message for at max this amount of time
     *                                 before signaling an error
     * @param asyncBackpressurer the executor to use for async delivery, supporting creation of at least one independent
     *                           thread
     * @param subscriberName This name must be unique and if deterministic it allows cold replay
     * @throws IllegalArgumentException if duration is not bigger than zero
     * @throws NullPointerException if any of the arguments is null
     * @return A {@link CompletionStage} that is going to be complete when the subscription is complete
     */
    public CompletionStage subscribe(Flow.Subscriber subscriber,
                                           Duration backpressureErrorTimeout, Executor asyncBackpressurer,
                                           String subscriberName) {
        return subscribe(ReActedSubscription.newBuilder()
                          .setSubscriber(subscriber)
                          .setBufferSize(Flow.defaultBufferSize())
                          .setBackpressureTimeout(ObjectUtils.requiredCondition(Objects.requireNonNull(backpressureErrorTimeout),
                                                                                Predicate.not(Duration::isZero),
                                                                                IllegalArgumentException::new))
                          .setAsyncBackpressurer(asyncBackpressurer)
                          .setSubscriberName(subscriberName)
                          .setSequencer(ReActedSubscription.NO_CUSTOM_SEQUENCER)
                          .build());
    }

    /**
     + Registers a producer slowdown subscriber. Submissions towards this subscriber will be marked as completed only
     * when the message has been actually delivered or on error, allowing the producer to slow down the production rate.
     * Strict message ordering is guaranteed to be the same of submission
     *
     * @param subscriber Java Flow compliant subscriber
     * @param bufferSize How many elements can be buffered in the best
     *                   effort subscriber
     * @param backpressureErrorTimeout the subscriber will try to deliver the message for at max this amount of time
     *                                 before signaling an error
     * @throws IllegalArgumentException if duration is not bigger than zero
     * @throws IllegalArgumentException if {@code bufferSize} is not positive
     * @throws NullPointerException if any of the arguments is null
     * @return A {@link CompletionStage} that is going to be complete when the subscription is complete
     * NOTE: this overload generates NON REPLAYABLE subscriptions
     */
    public CompletionStage subscribe(Flow.Subscriber subscriber, int bufferSize,
                                           Duration backpressureErrorTimeout) {
        return subscribe(ReActedSubscription.newBuilder()
                          .setSubscriber(subscriber)
                          .setBufferSize(bufferSize)
                          .setBackpressureTimeout(ObjectUtils.requiredCondition(Objects.requireNonNull(backpressureErrorTimeout),
                                                                                Predicate.not(Duration::isZero),
                                                                                IllegalArgumentException::new))
                          .setAsyncBackpressurer(ForkJoinPool.commonPool())
                          .setSubscriberName(UUID.randomUUID().toString())
                          .setSequencer(ReActedSubscription.NO_CUSTOM_SEQUENCER)
                          .build());
    }

    /**
     + Registers a producer slowdown subscriber. Submissions towards this subscriber will be marked as completed only
     * when the message has been actually delivered or on error, allowing the producer to slow down the production rate.
     * Strict message ordering is guaranteed to be the same of submission
     *
     * @param subscriber Java {@link Flow} compliant subscriber
     * @param bufferSize How many elements can be buffered in the best effort subscriber. Positive values only
     * @param asyncBackpressurer the executor to use for async delivery, supporting creation of at least one independent
     *                           thread
     * @param backpressureErrorTimeout the subscriber will try to deliver the message for at max this amount of time
     *                                 before signaling an error
     * @throws IllegalArgumentException if duration is not bigger than zero
     * @throws IllegalArgumentException if {@code bufferSize} is not positive
     * @throws NullPointerException if any of the arguments is null
     * @return A {@link CompletionStage} that is going to be complete when the subscription is complete
     * NOTE: this overload generates NON REPLAYABLE subscriptions
     */
    public CompletionStage subscribe(Flow.Subscriber subscriber, int bufferSize,
                                           Executor asyncBackpressurer, Duration backpressureErrorTimeout) {
        return subscribe(ReActedSubscription.newBuilder()
                          .setSubscriber(subscriber)
                          .setBufferSize(bufferSize)
                          .setBackpressureTimeout(ObjectUtils.requiredCondition(Objects.requireNonNull(backpressureErrorTimeout),
                                                                                Predicate.not(Duration::isZero),
                                                                                IllegalArgumentException::new))
                          .setAsyncBackpressurer(asyncBackpressurer)
                          .setSubscriberName(UUID.randomUUID().toString())
                          .setSequencer(ReActedSubscription.NO_CUSTOM_SEQUENCER)
                          .build());
    }

    /**
     * Register a generic subscriber to the stream.
     * Strict message ordering is guaranteed to be the same of submission
     *
     * @param subscription A {@link ReActedSubscription}
     * SneakyThrows any exception raised
     * @return A {@link CompletionStage} that is going to be complete when the subscription is complete
     */
    public CompletionStage subscribe(ReActedSubscription subscription) {
        CompletionStage subscriptionComplete = new CompletableFuture<>();
        var backpressureManager = new BackpressureManager<>(subscription, feedGate, subscriptionComplete);

        var subscriberConfig = ReActorConfig.newBuilder()
                                         .setReActorName(feedGate.getReActorId().getReActorName() +
                                                         "_subscriber_" + subscription.getSubscriberName() + "_" +
                                                         feedGate.getReActorId().getReActorUUID().toString())
                                         .setDispatcherName(ReActorSystem.DEFAULT_DISPATCHER_NAME)
                                         .setTypedSubscriptions(TypedSubscription.NO_SUBSCRIPTIONS)
                                         .setMailBoxProvider(backpressureManager.getManagerMailbox())
                                         .build();

        localReActorSystem.spawnChild(backpressureManager.getReActions(), localReActorSystem.getUserReActorsRoot(),
                                      subscriberConfig)
                          .orElseSneakyThrow();
        return subscriptionComplete;
    }

    /**
     * Submit a new message to the feed. The delivery order among the subscribers is not predictable
     * or consistent. If producer wants to regulate the production rate according to the consumers
     * speed (flow regulation backpressuring) the next message has to be sent when the previous one
     * has been delivered. Strict message ordering is guaranteed to be the same of submission.
     * This call is never blocking.
     *
     * @param message the message that should be propagated to the subscribers
     * @return a CompletionsStage that will be marked ad complete when the message has been
     * delivered to all the subscribers
     */
    public CompletionStage backpressurableSubmit(PayloadT message) {
        var deliveries = subscribers.stream()
                                    .map(subscribed -> subscribed.atell(subscribed, message))
                                    .collect(Collectors.toUnmodifiableList());
        return deliveries.stream()
                         .reduce((first, second) -> first.thenCompose(delivery -> second))
                         .map(lastDelivery -> lastDelivery.thenAccept(lastRetVal -> {}))
                         .orElse(CompletableFuture.completedFuture(null));
    }

    public void submit(PayloadT message) {
        subscribers.forEach(subscribed -> subscribed.tell(subscribed, message));
    }

    private void onInterrupt(ReActorContext raCtx, PublisherInterrupt interrupt) {
        subscribers.forEach(subscriber -> subscriber.tell(raCtx.getSelf(), interrupt));
        subscribers.clear();
        raCtx.stop();
    }

    private void onStop(ReActorContext raCtx, ReActorStop stop) {
        subscribers.forEach(subscriber -> subscriber.tell(raCtx.getSelf(), new PublisherComplete()));
        subscribers.clear();
    }

    private void onSubscriptionRequest(ReActorContext raCtx, SubscriptionRequest subscription) {
        var backpressuringManager = subscription.getSubscriptionBackpressuringManager();
        ifNotDelivered(backpressuringManager.atell(raCtx.getSelf(),
                                                   new SubscriptionReply(subscribers.add(backpressuringManager))),
                    error -> raCtx.logError("Unable to deliver subscription confirmation to {}",
                                            subscription.getSubscriptionBackpressuringManager(), error));
    }

    private void onUnSubscriptionRequest(ReActorContext raCtx, UnsubscriptionRequest unsubscriptionRequest) {
        subscribers.remove(unsubscriptionRequest.getSubscriptionBackpressuringManager());
    }

    private ReactedSubmissionPublisher setFeedGate(ReActorRef feedGate) {
        return SerializationUtils.setObjectField(this, ReactedSubmissionPublisher.FEED_GATE_OFFSET, feedGate);
    }

    @SuppressWarnings("UnusedReturnValue")
    private ReactedSubmissionPublisher setLocalReActorSystem(ReActorSystem localReActorSystem) {
        return SerializationUtils.setObjectField(this, ReactedSubmissionPublisher.LOCAL_REACTOR_SYSTEM,
                                                 localReActorSystem);
    }

    public final static class ReActedSubscription {
        @Nullable
        public static final ThreadPoolExecutor NO_CUSTOM_SEQUENCER = null;
        private final Flow.Subscriber subscriber;
        private final int bufferSize;
        private final Duration backpressureTimeout;
        private final Executor asyncBackpressurer;
        private final String subscriberName;
        @Nullable
        private final ThreadPoolExecutor sequencer;

        private ReActedSubscription(Builder builder) {
            this.subscriber = Objects.requireNonNull(builder.subscriber);
            this.bufferSize = ObjectUtils.requiredInRange(builder.bufferSize, 1, Integer.MAX_VALUE,
                                                          IllegalArgumentException::new);
            this.backpressureTimeout = ObjectUtils.requiredCondition(Objects.requireNonNull(builder.backpressureTimeout),
                                                                     timeout -> timeout.compareTo(RELIABLE_SUBSCRIPTION) <= 0 &&
                                                                                !timeout.isNegative(),
                                                                     IllegalArgumentException::new);
            this.asyncBackpressurer = Objects.requireNonNull(builder.asyncBackpressurer);
            this.subscriberName = Objects.requireNonNull(builder.subscriberName);
            this.sequencer = builder.sequencer != null
                             ? ObjectUtils.requiredCondition(builder.sequencer,
                                                             sequencer -> sequencer.getMaximumPoolSize() == 1,
                                                             IllegalArgumentException::new)
                             : null;
        }

        public Flow.Subscriber getSubscriber() { return subscriber; }

        public int getBufferSize() { return bufferSize; }

        public Duration getBackpressureTimeout() { return backpressureTimeout; }

        public Executor getAsyncBackpressurer() { return asyncBackpressurer; }

        public String getSubscriberName() { return subscriberName; }

        @Nullable
        public ThreadPoolExecutor getSequencer() { return sequencer; }

        public static  Builder newBuilder() { return new Builder<>(); }

        @SuppressWarnings("NotNullFieldNotInitialized")
        public static final class Builder {
            private Flow.Subscriber subscriber;
            private int bufferSize;
            private Duration backpressureTimeout;
            private Executor asyncBackpressurer;
            private String subscriberName;
            @Nullable
            private ThreadPoolExecutor sequencer = NO_CUSTOM_SEQUENCER;

            private Builder() { }

            /**
             *
             * @param subscriber Java {@link Flow} compliant subscriber
             * @return this builder
             */
            public Builder setSubscriber(Flow.Subscriber subscriber) {
                this.subscriber = subscriber;
                return this;
            }

            /**
             *
             * @param bufferSize Producer buffer size. Updates messages exceeding this buffer size will cause
             *                   a drop or a wait signal for the producer, according to the subscription type.
             *                   Positive values only
             * @return this builder
             */
            public Builder setBufferSize(int bufferSize) {
                this.bufferSize = bufferSize;
                return this;
            }

            /**
             *
             * @param backpressureTimeout At most this timeout will be waited while attempting a delivery. Once this
             *                            timeout is expired, the message is dropped.
             *                            {@link ReactedSubmissionPublisher#BEST_EFFORT_SUBSCRIPTION} for best effort subscriptions.
             *                            If the submission buffer is full, the new messages will be discarder
             *                            {@link ReactedSubmissionPublisher#RELIABLE_SUBSCRIPTION} for subscriptions where
             *                            no message can be lost. Publisher will wait indefinitely.
             * @return this builder
             */
            public Builder setBackpressureTimeout(Duration backpressureTimeout) {
                this.backpressureTimeout = backpressureTimeout;
                return this;
            }

            /**
             *
             * @param asyncBackpressurer the executor to use for async delivery, supporting creation of at least one
             *                           independent thread
             * @return this builder
             */
            public Builder setAsyncBackpressurer(Executor asyncBackpressurer) {
                this.asyncBackpressurer = asyncBackpressurer;
                return this;
            }

            /**
             *
             * @param subscriberName This name must be unique and if deterministic it allows replay
             * @return this builder
             */
            public Builder setSubscriberName(String subscriberName) {
                this.subscriberName = subscriberName;
                return this;
            }

            /**
             *
             * @param sequencer An optional *single* thread for asynchronously attempting the submission tasks. If not specified
             *                  a new thread will be automatically created for this
             * @return this builder
             */
            public Builder setSequencer(@Nullable ThreadPoolExecutor sequencer) {
                this.sequencer = sequencer;
                return this;
            }

            /**
             *
             * @return a {@link ReActedSubscription}
             * @throws NullPointerException if any of the parameters is null
             * @throws IllegalArgumentException if any of the supplied values does not comply to the specified
             *                                  requirements
             */
            public ReActedSubscription build() {
                return new ReActedSubscription<>(this);
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy