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

io.reacted.streams.BackpressureManager 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.mailboxes.BackpressuringMbox;
import io.reacted.core.mailboxes.BoundedBasicMbox;
import io.reacted.core.mailboxes.MailBox;
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.patterns.NonNullByDefault;
import io.reacted.patterns.Try;
import io.reacted.streams.messages.PublisherComplete;
import io.reacted.streams.messages.PublisherInterrupt;
import io.reacted.streams.messages.SubscriberError;
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.Serializable;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.Flow;
import java.util.function.Function;

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

@NonNullByDefault
public class BackpressureManager implements Flow.Subscription, AutoCloseable {
    private final Flow.Subscriber subscriber;
    private final ReActorRef feedGate;
    private final BackpressuringMbox.Builder bpMailboxBuilder;
    private final CompletionStage onSubscriptionCompleteTrigger;
    @Nullable
    private MailBox backpressuringMbox;
    @Nullable
    private volatile ReActorContext backpressurerCtx;

    /**
     * It manages a backpressured reactive streams. A reactive stream can accept local (to the reactor system) or
     * remote subscribers
     *
     * @param subscription A {@link io.reacted.streams.ReactedSubmissionPublisher.ReActedSubscription} containing the details of the subscriber
     * @param feedGate source of data for the managed stream
     */
    BackpressureManager(ReactedSubmissionPublisher.ReActedSubscription subscription,
                        ReActorRef feedGate, CompletionStage onSubscriptionCompleteTrigger) {
        this.onSubscriptionCompleteTrigger = onSubscriptionCompleteTrigger;
        this.subscriber = subscription.getSubscriber();
        this.feedGate = Objects.requireNonNull(feedGate);
        this.bpMailboxBuilder = BackpressuringMbox.newBuilder()
                                                  .setRealMbox(new BoundedBasicMbox(subscription.getBufferSize()))
                                                  .setBackpressureTimeout(subscription.getBackpressureTimeout())
                                                  .setBufferSize(subscription.getBufferSize())
                                                  .setRequestOnStartup(0)
                                                  .setAsyncBackpressurer(subscription.getAsyncBackpressurer())
                                                  .setNonDelayable(Set.of(ReActorInit.class, ReActorStop.class,
                                                                          SubscriptionRequest.class,
                                                                          SubscriptionReply.class,
                                                                          UnsubscriptionRequest.class,
                                                                          SubscriberError.class,
                                                                          PublisherInterrupt.class))
                                                  .setNonBackpressurable(Set.of(PublisherComplete.class))
                                                  .setSequencer(subscription.getSequencer());
    }

    @Override
    public void request(long elements) {
        if (elements <= 0) {
            errorTermination(Objects.requireNonNull(backpressurerCtx),
                             new IllegalArgumentException("non-positive subscription request"), subscriber);
        } else {
            if (backpressurerCtx != null && backpressuringMbox != null) {
                backpressuringMbox.request(elements);
            }
        }
    }

    @Override
    public void cancel() { close(); }

    @Override
    public void close() {
        if (backpressurerCtx != null) {
            Objects.requireNonNull(backpressurerCtx).stop();
        }
    }

    Function getManagerMailbox() {
        return mboxOwner -> bpMailboxBuilder.setRealMailboxOwner(mboxOwner).build();
    }

    ReActions getReActions() {
        return ReActions.newBuilder()
                        .reAct(ReActorInit.class, this::onInit)
                        .reAct(ReActorStop.class, this::onStop)
                        .reAct(SubscriptionReply.class, this::onSubscriptionReply)
                        .reAct(SubscriberError.class, this::onSubscriberError)
                        .reAct(PublisherComplete.class, this::onPublisherComplete)
                        .reAct(PublisherInterrupt.class, this::onPublisherInterrupt)
                        .reAct(this::forwarder)
                        .build();
    }

    private void onStop(ReActorContext raCtx, ReActorStop stop) {
        feedGate.tell(ReActorRef.NO_REACTOR_REF, new UnsubscriptionRequest(raCtx.getSelf()));
    }

    private void forwarder(ReActorContext raCtx, Object anyPayload) {
        try {
            //noinspection unchecked
            subscriber.onNext((PayloadT) anyPayload);
        } catch (Exception anyException) {
           errorTermination(raCtx, anyException, subscriber);
        }
    }

    private void onSubscriptionReply(ReActorContext raCtx, SubscriptionReply payload) {
        onSubscriptionCompleteTrigger.toCompletableFuture().complete(null);
        if (payload.isSuccess()) {
            Try.ofRunnable(() -> subscriber.onSubscribe(this))
               .ifError(error -> errorTermination(raCtx, error, subscriber));
        } else {
            errorTermination(raCtx, new RuntimeException("RemoteRegistrationException"), subscriber);
        }
    }

    private void onSubscriberError(ReActorContext raCtx, SubscriberError error) {
        errorTermination(raCtx, error.getError(), subscriber);
    }

    private void onPublisherComplete(ReActorContext raCtx, PublisherComplete publisherComplete) {
        completeTermination(raCtx, subscriber);
    }

    private void onPublisherInterrupt(ReActorContext raCtx, PublisherInterrupt interrupt) {
        completeTermination(raCtx, subscriber);
    }

    private void onInit(ReActorContext raCtx, ReActorInit init) {
        this.backpressurerCtx = raCtx;
        this.backpressuringMbox = raCtx.getMbox();

        Try.TryConsumer onSubscriptionError = error -> { subscriber.onSubscribe(this);
                                                                    errorTermination(raCtx, error, subscriber); };
        ifNotDelivered(feedGate.tell(raCtx.getSelf(), new SubscriptionRequest(raCtx.getSelf())),
                       onSubscriptionError);
    }

    private void completeTermination(ReActorContext raCtx, Flow.Subscriber localSubscriber) {
        close();
        Try.ofRunnable(localSubscriber::onComplete)
           .ifError(error -> raCtx.logError("Error in {} onComplete: ",
                                            localSubscriber.getClass().getSimpleName(), error));
    }

    private void errorTermination(ReActorContext raCtx, Throwable handlingError,
                                  Flow.Subscriber localSubscriber) {
        close();
        Try.ofRunnable(() -> localSubscriber.onError(handlingError))
           .ifError(error -> raCtx.logError("Error in {} onError: ", localSubscriber.getClass().getSimpleName(), error));
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy