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

io.github.tramchamploo.bufferslayer.RxReporter Maven / Gradle / Ivy

The newest version!
package io.github.tramchamploo.bufferslayer;

import static com.google.common.base.Preconditions.checkNotNull;
import static io.github.tramchamploo.bufferslayer.MessageDroppedException.dropped;
import static java.util.Collections.singletonList;

import io.github.tramchamploo.bufferslayer.Message.MessageKey;
import io.github.tramchamploo.bufferslayer.OverflowStrategy.Strategy;
import io.github.tramchamploo.bufferslayer.internal.Future;
import io.github.tramchamploo.bufferslayer.internal.FutureListener;
import io.github.tramchamploo.bufferslayer.internal.MessageFuture;
import io.github.tramchamploo.bufferslayer.internal.MessagePromise;
import io.reactivex.BackpressureOverflowStrategy;
import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
import io.reactivex.FlowableEmitter;
import io.reactivex.FlowableOnSubscribe;
import io.reactivex.Scheduler;
import io.reactivex.flowables.GroupedFlowable;
import io.reactivex.functions.Action;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Function;
import io.reactivex.schedulers.Schedulers;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RxReporter implements Reporter, FlowableOnSubscribe> {

  static final Logger logger = LoggerFactory.getLogger(RxReporter.class);

  final long messageTimeoutNanos;
  final int bufferedMaxMessages;
  final int pendingMaxMessages;
  final Strategy overflowStrategy;
  final Sender sender;
  final ReporterMetrics metrics;
  final AtomicBoolean closed = new AtomicBoolean(false);

  private FlowableEmitter> emitter;
  private Scheduler scheduler;

  private RxReporter(Builder builder) {
    this.sender = builder.sender;
    this.metrics = builder.metrics;

    this.messageTimeoutNanos = builder.messageTimeoutNanos;
    this.bufferedMaxMessages = builder.bufferedMaxMessages;
    this.pendingMaxMessages = builder.pendingMaxMessages;
    this.overflowStrategy = builder.overflowStrategy;
    this.scheduler = builder.scheduler;

    Flowable> flowable = Flowable.create(this, BackpressureStrategy.MISSING);
    initBackpressurePolicy(flowable)
        .observeOn(Schedulers.single())
        .groupBy(new MessagePartitioner())
        .subscribe(new MessageGroupSubscriber(messageTimeoutNanos, bufferedMaxMessages, sender, scheduler));
  }

  public static  Builder builder(Sender sender) {
    return new Builder<>(sender);
  }

  private Flowable> initBackpressurePolicy(Flowable> flowable) {
    Strategy strategy = this.overflowStrategy;
    if (strategy == Strategy.DropNew) {
      return flowable.onBackpressureDrop(new Consumer>() {
        @Override
        public void accept(MessagePromise promise) {
          metricsCallback(1);
        }
      });
    } else {
      BackpressureOverflowStrategy rxStrategy = RxOverflowStrategyBridge.toRxStrategy(strategy);
      return flowable.onBackpressureBuffer(pendingMaxMessages, new Action() {
        @Override
        public void run() {
          metricsCallback(1);
        }
      }, rxStrategy);
    }
  }

  private void metricsCallback(int quantity) {
    metrics.incrementMessagesDropped(quantity);
  }

  public static final class Builder extends Reporter.Builder {

    Scheduler scheduler = Schedulers.io();

    Builder(Sender sender) {
      super(sender);
    }

    @Override
    public Builder metrics(ReporterMetrics metrics) {
      super.metrics(metrics);
      return this;
    }

    @Override
    public Builder messageTimeout(long timeout, TimeUnit unit) {
      super.messageTimeout(timeout, unit);
      return this;
    }

    @Override
    public Builder bufferedMaxMessages(int bufferedMaxMessages) {
      super.bufferedMaxMessages(bufferedMaxMessages);
      return this;
    }

    @Override
    public Builder pendingMaxMessages(int pendingMaxMessages) {
      super.pendingMaxMessages(pendingMaxMessages);
      return this;
    }

    @Override
    public Builder overflowStrategy(Strategy overflowStrategy) {
      super.overflowStrategy(overflowStrategy);
      return this;
    }

    public Builder scheduler(Scheduler scheduler) {
      this.scheduler = checkNotNull(scheduler, "scheduler");
      return this;
    }

    @Override
    public RxReporter build() {
      return new RxReporter<>(this);
    }
  }

  private class MessageGroupSubscriber implements Consumer>> {

    final long messageTimeoutNanos;
    final int bufferedMaxMessages;
    final Sender sender;
    final Scheduler scheduler;

    private MessageGroupSubscriber(long messageTimeoutNanos,
                                   int bufferedMaxMessages,
                                   Sender sender,
                                   Scheduler scheduler) {
      this.messageTimeoutNanos = messageTimeoutNanos == 0 ? Long.MAX_VALUE : messageTimeoutNanos;
      this.bufferedMaxMessages = bufferedMaxMessages;
      this.sender = sender;
      this.scheduler = scheduler;
    }

    @Override
    public void accept(GroupedFlowable> group) {
      Flowable>> bufferedMessages =
          group.buffer(messageTimeoutNanos, TimeUnit.NANOSECONDS, scheduler, bufferedMaxMessages);
      bufferedMessages.subscribeOn(scheduler).subscribe(SenderConsumerBridge.toConsumer(sender));
    }
  }

  private class MessagePartitioner implements Function, MessageKey> {

    @Override
    public MessageKey apply(MessagePromise promise) {
      return promise.message().asMessageKey();
    }
  }

  @Override
  public CheckResult check() {
    return sender.check();
  }

  @Override
  @SuppressWarnings("unchecked")
  public MessageFuture report(M message) {
    checkNotNull(message, "message");
    metrics.incrementMessages(1);

    if (closed.get()) { // Drop the message when closed.
      MessageDroppedException cause =
          dropped(new IllegalStateException("closed!"), singletonList(message));
      MessageFuture future = (MessageFuture) message.newFailedFuture(cause);
      metricsCallback(1);
      return future;
    }

    MessagePromise promise = message.newPromise();
    emitter.onNext(promise);
    setLoggingListener(promise);
    return promise;
  }

  private void setLoggingListener(MessageFuture future) {
    future.addListener(new FutureListener() {

      @Override
      public void operationComplete(Future future) {
        if (!future.isSuccess()) {
          MessageDroppedException cause = (MessageDroppedException) future.cause();
          metricsCallback(cause.dropped.size());
          logger.warn(cause.getMessage());
        }
      }
    });
  }

  @Override
  public void subscribe(FlowableEmitter> emitter) throws Exception {
    this.emitter = emitter;
  }

  @Override
  public void close() {
    if (!closed.compareAndSet(false, true)) return;
    emitter.onComplete();
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy