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

org.glassfish.jersey.internal.util.JerseyPublisher Maven / Gradle / Ivy

There is a newer version: 4.0.0-M1
Show newest version
/*
 * Copyright (c) 2017, 2020 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package org.glassfish.jersey.internal.util;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.BiPredicate;
import java.util.function.Consumer;

import org.glassfish.jersey.internal.LocalizationMessages;
import org.glassfish.jersey.internal.jsr166.Flow;
import org.glassfish.jersey.internal.jsr166.SubmissionPublisher;
import org.glassfish.jersey.internal.jsr166.SubmissionPublisherFactory;
import org.glassfish.jersey.internal.jsr166.SubmittableFlowPublisher;


/**
 * Implementation of {@link Flow.Publisher} corresponding to reactive streams specification.
 * 

* Delegates to {@link SubmissionPublisher} repackaged from jsr166 on JDK 8 or to JDK {@code SubmissionPublisher}. * * @author Adam Lindenthal */ public class JerseyPublisher implements Flow.Publisher { private static final int DEFAULT_BUFFER_CAPACITY = 256; private SubmittableFlowPublisher submissionPublisher = SubmissionPublisherFactory.createSubmissionPublisher(); private final PublisherStrategy strategy; private boolean cascadingClose; /** * Creates a new JerseyPublisher using the {@link ForkJoinPool#commonPool()} for async delivery to subscribers * (unless it does not support a parallelism level of at least two, in which case, a new Thread is created to run * each task), with maximum buffer capacity of {@value DEFAULT_BUFFER_CAPACITY} and default {@link PublisherStrategy}, * which is {@link PublisherStrategy#BEST_EFFORT}. */ public JerseyPublisher() { this(ForkJoinPool.commonPool(), DEFAULT_BUFFER_CAPACITY, PublisherStrategy.BEST_EFFORT); } /** * Creates a new JerseyPublisher using the {@link ForkJoinPool#commonPool()} for async delivery to subscribers * (unless it does not support a parallelism level of at least two, in which case, a new Thread is created to run * each task), with maximum buffer capacity of {@value DEFAULT_BUFFER_CAPACITY} and given {@link PublisherStrategy}. * * @param strategy publisher delivering strategy */ public JerseyPublisher(final PublisherStrategy strategy) { this(ForkJoinPool.commonPool(), DEFAULT_BUFFER_CAPACITY, strategy); } /** * Creates a new JerseyPublisher using the given {@link Executor} for async delivery to subscribers, with the default * maximum buffer size of {@value DEFAULT_BUFFER_CAPACITY} and default {@link PublisherStrategy}, which is * {@link PublisherStrategy#BEST_EFFORT}. * * @param executor {@code Executor} the executor to use for async delivery, * supporting creation of at least one independent thread * @throws NullPointerException if executor is null * @throws IllegalArgumentException if maxBufferCapacity not positive */ public JerseyPublisher(final Executor executor) { this(executor, PublisherStrategy.BEST_EFFORT); } /** * Creates a new JerseyPublisher using the given {@link Executor} for async delivery to subscribers, with the default * maximum buffer size of {@value DEFAULT_BUFFER_CAPACITY} and given {@link PublisherStrategy}. * * @param executor {@code Executor} the executor to use for async delivery, * supporting creation of at least one independent thread * @param strategy publisher delivering strategy * @throws NullPointerException if executor is null * @throws IllegalArgumentException if maxBufferCapacity not positive */ public JerseyPublisher(final Executor executor, final PublisherStrategy strategy) { this.strategy = strategy; submissionPublisher = SubmissionPublisherFactory.createSubmissionPublisher(executor, DEFAULT_BUFFER_CAPACITY); } /** * Creates a new JerseyPublisher using the {@link ForkJoinPool#commonPool()} for async delivery to subscribers * (unless it does not support a parallelism level of at least two, in which case, a new Thread is created to run * each task), with specified maximum buffer capacity and default {@link PublisherStrategy}, which is * {@link PublisherStrategy#BEST_EFFORT}. * * @param maxBufferCapacity the maximum capacity for each * subscriber's buffer (the enforced capacity may be rounded up to * the nearest power of two and/or bounded by the largest value * supported by this implementation; method {@link #getMaxBufferCapacity} * returns the actual value) */ public JerseyPublisher(final int maxBufferCapacity) { this(ForkJoinPool.commonPool(), maxBufferCapacity, PublisherStrategy.BEST_EFFORT); } /** * Creates a new JerseyPublisher using the given {@link Executor} for async delivery to subscribers, with the given * maximum buffer size for each subscriber and given {@link PublisherStrategy}. * * @param executor {@code Executor} the executor to use for async delivery, * supporting creation of at least one independent thread * @param maxBufferCapacity the maximum capacity for each * subscriber's buffer (the enforced capacity may be rounded up to * the nearest power of two and/or bounded by the largest value * supported by this implementation; method {@link #getMaxBufferCapacity} * returns the actual value) * @param strategy publisher delivering strategy * @throws NullPointerException if executor is null * @throws IllegalArgumentException if maxBufferCapacity not positive */ public JerseyPublisher(final Executor executor, final int maxBufferCapacity, PublisherStrategy strategy) { this.strategy = strategy; submissionPublisher = SubmissionPublisherFactory.createSubmissionPublisher(executor, maxBufferCapacity); } @Override public void subscribe(final Flow.Subscriber subscriber) { submissionPublisher.subscribe(new SubscriberWrapper(subscriber)); } /** * Publishes the given item to each current subscriber by asynchronously invoking its onNext method. *

* Blocks uninterruptibly while resources for any subscriber are unavailable. * * @param data published data * @return the estimated maximum lag among subscribers * @throws IllegalStateException if closed * @throws NullPointerException if data is null * @throws java.util.concurrent.RejectedExecutionException if thrown by Executor */ private int submit(final T data) { return submissionPublisher.submit(data); } /** * Processes all published items using the given Consumer function. Returns a CompletableFuture that is completed * normally when this publisher signals {@code onComplete()}, or completed exceptionally upon any error, or an * exception is thrown by the Consumer, or the returned CompletableFuture is cancelled, in which case no further * items are processed. * * @param consumer function to process all published data * @return a {@link CompletableFuture} that is completed normally when the publisher signals onComplete, * and exceptionally upon any error or cancellation * @throws NullPointerException if consumer is null */ public CompletableFuture consume(final Consumer consumer) { return submissionPublisher.consume(consumer); } /** * Publishes the given item, if possible, to each current subscriber * by asynchronously invoking its * {@link Flow.Subscriber#onNext(Object) onNext} method. * The item may be dropped by one or more subscribers if resource * limits are exceeded, in which case the given handler (if non-null) * is invoked, and if it returns true, retried once. Other calls to * methods in this class by other threads are blocked while the * handler is invoked. Unless recovery is assured, options are * usually limited to logging the error and/or issuing an {@link * Flow.Subscriber#onError(Throwable) onError} * signal to the subscriber. *

* This method returns a status indicator: If negative, it * represents the (negative) number of drops (failed attempts to * issue the item to a subscriber). Otherwise it is an estimate of * the maximum lag (number of items submitted but not yet * consumed) among all current subscribers. This value is at least * one (accounting for this submitted item) if there are any * subscribers, else zero. *

* If the Executor for this publisher throws a * RejectedExecutionException (or any other RuntimeException or * Error) when attempting to asynchronously notify subscribers, or * the drop handler throws an exception when processing a dropped * item, then this exception is rethrown. * * @param item the (non-null) item to publish * @param onDrop if non-null, the handler invoked upon a drop to a * subscriber, with arguments of the subscriber and item; if it * returns true, an offer is re-attempted (once) * @return if negative, the (negative) number of drops; otherwise * an estimate of maximum lag * @throws IllegalStateException if closed * @throws NullPointerException if item is null * @throws RejectedExecutionException if thrown by Executor */ private int offer(T item, BiPredicate, ? super T> onDrop) { return offer(item, 0, TimeUnit.MILLISECONDS, onDrop); } /** * Publishes the given item, if possible, to each current subscriber * by asynchronously invoking its {@link * Flow.Subscriber#onNext(Object) onNext} method, * blocking while resources for any subscription are unavailable, * up to the specified timeout or until the caller thread is * interrupted, at which point the given handler (if non-null) is * invoked, and if it returns true, retried once. (The drop handler * may distinguish timeouts from interrupts by checking whether * the current thread is interrupted.) * Other calls to methods in this class by other * threads are blocked while the handler is invoked. Unless * recovery is assured, options are usually limited to logging the * error and/or issuing an * {@link Flow.Subscriber#onError(Throwable) onError} * signal to the subscriber. *

* This method returns a status indicator: If negative, it * represents the (negative) number of drops (failed attempts to * issue the item to a subscriber). Otherwise it is an estimate of * the maximum lag (number of items submitted but not yet * consumed) among all current subscribers. This value is at least * one (accounting for this submitted item) if there are any * subscribers, else zero. *

* If the Executor for this publisher throws a * RejectedExecutionException (or any other RuntimeException or * Error) when attempting to asynchronously notify subscribers, or * the drop handler throws an exception when processing a dropped * item, then this exception is rethrown. * * @param item the (non-null) item to publish * @param timeout how long to wait for resources for any subscriber * before giving up, in units of {@code unit} * @param unit a {@code TimeUnit} determining how to interpret the * {@code timeout} parameter * @param onDrop if non-null, the handler invoked upon a drop to a * subscriber, with arguments of the subscriber and item; if it * returns true, an offer is re-attempted (once) * @return if negative, the (negative) number of drops; otherwise * an estimate of maximum lag * @throws IllegalStateException if closed * @throws NullPointerException if item is null * @throws RejectedExecutionException if thrown by Executor */ private int offer(T item, long timeout, TimeUnit unit, BiPredicate, ? super T> onDrop) { BiPredicate, ? super T> callback; callback = onDrop == null ? this::onDrop : (BiPredicate, T>) (subscriber, data) -> { onDrop.test(getSubscriberWrapper(subscriber).getWrappedSubscriber(), data); return false; }; return submissionPublisher.offer(item, timeout, unit, callback); } private boolean onDrop(Flow.Subscriber subscriber, T t) { subscriber.onError(new IllegalStateException(LocalizationMessages.SLOW_SUBSCRIBER(t))); getSubscriberWrapper(subscriber).getSubscription().cancel(); return false; } private SubscriberWrapper getSubscriberWrapper(Flow.Subscriber subscriber) { if (subscriber instanceof SubscriberWrapper) { return ((SubscriberWrapper) subscriber); } else { throw new IllegalArgumentException(LocalizationMessages.UNKNOWN_SUBSCRIBER()); } } /** * Publishes the given item to all current subscribers by invoking its {@code onNext() method} using {@code Executor} * provided as constructor parameter (or the default {@code Executor} if not provided). *

* Concrete behaviour is specified by {@link PublisherStrategy} selected upon {@code JerseyPublisher} creation. * * @param item the (non-null) item to publish. * @return if negative, the (negative) number of drops; otherwise an estimate of maximum lag. * @throws IllegalStateException if closed * @throws NullPointerException if item is null * @throws RejectedExecutionException if thrown by {@code Executor} */ public int publish(T item) { if (PublisherStrategy.BLOCKING == strategy) { return submit(item); } else { // PublisherStrategy.BEST_EFFORT return submissionPublisher.offer(item, this::onDrop); } } /** * Unless already closed, issues {@code onComplete()} signals to current subscribers, and disallows subsequent * attempts to publish. Upon return, this method does NOT guarantee that all subscribers have yet * completed. */ public void close() { close(true); } /** * Same as {@link #close()} but with control as to whether registered subscribers should be * closed or not. * * @param cascading Boolean controlling whether to close subscribers or not. */ public void close(boolean cascading) { cascadingClose = cascading; submissionPublisher.close(); } /** * Issues onError signals to current subscribers with the given error, and disallows subsequent attempts to publish. * * @param error the {@code onError} argument sent to subscribers * @throws NullPointerException if error is null */ public void closeExceptionally(final Throwable error) { submissionPublisher.closeExceptionally(error); } /** * Returns an estimate of the maximum number of items produced but not yet consumed among all current subscribers. * * @return estimated maximum lag */ public int estimateMaximumLag() { return submissionPublisher.estimateMaximumLag(); } /** * Returns an estimate of the minimum number of items requested but not yet produced, among all current subscribers. * * @return estimated minimum demand */ public long estimateMinimumDemand() { return submissionPublisher.estimateMinimumDemand(); } /** * Returns the exception associated with {@link #closeExceptionally}, or null if not closed or if closed normally. * * @return exception thrown on closing or {@code null} */ public Throwable getClosedException() { return submissionPublisher.getClosedException(); } /** * Returns the maximum per-subscriber buffer capacity. * * @return the maximum per-subscriber buffer capacity */ public int getMaxBufferCapacity() { return submissionPublisher.getMaxBufferCapacity(); } public class SubscriberWrapper implements Flow.Subscriber { private Flow.Subscriber subscriber; private Flow.Subscription subscription = null; public SubscriberWrapper(Flow.Subscriber subscriber) { this.subscriber = subscriber; } @Override public void onSubscribe(final Flow.Subscription subscription) { this.subscription = subscription; subscriber.onSubscribe(new Flow.Subscription() { @Override public void request(final long n) { subscription.request(n); } @Override public void cancel() { subscription.cancel(); } }); } @Override public void onNext(final T item) { subscriber.onNext(item); } @Override public void onError(final Throwable throwable) { subscriber.onError(throwable); } @Override public void onComplete() { // Propagate only when cascadingClose is true if (cascadingClose) { subscriber.onComplete(); } } public Flow.Subscriber getWrappedSubscriber() { return subscriber; } /** * Get reference to subscriber's {@link Flow.Subscription}. * * @return subscriber's {@code subscription} */ public Flow.Subscription getSubscription() { return this.subscription; } } public enum PublisherStrategy { /** * Blocking publisher strategy - tries to deliver to all subscribers regardless the cost. * * The thread is blocked uninterruptibly while resources for any subscriber are unavailable. * This strategy comes with a risk of thread exhaustion, that will lead to publisher being completely blocked by slow * or incorrectly implemented subscribers. */ BLOCKING, /** * Best effort publisher strategy - tries to deliver to all subscribers if possible without blocking the processing. * * If the buffer is full, publisher invokes {@code onError()} and cancels subscription on a subscriber, that is not * capable of read the messages at a speed sufficient to unblock the processing. */ BEST_EFFORT, } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy