* Note that the operator takes an already instantiated, running or terminated {@code CompletionStage}. - * If the optional is to be created per consumer upon subscription, use {@link #defer(Supplier)} + * If the {@code CompletionStage} is to be created per consumer upon subscription, use {@link #defer(Supplier)} * around {@code fromCompletionStage}: *
* Maybe.defer(() -> Completable.fromCompletionStage(createCompletionStage()));
diff --git a/src/main/java/io/reactivex/rxjava3/core/Flowable.java b/src/main/java/io/reactivex/rxjava3/core/Flowable.java
index 6cbdf0a412..39f3c63b43 100644
--- a/src/main/java/io/reactivex/rxjava3/core/Flowable.java
+++ b/src/main/java/io/reactivex/rxjava3/core/Flowable.java
@@ -2040,7 +2040,7 @@ public static int bufferSize() {
@NonNull
@BackpressureSupport(BackpressureKind.PASS_THROUGH)
@SchedulerSupport(SchedulerSupport.NONE)
- public static <@NonNull T> Flowable defer(@NonNull Supplier extends Publisher extends T>> supplier) {
+ public static <@NonNull T> Flowable defer(@NonNull Supplier extends @NonNull Publisher extends T>> supplier) {
Objects.requireNonNull(supplier, "supplier is null");
return RxJavaPlugins.onAssembly(new FlowableDefer<>(supplier));
}
@@ -2095,7 +2095,7 @@ public static int bufferSize() {
@NonNull
@BackpressureSupport(BackpressureKind.PASS_THROUGH)
@SchedulerSupport(SchedulerSupport.NONE)
- public static <@NonNull T> Flowable error(@NonNull Supplier extends Throwable> supplier) {
+ public static <@NonNull T> Flowable error(@NonNull Supplier extends @NonNull Throwable> supplier) {
Objects.requireNonNull(supplier, "supplier is null");
return RxJavaPlugins.onAssembly(new FlowableError<>(supplier));
}
@@ -7475,8 +7475,10 @@ public final Flowable cacheWithInitialCapacity(int initialCapacity) {
}
/**
- * Returns a {@code Flowable} that emits the items emitted by the current {@code Flowable}, converted to the specified
- * type.
+ * Returns a {@code Flowable} that emits the upstream items while
+ * they can be cast via {@link Class#cast(Object)} until the upstream terminates,
+ * or until the upstream signals an item which can't be cast,
+ * resulting in a {@link ClassCastException} to be signaled to the downstream.
*
*
*
@@ -7489,8 +7491,7 @@ public final Flowable cacheWithInitialCapacity(int initialCapacity) {
*
* @param the output value type cast to
* @param clazz
- * the target class type that {@code cast} will cast the items emitted by the current {@code Flowable}
- * into before emitting them from the resulting {@code Flowable}
+ * the target class to use to try and cast the upstream items into
* @return the new {@code Flowable} instance
* @throws NullPointerException if {@code clazz} is {@code null}
* @see ReactiveX operators documentation: Map
@@ -8265,7 +8266,7 @@ public final Completable concatMapCompletableDelayError(@NonNull Function supe
* other succeeds or completes, emits their success value if available or terminates immediately if
* either this {@code Flowable} or the current inner {@code MaybeSource} fail.
*
- *
+ *
*
* - Backpressure:
* - The operator expects the upstream to support backpressure and honors
@@ -8298,7 +8299,7 @@ public final Completable concatMapCompletableDelayError(@NonNull Function supe
* other succeeds or completes, emits their success value if available or terminates immediately if
* either this {@code Flowable} or the current inner {@code MaybeSource} fail.
*
- *
+ *
*
* - Backpressure:
* - The operator expects the upstream to support backpressure and honors
@@ -8338,7 +8339,7 @@ public final Completable concatMapCompletableDelayError(@NonNull Function supe
* other terminates, emits their success value if available and delaying all errors
* till both this {@code Flowable} and all inner {@code MaybeSource}s terminate.
*
- *
+ *
*
* - Backpressure:
* - The operator expects the upstream to support backpressure and honors
@@ -8371,7 +8372,7 @@ public final Completable concatMapCompletableDelayError(@NonNull Function supe
* other terminates, emits their success value if available and optionally delaying all errors
* till both this {@code Flowable} and all inner {@code MaybeSource}s terminate.
*
- *
+ *
*
* - Backpressure:
* - The operator expects the upstream to support backpressure and honors
@@ -8410,7 +8411,7 @@ public final Completable concatMapCompletableDelayError(@NonNull Function supe
* other terminates, emits their success value if available and optionally delaying all errors
* till both this {@code Flowable} and all inner {@code MaybeSource}s terminate.
*
- *
+ *
*
* - Backpressure:
* - The operator expects the upstream to support backpressure and honors
@@ -8455,7 +8456,7 @@ public final Completable concatMapCompletableDelayError(@NonNull Function supe
* other succeeds, emits their success values or terminates immediately if
* either this {@code Flowable} or the current inner {@code SingleSource} fail.
*
- *
+ *
*
* - Backpressure:
* - The operator expects the upstream to support backpressure and honors
@@ -8488,7 +8489,7 @@ public final Completable concatMapCompletableDelayError(@NonNull Function supe
* other succeeds, emits their success values or terminates immediately if
* either this {@code Flowable} or the current inner {@code SingleSource} fail.
*
- *
+ *
*
* - Backpressure:
* - The operator expects the upstream to support backpressure and honors
@@ -8528,7 +8529,7 @@ public final Completable concatMapCompletableDelayError(@NonNull Function supe
* other succeeds or fails, emits their success values and delays all errors
* till both this {@code Flowable} and all inner {@code SingleSource}s terminate.
*
- *
+ *
*
* - Backpressure:
* - The operator expects the upstream to support backpressure and honors
@@ -8561,7 +8562,7 @@ public final Completable concatMapCompletableDelayError(@NonNull Function supe
* other succeeds or fails, emits their success values and optionally delays all errors
* till both this {@code Flowable} and all inner {@code SingleSource}s terminate.
*
- *
+ *
*
* - Backpressure:
* - The operator expects the upstream to support backpressure and honors
@@ -8600,7 +8601,7 @@ public final Completable concatMapCompletableDelayError(@NonNull Function supe
* other succeeds or fails, emits their success values and optionally delays errors
* till both this {@code Flowable} and all inner {@code SingleSource}s terminate.
*
- *
+ *
*
* - Backpressure:
* - The operator expects the upstream to support backpressure and honors
@@ -8930,7 +8931,59 @@ public final Flowable
debounce(long timeout, @NonNull TimeUnit unit) {
public final Flowable debounce(long timeout, @NonNull TimeUnit unit, @NonNull Scheduler scheduler) {
Objects.requireNonNull(unit, "unit is null");
Objects.requireNonNull(scheduler, "scheduler is null");
- return RxJavaPlugins.onAssembly(new FlowableDebounceTimed<>(this, timeout, unit, scheduler));
+ return RxJavaPlugins.onAssembly(new FlowableDebounceTimed<>(this, timeout, unit, scheduler, null));
+ }
+
+ /**
+ * Returns a {@code Flowable} that mirrors the current {@code Flowable}, except that it drops items emitted by the
+ * current {@code Flowable} that are followed by newer items before a timeout value expires on a specified
+ * {@link Scheduler}. The timer resets on each emission.
+ *
+ * Note: If items keep being emitted by the current {@code Flowable} faster than the timeout then no items
+ * will be emitted by the resulting {@code Flowable}.
+ *
+ *
+ *
+ * Delivery of the item after the grace period happens on the given {@code Scheduler}'s
+ * {@code Worker} which if takes too long, a newer item may arrive from the upstream, causing the
+ * {@code Worker}'s task to get disposed, which may also interrupt any downstream blocking operation
+ * (yielding an {@code InterruptedException}). It is recommended processing items
+ * that may take long time to be moved to another thread via {@link #observeOn} applied after
+ * {@code debounce} itself.
+ *
+ * - Backpressure:
+ * - This operator does not support backpressure as it uses time to control data flow.
+ * - Scheduler:
+ * - You specify which {@code Scheduler} this operator will use.
+ *
+ *
+ * @param timeout
+ * the time each item has to be "the most recent" of those emitted by the current {@code Flowable} to
+ * ensure that it's not dropped
+ * @param unit
+ * the unit of time for the specified {@code timeout}
+ * @param scheduler
+ * the {@code Scheduler} to use internally to manage the timers that handle the timeout for each
+ * item
+ * @param onDropped
+ * called with the current entry when it has been replaced by a new one
+ * @return the new {@code Flowable} instance
+ * @throws NullPointerException if {@code unit} or {@code scheduler} is {@code null} or {@code onDropped} is {@code null}
+ * @see ReactiveX operators documentation: Debounce
+ * @see RxJava wiki: Backpressure
+ * @see #throttleWithTimeout(long, TimeUnit, Scheduler, Consumer)
+ * @since 3.1.6 - Experimental
+ */
+ @CheckReturnValue
+ @NonNull
+ @BackpressureSupport(BackpressureKind.ERROR)
+ @SchedulerSupport(SchedulerSupport.CUSTOM)
+ @Experimental
+ public final Flowable debounce(long timeout, @NonNull TimeUnit unit, @NonNull Scheduler scheduler, @NonNull Consumer super T> onDropped) {
+ Objects.requireNonNull(unit, "unit is null");
+ Objects.requireNonNull(scheduler, "scheduler is null");
+ Objects.requireNonNull(onDropped, "onDropped is null");
+ return RxJavaPlugins.onAssembly(new FlowableDebounceTimed<>(this, timeout, unit, scheduler, onDropped));
}
/**
@@ -10148,7 +10201,7 @@ public final Maybe firstElement() {
* Returns a {@link Single} that emits only the very first item emitted by this {@code Flowable}, or a default
* item if this {@code Flowable} completes without emitting anything.
*
- *
+ *
*
* - Backpressure:
* - The operator honors backpressure from downstream and consumes the current {@code Flowable} in a bounded manner.
@@ -10904,6 +10957,8 @@ public final Completable flatMapCompletable(@NonNull Function super T, ? exten
/**
* Maps each element of the upstream {@code Flowable} into {@link MaybeSource}s, subscribes to all of them
* and merges their {@code onSuccess} values, in no particular order, into a single {@code Flowable} sequence.
+ *
+ *
*
* - Backpressure:
* - The operator consumes the upstream in an unbounded manner.
@@ -10927,6 +10982,8 @@ public final Completable flatMapCompletable(@NonNull Function super T, ? exten
* Maps each element of the upstream {@code Flowable} into {@link MaybeSource}s, subscribes to at most
* {@code maxConcurrency} {@code MaybeSource}s at a time and merges their {@code onSuccess} values,
* in no particular order, into a single {@code Flowable} sequence, optionally delaying all errors.
+ *
+ *
*
* - Backpressure:
* - If {@code maxConcurrency == }{@link Integer#MAX_VALUE} the operator consumes the upstream in an unbounded manner.
@@ -12494,7 +12551,7 @@ public final Flowable
onBackpressureBuffer(int capacity, boolean delayError)
@NonNull
public final Flowable onBackpressureBuffer(int capacity, boolean delayError, boolean unbounded) {
ObjectHelper.verifyPositive(capacity, "capacity");
- return RxJavaPlugins.onAssembly(new FlowableOnBackpressureBuffer<>(this, capacity, unbounded, delayError, Functions.EMPTY_ACTION));
+ return RxJavaPlugins.onAssembly(new FlowableOnBackpressureBuffer<>(this, capacity, unbounded, delayError, Functions.EMPTY_ACTION, Functions.emptyConsumer()));
}
/**
@@ -12525,6 +12582,7 @@ public final Flowable onBackpressureBuffer(int capacity, boolean delayError,
* @throws NullPointerException if {@code onOverflow} is {@code null}
* @throws IllegalArgumentException if {@code capacity} is non-positive
* @see ReactiveX operators documentation: backpressure operators
+ * @see #onBackpressureBuffer(int, boolean, boolean, Action, Consumer)
* @since 1.1.0
*/
@CheckReturnValue
@@ -12535,7 +12593,51 @@ public final Flowable onBackpressureBuffer(int capacity, boolean delayError,
@NonNull Action onOverflow) {
Objects.requireNonNull(onOverflow, "onOverflow is null");
ObjectHelper.verifyPositive(capacity, "capacity");
- return RxJavaPlugins.onAssembly(new FlowableOnBackpressureBuffer<>(this, capacity, unbounded, delayError, onOverflow));
+ return RxJavaPlugins.onAssembly(new FlowableOnBackpressureBuffer<>(this, capacity, unbounded, delayError, onOverflow, Functions.emptyConsumer()));
+ }
+
+ /**
+ * Buffers an optionally unlimited number of items from the current {@code Flowable} and allows it to emit as fast it can while allowing the
+ * downstream to consume the items at its own place.
+ * If {@code unbounded} is {@code true}, the resulting {@code Flowable} will signal a
+ * {@link MissingBackpressureException} via {@code onError} as soon as the buffer's capacity is exceeded, dropping all undelivered
+ * items, canceling the flow and calling the {@code onOverflow} action.
+ *
+ *
+ *
+ * - Backpressure:
+ * - The operator honors backpressure from downstream and consumes the current {@code Flowable} in an unbounded
+ * manner (i.e., not applying backpressure to it).
+ * - Scheduler:
+ * - {@code onBackpressureBuffer} does not operate by default on a particular {@link Scheduler}.
+ *
+ *
+ * @param capacity number of slots available in the buffer.
+ * @param delayError
+ * if {@code true}, an exception from the current {@code Flowable} is delayed until all buffered elements have been
+ * consumed by the downstream; if {@code false}, an exception is immediately signaled to the downstream, skipping
+ * any buffered element
+ * @param unbounded
+ * if {@code true}, the capacity value is interpreted as the internal "island" size of the unbounded buffer
+ * @param onOverflow action to execute if an item needs to be buffered, but there are no available slots.
+ * @param onDropped the {@link Consumer} to be called with the item that could not be buffered due to capacity constraints.
+ * @return the new {@code Flowable} instance
+ * @throws NullPointerException if {@code onOverflow} or {@code onDropped} is {@code null}
+ * @throws IllegalArgumentException if {@code capacity} is non-positive
+ * @see ReactiveX operators documentation: backpressure operators
+ * @since 3.1.7
+ */
+ @CheckReturnValue
+ @NonNull
+ @BackpressureSupport(BackpressureKind.SPECIAL)
+ @SchedulerSupport(SchedulerSupport.NONE)
+ @Experimental
+ public final Flowable onBackpressureBuffer(int capacity, boolean delayError, boolean unbounded,
+ @NonNull Action onOverflow, @NonNull Consumer super T> onDropped) {
+ Objects.requireNonNull(onOverflow, "onOverflow is null");
+ Objects.requireNonNull(onDropped, "onDropped is null");
+ ObjectHelper.verifyPositive(capacity, "capacity");
+ return RxJavaPlugins.onAssembly(new FlowableOnBackpressureBuffer<>(this, capacity, unbounded, delayError, onOverflow, onDropped));
}
/**
@@ -12601,6 +12703,7 @@ public final Flowable onBackpressureBuffer(int capacity, @NonNull Action onOv
* @throws NullPointerException if {@code onOverflow} or {@code overflowStrategy} is {@code null}
* @throws IllegalArgumentException if {@code capacity} is non-positive
* @see ReactiveX operators documentation: backpressure operators
+ * @see #onBackpressureBuffer(long, Action, BackpressureOverflowStrategy)
* @since 2.0
*/
@CheckReturnValue
@@ -12610,9 +12713,55 @@ public final Flowable onBackpressureBuffer(int capacity, @NonNull Action onOv
public final Flowable onBackpressureBuffer(long capacity, @Nullable Action onOverflow, @NonNull BackpressureOverflowStrategy overflowStrategy) {
Objects.requireNonNull(overflowStrategy, "overflowStrategy is null");
ObjectHelper.verifyPositive(capacity, "capacity");
- return RxJavaPlugins.onAssembly(new FlowableOnBackpressureBufferStrategy<>(this, capacity, onOverflow, overflowStrategy));
+ return RxJavaPlugins.onAssembly(new FlowableOnBackpressureBufferStrategy<>(this, capacity, onOverflow, overflowStrategy, null));
}
+ /**
+ * Buffers an optionally unlimited number of items from the current {@code Flowable} and allows it to emit as fast it can while allowing the
+ * downstream to consume the items at its own place.
+ * The resulting {@code Flowable} will behave as determined by {@code overflowStrategy} if the buffer capacity is exceeded:
+ *
+ * - {@link BackpressureOverflowStrategy#ERROR} (default) will call {@code onError} dropping all undelivered items,
+ * canceling the source, and notifying the producer with {@code onOverflow}.
+ * - {@link BackpressureOverflowStrategy#DROP_LATEST} will drop any new items emitted by the producer while
+ * the buffer is full, without generating any {@code onError}. Each drop will, however, invoke {@code onOverflow}
+ * to signal the overflow to the producer.
+ * - {@link BackpressureOverflowStrategy#DROP_OLDEST} will drop the oldest items in the buffer in order to make
+ * room for newly emitted ones. Overflow will not generate an {@code onError}, but each drop will invoke
+ * {@code onOverflow} to signal the overflow to the producer.
+ *
+ *
+ *
+ *
+ *
+ * - Backpressure:
+ * - The operator honors backpressure from downstream and consumes the current {@code Flowable} in an unbounded
+ * manner (i.e., not applying backpressure to it).
+ * - Scheduler:
+ * - {@code onBackpressureBuffer} does not operate by default on a particular {@link Scheduler}.
+ *
+ *
+ * @param capacity number of slots available in the buffer.
+ * @param onOverflow action to execute if an item needs to be buffered, but there are no available slots, {@code null} is allowed.
+ * @param overflowStrategy how should the resulting {@code Flowable} react to buffer overflows, {@code null} is not allowed.
+ * @param onDropped the {@link Consumer} to be called with the item that could not be buffered due to capacity constraints.
+ * @return the new {@code Flowable} instance
+ * @throws NullPointerException if {@code onOverflow}, {@code overflowStrategy} or {@code onDropped} is {@code null}
+ * @throws IllegalArgumentException if {@code capacity} is non-positive
+ * @see ReactiveX operators documentation: backpressure operators
+ * @since 3.1.7
+ */
+ @CheckReturnValue
+ @NonNull
+ @BackpressureSupport(BackpressureKind.SPECIAL)
+ @SchedulerSupport(SchedulerSupport.NONE)
+ @Experimental
+ public final Flowable onBackpressureBuffer(long capacity, @Nullable Action onOverflow, @NonNull BackpressureOverflowStrategy overflowStrategy, @NonNull Consumer super T> onDropped) {
+ Objects.requireNonNull(overflowStrategy, "overflowStrategy is null");
+ Objects.requireNonNull(onDropped, "onDropped is null");
+ ObjectHelper.verifyPositive(capacity, "capacity");
+ return RxJavaPlugins.onAssembly(new FlowableOnBackpressureBufferStrategy<>(this, capacity, onOverflow, overflowStrategy, onDropped));
+ }
/**
* Drops items from the current {@code Flowable} if the downstream is not ready to receive new items (indicated
* by a lack of {@link Subscription#request(long)} calls from it).
@@ -12703,7 +12852,46 @@ public final Flowable onBackpressureDrop(@NonNull Consumer super T> onDrop)
@SchedulerSupport(SchedulerSupport.NONE)
@NonNull
public final Flowable onBackpressureLatest() {
- return RxJavaPlugins.onAssembly(new FlowableOnBackpressureLatest<>(this));
+ return RxJavaPlugins.onAssembly(new FlowableOnBackpressureLatest<>(this, null));
+ }
+
+ /**
+ * Drops all but the latest item emitted by the current {@code Flowable} if the downstream is not ready to receive
+ * new items (indicated by a lack of {@link Subscription#request(long)} calls from it) and emits this latest
+ * item when the downstream becomes ready.
+ *
+ *
+ *
+ * Its behavior is logically equivalent to {@code blockingLatest()} with the exception that
+ * the downstream is not blocking while requesting more values.
+ *
+ * Note that if the current {@code Flowable} does support backpressure, this operator ignores that capability
+ * and doesn't propagate any backpressure requests from downstream.
+ *
+ * Note that due to the nature of how backpressure requests are propagated through subscribeOn/observeOn,
+ * requesting more than 1 from downstream doesn't guarantee a continuous delivery of {@code onNext} events.
+ *
+ * - Backpressure:
+ * - The operator honors backpressure from downstream and consumes the current {@code Flowable} in an unbounded
+ * manner (i.e., not applying backpressure to it).
+ * - Scheduler:
+ * - {@code onBackpressureLatest} does not operate by default on a particular {@link Scheduler}.
+ *
+ *
+ * @param onDropped
+ * called with the current entry when it has been replaced by a new one
+ * @throws NullPointerException if {@code onDropped} is {@code null}
+ * @return the new {@code Flowable} instance
+ * @since 3.1.7
+ */
+ @CheckReturnValue
+ @BackpressureSupport(BackpressureKind.UNBOUNDED_IN)
+ @SchedulerSupport(SchedulerSupport.NONE)
+ @NonNull
+ @Experimental
+ public final Flowable onBackpressureLatest(@NonNull Consumer super T> onDropped) {
+ Objects.requireNonNull(onDropped, "onDropped is null");
+ return RxJavaPlugins.onAssembly(new FlowableOnBackpressureLatest<>(this, onDropped));
}
/**
@@ -14672,7 +14860,7 @@ public final Flowable sample(long period, @NonNull TimeUnit unit, boolean emi
public final Flowable sample(long period, @NonNull TimeUnit unit, @NonNull Scheduler scheduler) {
Objects.requireNonNull(unit, "unit is null");
Objects.requireNonNull(scheduler, "scheduler is null");
- return RxJavaPlugins.onAssembly(new FlowableSampleTimed<>(this, period, unit, scheduler, false));
+ return RxJavaPlugins.onAssembly(new FlowableSampleTimed<>(this, period, unit, scheduler, false, null));
}
/**
@@ -14713,7 +14901,51 @@ public final Flowable sample(long period, @NonNull TimeUnit unit, @NonNull Sc
public final Flowable sample(long period, @NonNull TimeUnit unit, @NonNull Scheduler scheduler, boolean emitLast) {
Objects.requireNonNull(unit, "unit is null");
Objects.requireNonNull(scheduler, "scheduler is null");
- return RxJavaPlugins.onAssembly(new FlowableSampleTimed<>(this, period, unit, scheduler, emitLast));
+ return RxJavaPlugins.onAssembly(new FlowableSampleTimed<>(this, period, unit, scheduler, emitLast, null));
+ }
+
+ /**
+ * Returns a {@code Flowable} that emits the most recently emitted item (if any) emitted by the current {@code Flowable}
+ * within periodic time intervals, where the intervals are defined on a particular {@link Scheduler}
+ * and optionally emit the very last upstream item when the upstream completes.
+ *
+ *
+ *
+ * - Backpressure:
+ * - This operator does not support backpressure as it uses time to control data flow.
+ * - Scheduler:
+ * - You specify which {@code Scheduler} this operator will use.
+ *
+ *
+ * @param period
+ * the sampling rate
+ * @param unit
+ * the {@link TimeUnit} in which {@code period} is defined
+ * @param scheduler
+ * the {@code Scheduler} to use when sampling
+ * @param emitLast
+ * if {@code true} and the upstream completes while there is still an unsampled item available,
+ * that item is emitted to downstream before completion
+ * if {@code false}, an unsampled last item is ignored.
+ * @param onDropped
+ * called with the current entry when it has been replaced by a new one
+ * @return the new {@code Flowable} instance
+ * @throws NullPointerException if {@code unit} or {@code scheduler} is {@code null} or {@code onDropped} is {@code null}
+ * @see ReactiveX operators documentation: Sample
+ * @see RxJava wiki: Backpressure
+ * @see #throttleLast(long, TimeUnit, Scheduler)
+ * @since 3.1.6 - Experimental
+ */
+ @CheckReturnValue
+ @NonNull
+ @BackpressureSupport(BackpressureKind.ERROR)
+ @SchedulerSupport(SchedulerSupport.CUSTOM)
+ @Experimental
+ public final Flowable sample(long period, @NonNull TimeUnit unit, @NonNull Scheduler scheduler, boolean emitLast, @NonNull Consumer super T> onDropped) {
+ Objects.requireNonNull(unit, "unit is null");
+ Objects.requireNonNull(scheduler, "scheduler is null");
+ Objects.requireNonNull(onDropped, "onDropped is null");
+ return RxJavaPlugins.onAssembly(new FlowableSampleTimed<>(this, period, unit, scheduler, emitLast, onDropped));
}
/**
@@ -15816,7 +16048,7 @@ public final Disposable subscribe(@NonNull Consumer super T> onNext, @NonNull
* terminates or this particular {@code Disposable} is disposed, the {@code Subscriber} is removed
* from the given container.
*
- * The {@coded Subscriber} will be removed after the callback for the terminal event has been invoked.
+ * The {@code Subscriber} will be removed after the callback for the terminal event has been invoked.
*
* - Backpressure:
* - The operator consumes the current {@code Flowable} in an unbounded manner (i.e., no
@@ -17096,7 +17328,50 @@ public final Flowable
throttleFirst(long windowDuration, @NonNull TimeUnit un
public final Flowable throttleFirst(long skipDuration, @NonNull TimeUnit unit, @NonNull Scheduler scheduler) {
Objects.requireNonNull(unit, "unit is null");
Objects.requireNonNull(scheduler, "scheduler is null");
- return RxJavaPlugins.onAssembly(new FlowableThrottleFirstTimed<>(this, skipDuration, unit, scheduler));
+ return RxJavaPlugins.onAssembly(new FlowableThrottleFirstTimed<>(this, skipDuration, unit, scheduler, null));
+ }
+
+ /**
+ * Returns a {@code Flowable} that emits only the first item emitted by the current {@code Flowable} during sequential
+ * time windows of a specified duration, where the windows are managed by a specified {@link Scheduler}.
+ *
+ * This differs from {@link #throttleLast} in that this only tracks the passage of time whereas
+ * {@link #throttleLast} ticks at scheduled intervals.
+ *
+ *
+ *
+ * - Backpressure:
+ * - This operator does not support backpressure as it uses time to control data flow.
+ * - Scheduler:
+ * - You specify which {@code Scheduler} this operator will use.
+ *
+ *
+ * @param skipDuration
+ * time to wait before emitting another item after emitting the last item
+ * @param unit
+ * the unit of time of {@code skipDuration}
+ * @param scheduler
+ * the {@code Scheduler} to use internally to manage the timers that handle timeout for each
+ * event
+ * @param onDropped
+ * called when an item doesn't get delivered to the downstream
+ *
+ * @return the new {@code Flowable} instance
+ * @throws NullPointerException if {@code unit} or {@code scheduler} or {@code onDropped} is {@code null}
+ * @see ReactiveX operators documentation: Sample
+ * @see RxJava wiki: Backpressure
+ * @since 3.1.6 - Experimental
+ */
+ @CheckReturnValue
+ @NonNull
+ @BackpressureSupport(BackpressureKind.ERROR)
+ @SchedulerSupport(SchedulerSupport.CUSTOM)
+ @Experimental
+ public final Flowable throttleFirst(long skipDuration, @NonNull TimeUnit unit, @NonNull Scheduler scheduler, @NonNull Consumer super T> onDropped) {
+ Objects.requireNonNull(unit, "unit is null");
+ Objects.requireNonNull(scheduler, "scheduler is null");
+ Objects.requireNonNull(onDropped, "onDropped is null");
+ return RxJavaPlugins.onAssembly(new FlowableThrottleFirstTimed<>(this, skipDuration, unit, scheduler, onDropped));
}
/**
@@ -17170,6 +17445,47 @@ public final Flowable throttleLast(long intervalDuration, @NonNull TimeUnit u
return sample(intervalDuration, unit, scheduler);
}
+ /**
+ * Returns a {@code Flowable} that emits only the last item emitted by the current {@code Flowable} during sequential
+ * time windows of a specified duration, where the duration is governed by a specified {@link Scheduler}.
+ *
+ * This differs from {@link #throttleFirst(long, TimeUnit, Scheduler)} in that this ticks along at a scheduled interval whereas
+ * {@code throttleFirst} does not tick, it just tracks the passage of time.
+ *
+ *
+ *
+ * - Backpressure:
+ * - This operator does not support backpressure as it uses time to control data flow.
+ * - Scheduler:
+ * - You specify which {@code Scheduler} this operator will use.
+ *
+ *
+ * @param intervalDuration
+ * duration of windows within which the last item emitted by the current {@code Flowable} will be
+ * emitted
+ * @param unit
+ * the unit of time of {@code intervalDuration}
+ * @param scheduler
+ * the {@code Scheduler} to use internally to manage the timers that handle timeout for each
+ * event
+ * @param onDropped
+ * called with the current entry when it has been replaced by a new one
+ * @return the new {@code Flowable} instance
+ * @throws NullPointerException if {@code unit} or {@code scheduler} is {@code null} or {@code onDropped} is {@code null}
+ * @see ReactiveX operators documentation: Sample
+ * @see RxJava wiki: Backpressure
+ * @see #sample(long, TimeUnit, Scheduler)
+ * @since 3.1.6 - Experimental
+ */
+ @CheckReturnValue
+ @BackpressureSupport(BackpressureKind.ERROR)
+ @SchedulerSupport(SchedulerSupport.CUSTOM)
+ @NonNull
+ @Experimental
+ public final Flowable throttleLast(long intervalDuration, @NonNull TimeUnit unit, @NonNull Scheduler scheduler, @NonNull Consumer super T> onDropped) {
+ return sample(intervalDuration, unit, scheduler, false, onDropped);
+ }
+
/**
* Throttles items from the upstream {@code Flowable} by first emitting the next
* item from upstream, then periodically emitting the latest item (if any) when
@@ -17327,7 +17643,61 @@ public final Flowable throttleLatest(long timeout, @NonNull TimeUnit unit, @N
public final Flowable throttleLatest(long timeout, @NonNull TimeUnit unit, @NonNull Scheduler scheduler, boolean emitLast) {
Objects.requireNonNull(unit, "unit is null");
Objects.requireNonNull(scheduler, "scheduler is null");
- return RxJavaPlugins.onAssembly(new FlowableThrottleLatest<>(this, timeout, unit, scheduler, emitLast));
+ return RxJavaPlugins.onAssembly(new FlowableThrottleLatest<>(this, timeout, unit, scheduler, emitLast, null));
+ }
+
+ /**
+ * Throttles items from the upstream {@code Flowable} by first emitting the next
+ * item from upstream, then periodically emitting the latest item (if any) when
+ * the specified timeout elapses between them, invoking the consumer for any dropped item.
+ *
+ *
+ *
+ * If no items were emitted from the upstream during this timeout phase, the next
+ * upstream item is emitted immediately and the timeout window starts from then.
+ *
+ * - Backpressure:
+ * - This operator does not support backpressure as it uses time to control data flow.
+ * If the downstream is not ready to receive items, a
+ * {@link io.reactivex.rxjava3.exceptions.MissingBackpressureException MissingBackpressureException}
+ * will be signaled.
+ * - Scheduler:
+ * - You specify which {@link Scheduler} this operator will use.
+ * - Error handling:
+ * -
+ * If the upstream signals an {@code onError} or {@code onDropped} callback crashes,
+ * the error is delivered immediately to the downstream. If both happen, a {@link CompositeException}
+ * is created, containing both the upstream and the callback error.
+ * If the {@code onDropped} callback crashes during cancellation, the exception is forwarded
+ * to the global error handler via {@link RxJavaPlugins#onError(Throwable)}.
+ *
+ *
+ * @param timeout the time to wait after an item emission towards the downstream
+ * before trying to emit the latest item from upstream again
+ * @param unit the time unit
+ * @param scheduler the {@code Scheduler} where the timed wait and latest item
+ * emission will be performed
+ * @param emitLast If {@code true}, the very last item from the upstream will be emitted
+ * immediately when the upstream completes, regardless if there is
+ * a timeout window active or not. If {@code false}, the very last
+ * upstream item is ignored and the flow terminates.
+ * @param onDropped called when an item is replaced by a newer item that doesn't get delivered
+ * to the downstream, including the very last item if {@code emitLast} is {@code false}
+ * and the current undelivered item when the sequence gets canceled.
+ * @return the new {@code Flowable} instance
+ * @throws NullPointerException if {@code unit}, {@code scheduler} or {@code onDropped} is {@code null}
+ * @since 3.1.6 - Experimental
+ */
+ @CheckReturnValue
+ @NonNull
+ @BackpressureSupport(BackpressureKind.ERROR)
+ @SchedulerSupport(SchedulerSupport.CUSTOM)
+ @Experimental
+ public final Flowable throttleLatest(long timeout, @NonNull TimeUnit unit, @NonNull Scheduler scheduler, boolean emitLast, @NonNull Consumer super T> onDropped) {
+ Objects.requireNonNull(unit, "unit is null");
+ Objects.requireNonNull(scheduler, "scheduler is null");
+ Objects.requireNonNull(onDropped, "onDropped is null");
+ return RxJavaPlugins.onAssembly(new FlowableThrottleLatest<>(this, timeout, unit, scheduler, emitLast, onDropped));
}
/**
@@ -17405,6 +17775,49 @@ public final Flowable throttleWithTimeout(long timeout, @NonNull TimeUnit uni
return debounce(timeout, unit, scheduler);
}
+ /**
+ * Returns a {@code Flowable} that mirrors the current {@code Flowable}, except that it drops items emitted by the
+ * current {@code Flowable} that are followed by newer items before a timeout value expires on a specified
+ * {@link Scheduler}. The timer resets on each emission (alias to {@link #debounce(long, TimeUnit, Scheduler, Consumer)}).
+ *
+ * Note: If items keep being emitted by the current {@code Flowable} faster than the timeout then no items
+ * will be emitted by the resulting {@code Flowable}.
+ *
+ *
+ *
+ * - Backpressure:
+ * - This operator does not support backpressure as it uses time to control data flow.
+ * - Scheduler:
+ * - You specify which {@code Scheduler} this operator will use.
+ *
+ *
+ * @param timeout
+ * the length of the window of time that must pass after the emission of an item from the current
+ * {@code Flowable} in which it emits no items in order for the item to be emitted by the
+ * resulting {@code Flowable}
+ * @param unit
+ * the unit of time for the specified {@code timeout}
+ * @param scheduler
+ * the {@code Scheduler} to use internally to manage the timers that handle the timeout for each
+ * item
+ * @param onDropped
+ * called with the current entry when it has been replaced by a new one
+ * @return the new {@code Flowable} instance
+ * @throws NullPointerException if {@code unit} or {@code scheduler} is {@code null} or {@code onDropped} is {@code null}
+ * @see ReactiveX operators documentation: Debounce
+ * @see RxJava wiki: Backpressure
+ * @see #debounce(long, TimeUnit, Scheduler, Consumer)
+ * @since 3.1.6 - Experimental
+ */
+ @CheckReturnValue
+ @BackpressureSupport(BackpressureKind.ERROR)
+ @SchedulerSupport(SchedulerSupport.CUSTOM)
+ @NonNull
+ @Experimental
+ public final Flowable throttleWithTimeout(long timeout, @NonNull TimeUnit unit, @NonNull Scheduler scheduler, @NonNull Consumer super T> onDropped) {
+ return debounce(timeout, unit, scheduler, onDropped);
+ }
+
/**
* Returns a {@code Flowable} that emits records of the time interval between consecutive items emitted by the
* current {@code Flowable}.
@@ -19851,7 +20264,7 @@ public final TestSubscriber test(long initialRequest, boolean cancel) { // No
*
*
* Note that the operator takes an already instantiated, running or terminated {@code CompletionStage}.
- * If the optional is to be created per consumer upon subscription, use {@link #defer(Supplier)}
+ * If the {@code CompletionStage} is to be created per consumer upon subscription, use {@link #defer(Supplier)}
* around {@code fromCompletionStage}:
*
* Flowable.defer(() -> Flowable.fromCompletionStage(createCompletionStage()));
@@ -19986,7 +20399,7 @@ public final TestSubscriber test(long initialRequest, boolean cancel) { // No
@BackpressureSupport(BackpressureKind.UNBOUNDED_IN)
@SchedulerSupport(SchedulerSupport.NONE)
@NonNull
- public final <@NonNull R, @NonNull A> Single collect(@NonNull Collector super T, A, R> collector) {
+ public final <@NonNull R, @Nullable A> Single collect(@NonNull Collector super T, A, R> collector) {
Objects.requireNonNull(collector, "collector is null");
return RxJavaPlugins.onAssembly(new FlowableCollectWithCollectorSingle<>(this, collector));
}
diff --git a/src/main/java/io/reactivex/rxjava3/core/Maybe.java b/src/main/java/io/reactivex/rxjava3/core/Maybe.java
index bbb3a10d4b..8ae4137c83 100644
--- a/src/main/java/io/reactivex/rxjava3/core/Maybe.java
+++ b/src/main/java/io/reactivex/rxjava3/core/Maybe.java
@@ -110,7 +110,7 @@
* @since 2.0
* @see io.reactivex.rxjava3.observers.DisposableMaybeObserver
*/
-public abstract class Maybe implements MaybeSource {
+public abstract class Maybe<@NonNull T> implements MaybeSource {
/**
* Runs multiple {@link MaybeSource}s provided by an {@link Iterable} sequence and
@@ -889,7 +889,7 @@ public abstract class Maybe implements MaybeSource {
@CheckReturnValue
@NonNull
@SchedulerSupport(SchedulerSupport.NONE)
- public static <@NonNull T> Maybe defer(@NonNull Supplier extends MaybeSource extends T>> supplier) {
+ public static <@NonNull T> Maybe defer(@NonNull Supplier extends @NonNull MaybeSource extends T>> supplier) {
Objects.requireNonNull(supplier, "supplier is null");
return RxJavaPlugins.onAssembly(new MaybeDefer<>(supplier));
}
@@ -961,7 +961,7 @@ public abstract class Maybe implements MaybeSource {
@CheckReturnValue
@NonNull
@SchedulerSupport(SchedulerSupport.NONE)
- public static <@NonNull T> Maybe error(@NonNull Supplier extends Throwable> supplier) {
+ public static <@NonNull T> Maybe error(@NonNull Supplier extends @NonNull Throwable> supplier) {
Objects.requireNonNull(supplier, "supplier is null");
return RxJavaPlugins.onAssembly(new MaybeErrorCallable<>(supplier));
}
@@ -1079,7 +1079,7 @@ public abstract class Maybe implements MaybeSource {
@CheckReturnValue
@NonNull
@SchedulerSupport(SchedulerSupport.NONE)
- public static <@NonNull T> Maybe fromCallable(@NonNull Callable extends T> callable) {
+ public static Maybe<@NonNull T> fromCallable(@NonNull Callable extends @Nullable T> callable) {
Objects.requireNonNull(callable, "callable is null");
return RxJavaPlugins.onAssembly(new MaybeFromCallable<>(callable));
}
@@ -1109,6 +1109,7 @@ public abstract class Maybe implements MaybeSource {
* @return the new {@code Maybe} instance
* @throws NullPointerException if {@code future} is {@code null}
* @see ReactiveX operators documentation: From
+ * @see #fromCompletionStage(CompletionStage)
*/
@CheckReturnValue
@NonNull
@@ -1147,6 +1148,7 @@ public abstract class Maybe implements MaybeSource {
* @return the new {@code Maybe} instance
* @throws NullPointerException if {@code future} or {@code unit} is {@code null}
* @see ReactiveX operators documentation: From
+ * @see #fromCompletionStage(CompletionStage)
*/
@CheckReturnValue
@NonNull
@@ -1285,7 +1287,7 @@ public abstract class Maybe implements MaybeSource {
@CheckReturnValue
@NonNull
@SchedulerSupport(SchedulerSupport.NONE)
- public static <@NonNull T> Maybe fromSupplier(@NonNull Supplier extends T> supplier) {
+ public static Maybe<@NonNull T> fromSupplier(@NonNull Supplier extends @Nullable T> supplier) {
Objects.requireNonNull(supplier, "supplier is null");
return RxJavaPlugins.onAssembly(new MaybeFromSupplier<>(supplier));
}
@@ -6145,7 +6147,7 @@ public final TestObserver test(boolean dispose) {
*
*
* Note that the operator takes an already instantiated, running or terminated {@code CompletionStage}.
- * If the optional is to be created per consumer upon subscription, use {@link #defer(Supplier)}
+ * If the {@code CompletionStage} is to be created per consumer upon subscription, use {@link #defer(Supplier)}
* around {@code fromCompletionStage}:
*
* Maybe.defer(() -> Maybe.fromCompletionStage(createCompletionStage()));
diff --git a/src/main/java/io/reactivex/rxjava3/core/Observable.java b/src/main/java/io/reactivex/rxjava3/core/Observable.java
index 2206319b18..fcf809cdf6 100644
--- a/src/main/java/io/reactivex/rxjava3/core/Observable.java
+++ b/src/main/java/io/reactivex/rxjava3/core/Observable.java
@@ -1790,7 +1790,7 @@ public static int bufferSize() {
@CheckReturnValue
@NonNull
@SchedulerSupport(SchedulerSupport.NONE)
- public static <@NonNull T> Observable defer(@NonNull Supplier extends ObservableSource extends T>> supplier) {
+ public static <@NonNull T> Observable defer(@NonNull Supplier extends @NonNull ObservableSource extends T>> supplier) {
Objects.requireNonNull(supplier, "supplier is null");
return RxJavaPlugins.onAssembly(new ObservableDefer<>(supplier));
}
@@ -1839,7 +1839,7 @@ public static int bufferSize() {
@CheckReturnValue
@NonNull
@SchedulerSupport(SchedulerSupport.NONE)
- public static <@NonNull T> Observable error(@NonNull Supplier extends Throwable> supplier) {
+ public static <@NonNull T> Observable error(@NonNull Supplier extends @NonNull Throwable> supplier) {
Objects.requireNonNull(supplier, "supplier is null");
return RxJavaPlugins.onAssembly(new ObservableError<>(supplier));
}
@@ -2020,6 +2020,7 @@ public static int bufferSize() {
* @return the new {@code Observable} instance
* @throws NullPointerException if {@code future} is {@code null}
* @see ReactiveX operators documentation: From
+ * @see #fromCompletionStage(CompletionStage)
*/
@CheckReturnValue
@NonNull
@@ -2062,6 +2063,7 @@ public static int bufferSize() {
* @return the new {@code Observable} instance
* @throws NullPointerException if {@code future} or {@code unit} is {@code null}
* @see ReactiveX operators documentation: From
+ * @see #fromCompletionStage(CompletionStage)
*/
@CheckReturnValue
@NonNull
@@ -5520,7 +5522,6 @@ public final T blockingFirst(@NonNull T defaultItem) {
* @see #blockingForEach(Consumer, int)
*/
@SchedulerSupport(SchedulerSupport.NONE)
- @NonNull
public final void blockingForEach(@NonNull Consumer super T> onNext) {
blockingForEach(onNext, bufferSize());
}
@@ -5560,7 +5561,6 @@ public final void blockingForEach(@NonNull Consumer super T> onNext) {
* @see #subscribe(Consumer)
*/
@SchedulerSupport(SchedulerSupport.NONE)
- @NonNull
public final void blockingForEach(@NonNull Consumer super T> onNext, int capacityHint) {
Objects.requireNonNull(onNext, "onNext is null");
Iterator it = blockingIterable(capacityHint).iterator();
@@ -6668,8 +6668,10 @@ public final Observable cacheWithInitialCapacity(int initialCapacity) {
}
/**
- * Returns an {@code Observable} that emits the items emitted by the current {@code Observable}, converted to the specified
- * type.
+ * Returns an {@code Observable} that emits the upstream items while
+ * they can be cast via {@link Class#cast(Object)} until the upstream terminates,
+ * or until the upstream signals an item which can't be cast,
+ * resulting in a {@link ClassCastException} to be signaled to the downstream.
*
*
*
@@ -6679,8 +6681,7 @@ public final Observable cacheWithInitialCapacity(int initialCapacity) {
*
* @param the output value type cast to
* @param clazz
- * the target class type that {@code cast} will cast the items emitted by the current {@code Observable}
- * into before emitting them from the resulting {@code Observable}
+ * the target class to use to try and cast the upstream items into
* @return the new {@code Observable} instance
* @throws NullPointerException if {@code clazz} is {@code null}
* @see ReactiveX operators documentation: Map
@@ -7328,7 +7329,7 @@ public final Completable concatMapCompletableDelayError(@NonNull Function supe
* other succeeds or completes, emits their success value if available or terminates immediately if
* either the current {@code Observable} or the current inner {@code MaybeSource} fail.
*
- *
+ *
*
* - Scheduler:
* - {@code concatMapMaybe} does not operate by default on a particular {@link Scheduler}.
@@ -7356,7 +7357,7 @@ public final Completable concatMapCompletableDelayError(@NonNull Function supe
* other succeeds or completes, emits their success value if available or terminates immediately if
* either the current {@code Observable} or the current inner {@code MaybeSource} fail.
*
- *
+ *
*
* - Scheduler:
* - {@code concatMapMaybe} does not operate by default on a particular {@link Scheduler}.
@@ -7389,7 +7390,7 @@ public final Completable concatMapCompletableDelayError(@NonNull Function supe
* other terminates, emits their success value if available and delaying all errors
* till both the current {@code Observable} and all inner {@code MaybeSource}s terminate.
*
- *
+ *
*
* - Scheduler:
* - {@code concatMapMaybeDelayError} does not operate by default on a particular {@link Scheduler}.
@@ -7417,7 +7418,7 @@ public final Completable concatMapCompletableDelayError(@NonNull Function supe
* other terminates, emits their success value if available and optionally delaying all errors
* till both the current {@code Observable} and all inner {@code MaybeSource}s terminate.
*
- *
+ *
*
* - Scheduler:
* - {@code concatMapMaybeDelayError} does not operate by default on a particular {@link Scheduler}.
@@ -7451,7 +7452,7 @@ public final Completable concatMapCompletableDelayError(@NonNull Function supe
* other terminates, emits their success value if available and optionally delaying all errors
* till both the current {@code Observable} and all inner {@code MaybeSource}s terminate.
*
- *
+ *
*
* - Scheduler:
* - {@code concatMapMaybeDelayError} does not operate by default on a particular {@link Scheduler}.
@@ -7489,7 +7490,7 @@ public final Completable concatMapCompletableDelayError(@NonNull Function supe
* other succeeds, emits their success values or terminates immediately if
* either the current {@code Observable} or the current inner {@code SingleSource} fail.
*
- *
+ *
*
* - Scheduler:
* - {@code concatMapSingle} does not operate by default on a particular {@link Scheduler}.
@@ -7517,7 +7518,7 @@ public final Completable concatMapCompletableDelayError(@NonNull Function supe
* other succeeds, emits their success values or terminates immediately if
* either the current {@code Observable} or the current inner {@code SingleSource} fail.
*
- *
+ *
*
* - Scheduler:
* - {@code concatMapSingle} does not operate by default on a particular {@link Scheduler}.
@@ -7550,7 +7551,7 @@ public final Completable concatMapCompletableDelayError(@NonNull Function supe
* other succeeds or fails, emits their success values and delays all errors
* till both the current {@code Observable} and all inner {@code SingleSource}s terminate.
*
- *
+ *
*
* - Scheduler:
* - {@code concatMapSingleDelayError} does not operate by default on a particular {@link Scheduler}.
@@ -7578,7 +7579,7 @@ public final Completable concatMapCompletableDelayError(@NonNull Function supe
* other succeeds or fails, emits their success values and optionally delays all errors
* till both the current {@code Observable} and all inner {@code SingleSource}s terminate.
*
- *
+ *
*
* - Scheduler:
* - {@code concatMapSingleDelayError} does not operate by default on a particular {@link Scheduler}.
@@ -7612,7 +7613,7 @@ public final Completable concatMapCompletableDelayError(@NonNull Function supe
* other succeeds or fails, emits their success values and optionally delays errors
* till both the current {@code Observable} and all inner {@code SingleSource}s terminate.
*
- *
+ *
*
* - Scheduler:
* - {@code concatMapSingleDelayError} does not operate by default on a particular {@link Scheduler}.
@@ -7896,7 +7897,55 @@ public final Observable debounce(long timeout, @NonNull TimeUnit unit) {
public final Observable debounce(long timeout, @NonNull TimeUnit unit, @NonNull Scheduler scheduler) {
Objects.requireNonNull(unit, "unit is null");
Objects.requireNonNull(scheduler, "scheduler is null");
- return RxJavaPlugins.onAssembly(new ObservableDebounceTimed<>(this, timeout, unit, scheduler));
+ return RxJavaPlugins.onAssembly(new ObservableDebounceTimed<>(this, timeout, unit, scheduler, null));
+ }
+
+ /**
+ * Returns an {@code Observable} that mirrors the current {@code Observable}, except that it drops items emitted by the
+ * current {@code Observable} that are followed by newer items before a timeout value expires on a specified
+ * {@link Scheduler}. The timer resets on each emission.
+ *
+ * Note: If items keep being emitted by the current {@code Observable} faster than the timeout then no items
+ * will be emitted by the resulting {@code Observable}.
+ *
+ *
+ *
+ * Delivery of the item after the grace period happens on the given {@code Scheduler}'s
+ * {@code Worker} which if takes too long, a newer item may arrive from the upstream, causing the
+ * {@code Worker}'s task to get disposed, which may also interrupt any downstream blocking operation
+ * (yielding an {@code InterruptedException}). It is recommended processing items
+ * that may take long time to be moved to another thread via {@link #observeOn} applied after
+ * {@code debounce} itself.
+ *
+ * - Scheduler:
+ * - You specify which {@code Scheduler} this operator will use.
+ *
+ *
+ * @param timeout
+ * the time each item has to be "the most recent" of those emitted by the current {@code Observable} to
+ * ensure that it's not dropped
+ * @param unit
+ * the unit of time for the specified {@code timeout}
+ * @param scheduler
+ * the {@code Scheduler} to use internally to manage the timers that handle the timeout for each
+ * item
+ * @param onDropped
+ * called with the current entry when it has been replaced by a new one
+ * @return the new {@code Observable} instance
+ * @throws NullPointerException if {@code unit} or {@code scheduler} is {@code null} } or {@code onDropped} is {@code null}
+ * @see ReactiveX operators documentation: Debounce
+ * @see #throttleWithTimeout(long, TimeUnit, Scheduler, Consumer)
+ * @since 3.1.6 - Experimental
+ */
+ @CheckReturnValue
+ @SchedulerSupport(SchedulerSupport.CUSTOM)
+ @NonNull
+ @Experimental
+ public final Observable debounce(long timeout, @NonNull TimeUnit unit, @NonNull Scheduler scheduler, @NonNull Consumer super T> onDropped) {
+ Objects.requireNonNull(unit, "unit is null");
+ Objects.requireNonNull(scheduler, "scheduler is null");
+ Objects.requireNonNull(onDropped, "onDropped is null");
+ return RxJavaPlugins.onAssembly(new ObservableDebounceTimed<>(this, timeout, unit, scheduler, onDropped));
}
/**
@@ -8953,7 +9002,7 @@ public final Maybe firstElement() {
* Returns a {@link Single} that emits only the very first item emitted by the current {@code Observable}, or a default item
* if the current {@code Observable} completes without emitting any items.
*
- *
+ *
*
* - Scheduler:
* - {@code first} does not operate by default on a particular {@link Scheduler}.
@@ -12036,7 +12085,6 @@ public final Observable retryWhen(
* @throws NullPointerException if {@code observer} is {@code null}
*/
@SchedulerSupport(SchedulerSupport.NONE)
- @NonNull
public final void safeSubscribe(@NonNull Observer super T> observer) {
Objects.requireNonNull(observer, "observer is null");
if (observer instanceof SafeObserver) {
@@ -12131,7 +12179,7 @@ public final Observable sample(long period, @NonNull TimeUnit unit, boolean e
public final Observable sample(long period, @NonNull TimeUnit unit, @NonNull Scheduler scheduler) {
Objects.requireNonNull(unit, "unit is null");
Objects.requireNonNull(scheduler, "scheduler is null");
- return RxJavaPlugins.onAssembly(new ObservableSampleTimed<>(this, period, unit, scheduler, false));
+ return RxJavaPlugins.onAssembly(new ObservableSampleTimed<>(this, period, unit, scheduler, false, null));
}
/**
@@ -12168,7 +12216,46 @@ public final Observable sample(long period, @NonNull TimeUnit unit, @NonNull
public final Observable sample(long period, @NonNull TimeUnit unit, @NonNull Scheduler scheduler, boolean emitLast) {
Objects.requireNonNull(unit, "unit is null");
Objects.requireNonNull(scheduler, "scheduler is null");
- return RxJavaPlugins.onAssembly(new ObservableSampleTimed<>(this, period, unit, scheduler, emitLast));
+ return RxJavaPlugins.onAssembly(new ObservableSampleTimed<>(this, period, unit, scheduler, emitLast, null));
+ }
+
+ /**
+ * Returns an {@code Observable} that emits the most recently emitted item (if any) emitted by the current {@code Observable}
+ * within periodic time intervals, where the intervals are defined on a particular {@link Scheduler}.
+ *
+ *
+ *
+ * - Scheduler:
+ * - You specify which {@code Scheduler} this operator will use.
+ *
+ *
+ * @param period
+ * the sampling rate
+ * @param unit
+ * the {@link TimeUnit} in which {@code period} is defined
+ * @param scheduler
+ * the {@code Scheduler} to use when sampling
+ * @param emitLast
+ * if {@code true} and the upstream completes while there is still an unsampled item available,
+ * that item is emitted to downstream before completion
+ * if {@code false}, an unsampled last item is ignored.
+ * @param onDropped
+ * called with the current entry when it has been replaced by a new one
+ * @return the new {@code Observable} instance
+ * @throws NullPointerException if {@code unit} or {@code scheduler} is {@code null} or {@code onDropped} is {@code null}
+ * @see ReactiveX operators documentation: Sample
+ * @see #throttleLast(long, TimeUnit, Scheduler)
+ * @since 3.1.6 - Experimental
+ */
+ @CheckReturnValue
+ @SchedulerSupport(SchedulerSupport.CUSTOM)
+ @NonNull
+ @Experimental
+ public final Observable sample(long period, @NonNull TimeUnit unit, @NonNull Scheduler scheduler, boolean emitLast, @NonNull Consumer super T> onDropped) {
+ Objects.requireNonNull(unit, "unit is null");
+ Objects.requireNonNull(scheduler, "scheduler is null");
+ Objects.requireNonNull(onDropped, "onDropped is null");
+ return RxJavaPlugins.onAssembly(new ObservableSampleTimed<>(this, period, unit, scheduler, emitLast, onDropped));
}
/**
@@ -14166,7 +14253,46 @@ public final Observable throttleFirst(long windowDuration, @NonNull TimeUnit
public final Observable throttleFirst(long skipDuration, @NonNull TimeUnit unit, @NonNull Scheduler scheduler) {
Objects.requireNonNull(unit, "unit is null");
Objects.requireNonNull(scheduler, "scheduler is null");
- return RxJavaPlugins.onAssembly(new ObservableThrottleFirstTimed<>(this, skipDuration, unit, scheduler));
+ return RxJavaPlugins.onAssembly(new ObservableThrottleFirstTimed<>(this, skipDuration, unit, scheduler, null));
+ }
+
+ /**
+ * Returns an {@code Observable} that emits only the first item emitted by the current {@code Observable} during sequential
+ * time windows of a specified duration, where the windows are managed by a specified {@link Scheduler}.
+ *
+ * This differs from {@link #throttleLast} in that this only tracks passage of time whereas
+ * {@code throttleLast} ticks at scheduled intervals.
+ *
+ *
+ *
+ * - Scheduler:
+ * - You specify which {@code Scheduler} this operator will use.
+ *
+ *
+ * @param skipDuration
+ * time to wait before emitting another item after emitting the last item
+ * @param unit
+ * the unit of time of {@code skipDuration}
+ * @param scheduler
+ * the {@code Scheduler} to use internally to manage the timers that handle timeout for each
+ * event
+ * @param onDropped
+ * called when an item doesn't get delivered to the downstream
+ *
+ * @return the new {@code Observable} instance
+ * @throws NullPointerException if {@code unit} or {@code scheduler} or {@code onDropped} is {@code null}
+ * @see ReactiveX operators documentation: Sample
+ * @since 3.1.6 - Experimental
+ */
+ @CheckReturnValue
+ @SchedulerSupport(SchedulerSupport.CUSTOM)
+ @NonNull
+ @Experimental
+ public final Observable throttleFirst(long skipDuration, @NonNull TimeUnit unit, @NonNull Scheduler scheduler, @NonNull Consumer super T> onDropped) {
+ Objects.requireNonNull(unit, "unit is null");
+ Objects.requireNonNull(scheduler, "scheduler is null");
+ Objects.requireNonNull(onDropped, "onDropped is null");
+ return RxJavaPlugins.onAssembly(new ObservableThrottleFirstTimed<>(this, skipDuration, unit, scheduler, onDropped));
}
/**
@@ -14199,6 +14325,43 @@ public final Observable throttleLast(long intervalDuration, @NonNull TimeUnit
return sample(intervalDuration, unit);
}
+ /**
+ * Returns an {@code Observable} that emits only the last item emitted by the current {@code Observable} during sequential
+ * time windows of a specified duration, where the duration is governed by a specified {@link Scheduler}.
+ *
+ * This differs from {@link #throttleFirst} in that this ticks along at a scheduled interval whereas
+ * {@code throttleFirst} does not tick, it just tracks passage of time.
+ *
+ *
+ *
+ * - Scheduler:
+ * - You specify which {@code Scheduler} this operator will use.
+ *
+ *
+ * @param intervalDuration
+ * duration of windows within which the last item emitted by the current {@code Observable} will be
+ * emitted
+ * @param unit
+ * the unit of time of {@code intervalDuration}
+ * @param scheduler
+ * the {@code Scheduler} to use internally to manage the timers that handle timeout for each
+ * event
+ * @param onDropped
+ * called with the current entry when it has been replaced by a new one
+ * @return the new {@code Observable} instance
+ * @throws NullPointerException if {@code unit} or {@code scheduler} is {@code null} or {@code onDropped} is {@code null}
+ * @see ReactiveX operators documentation: Sample
+ * @see #sample(long, TimeUnit, Scheduler)
+ * @since 3.1.6 - Experimental
+ */
+ @CheckReturnValue
+ @SchedulerSupport(SchedulerSupport.CUSTOM)
+ @NonNull
+ @Experimental
+ public final Observable throttleLast(long intervalDuration, @NonNull TimeUnit unit, @NonNull Scheduler scheduler, @NonNull Consumer super T> onDropped) {
+ return sample(intervalDuration, unit, scheduler, false, onDropped);
+ }
+
/**
* Returns an {@code Observable} that emits only the last item emitted by the current {@code Observable} during sequential
* time windows of a specified duration, where the duration is governed by a specified {@link Scheduler}.
@@ -14365,7 +14528,55 @@ public final Observable throttleLatest(long timeout, @NonNull TimeUnit unit,
public final Observable throttleLatest(long timeout, @NonNull TimeUnit unit, @NonNull Scheduler scheduler, boolean emitLast) {
Objects.requireNonNull(unit, "unit is null");
Objects.requireNonNull(scheduler, "scheduler is null");
- return RxJavaPlugins.onAssembly(new ObservableThrottleLatest<>(this, timeout, unit, scheduler, emitLast));
+ return RxJavaPlugins.onAssembly(new ObservableThrottleLatest<>(this, timeout, unit, scheduler, emitLast, null));
+ }
+
+ /**
+ * Throttles items from the current {@code Observable} by first emitting the next
+ * item from upstream, then periodically emitting the latest item (if any) when
+ * the specified timeout elapses between them, invoking the consumer for any dropped item.
+ *
+ *
+ *
+ * If no items were emitted from the upstream during this timeout phase, the next
+ * upstream item is emitted immediately and the timeout window starts from then.
+ *
+ * - Scheduler:
+ * - You specify which {@link Scheduler} this operator will use.
+ * - Error handling:
+ * -
+ * If the upstream signals an {@code onError} or {@code onDropped} callback crashes,
+ * the error is delivered immediately to the downstream. If both happen, a {@link CompositeException}
+ * is created, containing both the upstream and the callback error.
+ * If the {@code onDropped} callback crashes when the sequence gets disposed, the exception is forwarded
+ * to the global error handler via {@link RxJavaPlugins#onError(Throwable)}.
+ *
+ *
+ * @param timeout the time to wait after an item emission towards the downstream
+ * before trying to emit the latest item from upstream again
+ * @param unit the time unit
+ * @param scheduler the {@code Scheduler} where the timed wait and latest item
+ * emission will be performed
+ * @param emitLast If {@code true}, the very last item from the upstream will be emitted
+ * immediately when the upstream completes, regardless if there is
+ * a timeout window active or not. If {@code false}, the very last
+ * upstream item is ignored and the flow terminates.
+ * @param onDropped called when an item is replaced by a newer item that doesn't get delivered
+ * to the downstream, including the very last item if {@code emitLast} is {@code false}
+ * and the current undelivered item when the sequence gets disposed.
+ * @return the new {@code Observable} instance
+ * @throws NullPointerException if {@code unit}, {@code scheduler} or {@code onDropped} is {@code null}
+ * @since 3.1.6 - Experimental
+ */
+ @CheckReturnValue
+ @SchedulerSupport(SchedulerSupport.CUSTOM)
+ @NonNull
+ @Experimental
+ public final Observable throttleLatest(long timeout, @NonNull TimeUnit unit, @NonNull Scheduler scheduler, boolean emitLast, @NonNull Consumer super T> onDropped) {
+ Objects.requireNonNull(unit, "unit is null");
+ Objects.requireNonNull(scheduler, "scheduler is null");
+ Objects.requireNonNull(onDropped, "onDropped is null");
+ return RxJavaPlugins.onAssembly(new ObservableThrottleLatest<>(this, timeout, unit, scheduler, emitLast, onDropped));
}
/**
@@ -14435,6 +14646,45 @@ public final Observable throttleWithTimeout(long timeout, @NonNull TimeUnit u
return debounce(timeout, unit, scheduler);
}
+ /**
+ * Returns an {@code Observable} that mirrors the current {@code Observable}, except that it drops items emitted by the
+ * current {@code Observable} that are followed by newer items before a timeout value expires on a specified
+ * {@link Scheduler}. The timer resets on each emission (Alias to {@link #debounce(long, TimeUnit, Scheduler)}).
+ *
+ * Note: If items keep being emitted by the current {@code Observable} faster than the timeout then no items
+ * will be emitted by the resulting {@code Observable}.
+ *
+ *
+ *
+ * - Scheduler:
+ * - You specify which {@code Scheduler} this operator will use.
+ *
+ *
+ * @param timeout
+ * the length of the window of time that must pass after the emission of an item from the current
+ * {@code Observable}, in which the current {@code Observable} emits no items, in order for the item to be emitted by the
+ * resulting {@code Observable}
+ * @param unit
+ * the unit of time for the specified {@code timeout}
+ * @param scheduler
+ * the {@code Scheduler} to use internally to manage the timers that handle the timeout for each
+ * item
+ * @param onDropped
+ * called with the current entry when it has been replaced by a new one
+ * @return the new {@code Observable} instance
+ * @throws NullPointerException if {@code unit} or {@code scheduler} is {@code null} or {@code onDropped} is {@code null}
+ * @see ReactiveX operators documentation: Debounce
+ * @see #debounce(long, TimeUnit, Scheduler, Consumer)
+ * @since 3.1.6 - Experimental
+ */
+ @CheckReturnValue
+ @SchedulerSupport(SchedulerSupport.CUSTOM)
+ @NonNull
+ @Experimental
+ public final Observable throttleWithTimeout(long timeout, @NonNull TimeUnit unit, @NonNull Scheduler scheduler, @NonNull Consumer super T> onDropped) {
+ return debounce(timeout, unit, scheduler, onDropped);
+ }
+
/**
* Returns an {@code Observable} that emits records of the time interval between consecutive items emitted by the
* current {@code Observable}.
@@ -16616,7 +16866,7 @@ public final TestObserver test(boolean dispose) { // NoPMD
*
*
* Note that the operator takes an already instantiated, running or terminated {@code CompletionStage}.
- * If the optional is to be created per consumer upon subscription, use {@link #defer(Supplier)}
+ * If the {@code CompletionStage} is to be created per consumer upon subscription, use {@link #defer(Supplier)}
* around {@code fromCompletionStage}:
*
* Observable.defer(() -> Observable.fromCompletionStage(createCompletionStage()));
@@ -16736,7 +16986,7 @@ public final TestObserver test(boolean dispose) { // NoPMD
@CheckReturnValue
@SchedulerSupport(SchedulerSupport.NONE)
@NonNull
- public final <@NonNull R, @NonNull A> Single collect(@NonNull Collector super T, A, R> collector) {
+ public final <@NonNull R, @Nullable A> Single collect(@NonNull Collector super T, A, R> collector) {
Objects.requireNonNull(collector, "collector is null");
return RxJavaPlugins.onAssembly(new ObservableCollectWithCollectorSingle<>(this, collector));
}
diff --git a/src/main/java/io/reactivex/rxjava3/core/Scheduler.java b/src/main/java/io/reactivex/rxjava3/core/Scheduler.java
index 580e2739cb..3aa001127a 100644
--- a/src/main/java/io/reactivex/rxjava3/core/Scheduler.java
+++ b/src/main/java/io/reactivex/rxjava3/core/Scheduler.java
@@ -350,7 +350,7 @@ public Disposable schedulePeriodicallyDirect(@NonNull Runnable run, long initial
* });
*
*
- * Slowing down the rate to no more than than 1 a second. This suffers from
+ * Slowing down the rate to no more than 1 a second. This suffers from
* the same problem as the one above I could find an {@link Flowable}
* operator that limits the rate without dropping the values (aka leaky
* bucket algorithm).
diff --git a/src/main/java/io/reactivex/rxjava3/core/Single.java b/src/main/java/io/reactivex/rxjava3/core/Single.java
index ea6a610feb..6cf5a3f789 100644
--- a/src/main/java/io/reactivex/rxjava3/core/Single.java
+++ b/src/main/java/io/reactivex/rxjava3/core/Single.java
@@ -895,7 +895,7 @@ public abstract class Single<@NonNull T> implements SingleSource {
@CheckReturnValue
@NonNull
@SchedulerSupport(SchedulerSupport.NONE)
- public static <@NonNull T> Single defer(@NonNull Supplier extends SingleSource extends T>> supplier) {
+ public static <@NonNull T> Single defer(@NonNull Supplier extends @NonNull SingleSource extends T>> supplier) {
Objects.requireNonNull(supplier, "supplier is null");
return RxJavaPlugins.onAssembly(new SingleDefer<>(supplier));
}
@@ -917,7 +917,7 @@ public abstract class Single<@NonNull T> implements SingleSource {
@CheckReturnValue
@NonNull
@SchedulerSupport(SchedulerSupport.NONE)
- public static <@NonNull T> Single error(@NonNull Supplier extends Throwable> supplier) {
+ public static <@NonNull T> Single error(@NonNull Supplier extends @NonNull Throwable> supplier) {
Objects.requireNonNull(supplier, "supplier is null");
return RxJavaPlugins.onAssembly(new SingleError<>(supplier));
}
@@ -5598,7 +5598,7 @@ private static Single toSingle(@NonNull Flowable source) {
*
*
* Note that the operator takes an already instantiated, running or terminated {@code CompletionStage}.
- * If the optional is to be created per consumer upon subscription, use {@link #defer(Supplier)}
+ * If the {@code CompletionStage} is to be created per consumer upon subscription, use {@link #defer(Supplier)}
* around {@code fromCompletionStage}:
*
* Single.defer(() -> Single.fromCompletionStage(createCompletionStage()));
diff --git a/src/main/java/io/reactivex/rxjava3/disposables/Disposable.java b/src/main/java/io/reactivex/rxjava3/disposables/Disposable.java
index 67fd235086..845d603171 100644
--- a/src/main/java/io/reactivex/rxjava3/disposables/Disposable.java
+++ b/src/main/java/io/reactivex/rxjava3/disposables/Disposable.java
@@ -40,8 +40,8 @@ public interface Disposable {
/**
* Construct a {@code Disposable} by wrapping a {@link Runnable} that is
* executed exactly once when the {@code Disposable} is disposed.
- * @param run the Runnable to wrap
- * @return the new Disposable instance
+ * @param run the {@code Runnable} to wrap
+ * @return the new {@code Disposable} instance
* @throws NullPointerException if {@code run} is {@code null}
* @since 3.0.0
*/
@@ -54,8 +54,8 @@ static Disposable fromRunnable(@NonNull Runnable run) {
/**
* Construct a {@code Disposable} by wrapping a {@link Action} that is
* executed exactly once when the {@code Disposable} is disposed.
- * @param action the Action to wrap
- * @return the new Disposable instance
+ * @param action the {@code Action} to wrap
+ * @return the new {@code Disposable} instance
* @throws NullPointerException if {@code action} is {@code null}
* @since 3.0.0
*/
@@ -70,8 +70,8 @@ static Disposable fromAction(@NonNull Action action) {
* cancelled exactly once when the {@code Disposable} is disposed.
*
* The {@code Future} is cancelled with {@code mayInterruptIfRunning == true}.
- * @param future the Future to wrap
- * @return the new Disposable instance
+ * @param future the {@code Future} to wrap
+ * @return the new {@code Disposable} instance
* @throws NullPointerException if {@code future} is {@code null}
* @see #fromFuture(Future, boolean)
* @since 3.0.0
@@ -85,9 +85,9 @@ static Disposable fromFuture(@NonNull Future> future) {
/**
* Construct a {@code Disposable} by wrapping a {@link Future} that is
* cancelled exactly once when the {@code Disposable} is disposed.
- * @param future the Future to wrap
+ * @param future the {@code Future} to wrap
* @param allowInterrupt if true, the future cancel happens via {@code Future.cancel(true)}
- * @return the new Disposable instance
+ * @return the new {@code Disposable} instance
* @throws NullPointerException if {@code future} is {@code null}
* @since 3.0.0
*/
@@ -100,8 +100,8 @@ static Disposable fromFuture(@NonNull Future> future, boolean allowInterrupt)
/**
* Construct a {@code Disposable} by wrapping a {@link Subscription} that is
* cancelled exactly once when the {@code Disposable} is disposed.
- * @param subscription the Runnable to wrap
- * @return the new Disposable instance
+ * @param subscription the {@code Runnable} to wrap
+ * @return the new {@code Disposable} instance
* @throws NullPointerException if {@code subscription} is {@code null}
* @since 3.0.0
*/
@@ -114,8 +114,8 @@ static Disposable fromSubscription(@NonNull Subscription subscription) {
/**
* Construct a {@code Disposable} by wrapping an {@link AutoCloseable} that is
* closed exactly once when the {@code Disposable} is disposed.
- * @param autoCloseable the AutoCloseable to wrap
- * @return the new Disposable instance
+ * @param autoCloseable the {@code AutoCloseable} to wrap
+ * @return the new {@code Disposable} instance
* @throws NullPointerException if {@code autoCloseable} is {@code null}
* @since 3.0.0
*/
@@ -128,8 +128,8 @@ static Disposable fromAutoCloseable(@NonNull AutoCloseable autoCloseable) {
/**
* Construct an {@link AutoCloseable} by wrapping a {@code Disposable} that is
* disposed when the returned {@code AutoCloseable} is closed.
- * @param disposable the Disposable instance
- * @return the new AutoCloseable instance
+ * @param disposable the {@code Disposable} instance
+ * @return the new {@code AutoCloseable} instance
* @throws NullPointerException if {@code disposable} is {@code null}
* @since 3.0.0
*/
diff --git a/src/main/java/io/reactivex/rxjava3/exceptions/MissingBackpressureException.java b/src/main/java/io/reactivex/rxjava3/exceptions/MissingBackpressureException.java
index a817e6fb93..f0a173ba59 100644
--- a/src/main/java/io/reactivex/rxjava3/exceptions/MissingBackpressureException.java
+++ b/src/main/java/io/reactivex/rxjava3/exceptions/MissingBackpressureException.java
@@ -20,6 +20,15 @@ public final class MissingBackpressureException extends RuntimeException {
private static final long serialVersionUID = 8517344746016032542L;
+ /**
+ * The default error message.
+ *
+ * This can happen if the downstream doesn't call {@link org.reactivestreams.Subscription#request(long)}
+ * in time or at all.
+ * @since 3.1.6
+ */
+ public static final String DEFAULT_MESSAGE = "Could not emit value due to lack of requests";
+
/**
* Constructs a MissingBackpressureException without message or cause.
*/
@@ -35,4 +44,13 @@ public MissingBackpressureException(String message) {
super(message);
}
+ /**
+ * Constructs a new {@code MissingBackpressureException} with the
+ * default message {@value #DEFAULT_MESSAGE}.
+ * @return the new {@code MissingBackpressureException} instance.
+ * @since 3.1.6
+ */
+ public static MissingBackpressureException createDefault() {
+ return new MissingBackpressureException(DEFAULT_MESSAGE);
+ }
}
diff --git a/src/main/java/io/reactivex/rxjava3/exceptions/QueueOverflowException.java b/src/main/java/io/reactivex/rxjava3/exceptions/QueueOverflowException.java
new file mode 100644
index 0000000000..bdd8a25e6f
--- /dev/null
+++ b/src/main/java/io/reactivex/rxjava3/exceptions/QueueOverflowException.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2016-present, RxJava Contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
+ * the License for the specific language governing permissions and limitations under the License.
+ */
+
+package io.reactivex.rxjava3.exceptions;
+
+/**
+ * Indicates an overflow happened because the upstream disregarded backpressure completely or
+ * {@link org.reactivestreams.Subscriber#onNext(Object)} was called concurrently from multiple threads
+ * without synchronization. Rarely, it is an indication of bugs inside an operator.
+ * @since 3.1.6
+ */
+public final class QueueOverflowException extends RuntimeException {
+
+ private static final long serialVersionUID = 8517344746016032542L;
+
+ /**
+ * The message for queue overflows.
+ *
+ * This can happen if the upstream disregards backpressure completely or calls
+ * {@link org.reactivestreams.Subscriber#onNext(Object)} concurrently from multiple threads
+ * without synchronization. Rarely, it is an indication of bugs inside an operator.
+ */
+ private static final String DEFAULT_MESSAGE = "Queue overflow due to illegal concurrent onNext calls or a bug in an operator";
+
+ /**
+ * Constructs a QueueOverflowException with the default message.
+ */
+ public QueueOverflowException() {
+ this(DEFAULT_MESSAGE);
+ }
+
+ /**
+ * Constructs a QueueOverflowException with the given message but no cause.
+ * @param message the error message
+ */
+ public QueueOverflowException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/io/reactivex/rxjava3/functions/Supplier.java b/src/main/java/io/reactivex/rxjava3/functions/Supplier.java
index fe43f0d8c8..ebf7fdda6d 100644
--- a/src/main/java/io/reactivex/rxjava3/functions/Supplier.java
+++ b/src/main/java/io/reactivex/rxjava3/functions/Supplier.java
@@ -13,8 +13,6 @@
package io.reactivex.rxjava3.functions;
-import io.reactivex.rxjava3.annotations.NonNull;
-
/**
* A functional interface (callback) that provides a single value or
* throws an exception.
@@ -25,7 +23,7 @@
* @since 3.0.0
*/
@FunctionalInterface
-public interface Supplier<@NonNull T> {
+public interface Supplier {
/**
* Produces a value or throws an exception.
diff --git a/src/main/java/io/reactivex/rxjava3/internal/jdk8/FlowableFlatMapStream.java b/src/main/java/io/reactivex/rxjava3/internal/jdk8/FlowableFlatMapStream.java
index 35292bb17c..48ea8b6e5f 100644
--- a/src/main/java/io/reactivex/rxjava3/internal/jdk8/FlowableFlatMapStream.java
+++ b/src/main/java/io/reactivex/rxjava3/internal/jdk8/FlowableFlatMapStream.java
@@ -174,7 +174,7 @@ public void onNext(T t) {
if (sourceMode != QueueFuseable.ASYNC) {
if (!queue.offer(t)) {
upstream.cancel();
- onError(new MissingBackpressureException("Queue full?!"));
+ onError(new QueueOverflowException());
return;
}
}
diff --git a/src/main/java/io/reactivex/rxjava3/internal/operators/completable/CompletableConcat.java b/src/main/java/io/reactivex/rxjava3/internal/operators/completable/CompletableConcat.java
index d83ce2d398..00a53f3ad7 100644
--- a/src/main/java/io/reactivex/rxjava3/internal/operators/completable/CompletableConcat.java
+++ b/src/main/java/io/reactivex/rxjava3/internal/operators/completable/CompletableConcat.java
@@ -120,7 +120,7 @@ public void onSubscribe(Subscription s) {
public void onNext(CompletableSource t) {
if (sourceFused == QueueSubscription.NONE) {
if (!queue.offer(t)) {
- onError(new MissingBackpressureException());
+ onError(new QueueOverflowException());
return;
}
}
diff --git a/src/main/java/io/reactivex/rxjava3/internal/operators/completable/CompletableOnErrorComplete.java b/src/main/java/io/reactivex/rxjava3/internal/operators/completable/CompletableOnErrorComplete.java
index bebb401e48..c4252e7f3b 100644
--- a/src/main/java/io/reactivex/rxjava3/internal/operators/completable/CompletableOnErrorComplete.java
+++ b/src/main/java/io/reactivex/rxjava3/internal/operators/completable/CompletableOnErrorComplete.java
@@ -32,15 +32,18 @@ public CompletableOnErrorComplete(CompletableSource source, Predicate super Th
@Override
protected void subscribeActual(final CompletableObserver observer) {
- source.subscribe(new OnError(observer));
+ source.subscribe(new OnError(observer, predicate));
}
- final class OnError implements CompletableObserver {
+ static final class OnError implements CompletableObserver {
private final CompletableObserver downstream;
+ private final Predicate super Throwable> predicate;
- OnError(CompletableObserver observer) {
+ OnError(CompletableObserver observer,
+ Predicate super Throwable> predicate) {
this.downstream = observer;
+ this.predicate = predicate;
}
@Override
diff --git a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/BlockingFlowableIterable.java b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/BlockingFlowableIterable.java
index a25032e33b..11239d04a7 100644
--- a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/BlockingFlowableIterable.java
+++ b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/BlockingFlowableIterable.java
@@ -138,9 +138,12 @@ public void onSubscribe(Subscription s) {
@Override
public void onNext(T t) {
if (!queue.offer(t)) {
+ // Error must be set first before calling cancel to avoid race
+ // with hasNext(), which checks for cancel first before checking
+ // for error.
+ error = new QueueOverflowException();
SubscriptionHelper.cancel(this);
-
- onError(new MissingBackpressureException("Queue full?!"));
+ onComplete();
} else {
signalConsumer();
}
diff --git a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableBufferExactBoundary.java b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableBufferExactBoundary.java
index e543272a39..3e2abefdd9 100644
--- a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableBufferExactBoundary.java
+++ b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableBufferExactBoundary.java
@@ -45,7 +45,7 @@ protected void subscribeActual(Subscriber super U> s) {
}
static final class BufferExactBoundarySubscriber, B>
- extends QueueDrainSubscriber implements FlowableSubscriber, Subscription, Disposable {
+ extends QueueDrainSubscriber implements Subscription, Disposable {
final Supplier bufferSupplier;
final Publisher boundary;
diff --git a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableConcatMap.java b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableConcatMap.java
index bf0f0ff163..5d1b6e4c33 100644
--- a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableConcatMap.java
+++ b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableConcatMap.java
@@ -19,7 +19,7 @@
import org.reactivestreams.*;
import io.reactivex.rxjava3.core.*;
-import io.reactivex.rxjava3.exceptions.Exceptions;
+import io.reactivex.rxjava3.exceptions.*;
import io.reactivex.rxjava3.functions.*;
import io.reactivex.rxjava3.internal.subscriptions.*;
import io.reactivex.rxjava3.internal.util.*;
@@ -152,7 +152,7 @@ public final void onNext(T t) {
if (sourceMode != QueueSubscription.ASYNC) {
if (!queue.offer(t)) {
upstream.cancel();
- onError(new IllegalStateException("Queue full?!"));
+ onError(new QueueOverflowException());
return;
}
}
diff --git a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableConcatMapEager.java b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableConcatMapEager.java
index af33ec9281..dc423d89fc 100644
--- a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableConcatMapEager.java
+++ b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableConcatMapEager.java
@@ -200,7 +200,7 @@ public void innerNext(InnerQueuedSubscriber inner, R value) {
drain();
} else {
inner.cancel();
- innerError(inner, new MissingBackpressureException());
+ innerError(inner, MissingBackpressureException.createDefault());
}
}
diff --git a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableConcatMapScheduler.java b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableConcatMapScheduler.java
index c53ebe0867..707d10a19e 100644
--- a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableConcatMapScheduler.java
+++ b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableConcatMapScheduler.java
@@ -19,7 +19,7 @@
import org.reactivestreams.*;
import io.reactivex.rxjava3.core.*;
-import io.reactivex.rxjava3.exceptions.Exceptions;
+import io.reactivex.rxjava3.exceptions.*;
import io.reactivex.rxjava3.functions.*;
import io.reactivex.rxjava3.internal.operators.flowable.FlowableConcatMap.*;
import io.reactivex.rxjava3.internal.subscriptions.SubscriptionHelper;
@@ -151,7 +151,7 @@ public final void onNext(T t) {
if (sourceMode != QueueSubscription.ASYNC) {
if (!queue.offer(t)) {
upstream.cancel();
- onError(new IllegalStateException("Queue full?!"));
+ onError(new QueueOverflowException());
return;
}
}
diff --git a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableCreate.java b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableCreate.java
index 673fc4bc22..1e87c4b7e6 100644
--- a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableCreate.java
+++ b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableCreate.java
@@ -442,7 +442,7 @@ static final class ErrorAsyncEmitter extends NoOverflowBaseAsyncEmitter {
@Override
void onOverflow() {
- onError(new MissingBackpressureException("create: could not emit value due to lack of requests"));
+ onError(new MissingBackpressureException("create: " + MissingBackpressureException.DEFAULT_MESSAGE));
}
}
diff --git a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableDebounce.java b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableDebounce.java
index b1066c0133..51838a1864 100644
--- a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableDebounce.java
+++ b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableDebounce.java
@@ -148,7 +148,7 @@ void emit(long idx, T value) {
BackpressureHelper.produced(this, 1);
} else {
cancel();
- downstream.onError(new MissingBackpressureException("Could not deliver value due to lack of requests"));
+ downstream.onError(MissingBackpressureException.createDefault());
}
}
}
diff --git a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableDebounceTimed.java b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableDebounceTimed.java
index 35bfaec397..b9e5dff7f3 100644
--- a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableDebounceTimed.java
+++ b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableDebounceTimed.java
@@ -16,6 +16,8 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.*;
+import io.reactivex.rxjava3.exceptions.Exceptions;
+import io.reactivex.rxjava3.functions.Consumer;
import org.reactivestreams.*;
import io.reactivex.rxjava3.core.*;
@@ -32,19 +34,20 @@ public final class FlowableDebounceTimed extends AbstractFlowableWithUpstream
final long timeout;
final TimeUnit unit;
final Scheduler scheduler;
+ final Consumer super T> onDropped;
- public FlowableDebounceTimed(Flowable source, long timeout, TimeUnit unit, Scheduler scheduler) {
+ public FlowableDebounceTimed(Flowable source, long timeout, TimeUnit unit, Scheduler scheduler, Consumer super T> onDropped) {
super(source);
this.timeout = timeout;
this.unit = unit;
this.scheduler = scheduler;
+ this.onDropped = onDropped;
}
@Override
protected void subscribeActual(Subscriber super T> s) {
source.subscribe(new DebounceTimedSubscriber<>(
- new SerializedSubscriber<>(s),
- timeout, unit, scheduler.createWorker()));
+ new SerializedSubscriber<>(s), timeout, unit, scheduler.createWorker(), onDropped));
}
static final class DebounceTimedSubscriber extends AtomicLong
@@ -55,20 +58,22 @@ static final class DebounceTimedSubscriber extends AtomicLong
final long timeout;
final TimeUnit unit;
final Scheduler.Worker worker;
+ final Consumer super T> onDropped;
Subscription upstream;
- Disposable timer;
+ DebounceEmitter timer;
volatile long index;
boolean done;
- DebounceTimedSubscriber(Subscriber super T> actual, long timeout, TimeUnit unit, Worker worker) {
+ DebounceTimedSubscriber(Subscriber super T> actual, long timeout, TimeUnit unit, Worker worker, Consumer super T> onDropped) {
this.downstream = actual;
this.timeout = timeout;
this.unit = unit;
this.worker = worker;
+ this.onDropped = onDropped;
}
@Override
@@ -88,15 +93,26 @@ public void onNext(T t) {
long idx = index + 1;
index = idx;
- Disposable d = timer;
- if (d != null) {
- d.dispose();
+ DebounceEmitter currentEmitter = timer;
+ if (currentEmitter != null) {
+ currentEmitter.dispose();
+ }
+
+ if (onDropped != null && currentEmitter != null) {
+ try {
+ onDropped.accept(currentEmitter.value);
+ } catch (Throwable ex) {
+ Exceptions.throwIfFatal(ex);
+ upstream.cancel();
+ done = true;
+ downstream.onError(ex);
+ worker.dispose();
+ }
}
- DebounceEmitter de = new DebounceEmitter<>(t, idx, this);
- timer = de;
- d = worker.schedule(de, timeout, unit);
- de.setResource(d);
+ DebounceEmitter newEmitter = new DebounceEmitter<>(t, idx, this);
+ timer = newEmitter;
+ newEmitter.setResource(worker.schedule(newEmitter, timeout, unit));
}
@Override
@@ -121,15 +137,13 @@ public void onComplete() {
}
done = true;
- Disposable d = timer;
+ DebounceEmitter d = timer;
if (d != null) {
d.dispose();
}
- @SuppressWarnings("unchecked")
- DebounceEmitter de = (DebounceEmitter)d;
- if (de != null) {
- de.emit();
+ if (d != null) {
+ d.emit();
}
downstream.onComplete();
@@ -159,7 +173,7 @@ void emit(long idx, T t, DebounceEmitter emitter) {
emitter.dispose();
} else {
cancel();
- downstream.onError(new MissingBackpressureException("Could not deliver value due to lack of requests"));
+ downstream.onError(MissingBackpressureException.createDefault());
}
}
}
diff --git a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableDelay.java b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableDelay.java
index 469d0dd48b..a7de73213a 100644
--- a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableDelay.java
+++ b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableDelay.java
@@ -111,7 +111,9 @@ final class OnNext implements Runnable {
@Override
public void run() {
- downstream.onNext(t);
+ if (!w.isDisposed()) {
+ downstream.onNext(t);
+ }
}
}
diff --git a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableFlatMap.java b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableFlatMap.java
index 78c9a9e542..c250d3b165 100644
--- a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableFlatMap.java
+++ b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableFlatMap.java
@@ -243,7 +243,7 @@ void tryEmitScalar(U value) {
q = getMainQueue();
}
if (!q.offer(value)) {
- onError(new MissingBackpressureException("Scalar queue full?!"));
+ onError(new QueueOverflowException());
}
}
if (decrementAndGet() == 0) {
@@ -252,7 +252,7 @@ void tryEmitScalar(U value) {
} else {
SimpleQueue q = getMainQueue();
if (!q.offer(value)) {
- onError(new MissingBackpressureException("Scalar queue full?!"));
+ onError(new QueueOverflowException());
return;
}
if (getAndIncrement() != 0) {
@@ -278,7 +278,7 @@ void tryEmit(U value, InnerSubscriber inner) {
inner.queue = q;
}
if (!q.offer(value)) {
- onError(new MissingBackpressureException("Inner queue full?!"));
+ onError(new QueueOverflowException());
}
}
if (decrementAndGet() == 0) {
@@ -291,7 +291,7 @@ void tryEmit(U value, InnerSubscriber inner) {
inner.queue = q;
}
if (!q.offer(value)) {
- onError(new MissingBackpressureException("Inner queue full?!"));
+ onError(new QueueOverflowException());
return;
}
if (getAndIncrement() != 0) {
diff --git a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableFlattenIterable.java b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableFlattenIterable.java
index 019a132329..0b9164e0cd 100644
--- a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableFlattenIterable.java
+++ b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableFlattenIterable.java
@@ -180,7 +180,7 @@ public void onNext(T t) {
return;
}
if (fusionMode == NONE && !queue.offer(t)) {
- onError(new MissingBackpressureException("Queue is full?!"));
+ onError(new QueueOverflowException());
return;
}
drain();
diff --git a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableGroupBy.java b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableGroupBy.java
index 344b029018..50e641ffc1 100644
--- a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableGroupBy.java
+++ b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableGroupBy.java
@@ -168,7 +168,7 @@ public void onNext(T t) {
if (emittedGroups != get()) {
downstream.onNext(group);
} else {
- MissingBackpressureException mbe = new MissingBackpressureException(groupHangWarning(emittedGroups));
+ MissingBackpressureException mbe = groupHangWarning(emittedGroups);
mbe.initCause(ex);
onError(mbe);
return;
@@ -194,13 +194,13 @@ public void onNext(T t) {
}
} else {
upstream.cancel();
- onError(new MissingBackpressureException(groupHangWarning(emittedGroups)));
+ onError(groupHangWarning(emittedGroups));
}
}
}
- static String groupHangWarning(long n) {
- return "Unable to emit a new group (#" + n + ") due to lack of requests. Please make sure the downstream can always accept a new group as well as each group is consumed in order for the whole operator to be able to proceed.";
+ static MissingBackpressureException groupHangWarning(long n) {
+ return new MissingBackpressureException("Unable to emit a new group (#" + n + ") due to lack of requests. Please make sure the downstream can always accept a new group as well as each group is consumed in order for the whole operator to be able to proceed.");
}
@Override
diff --git a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableGroupJoin.java b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableGroupJoin.java
index 3021b9ddc5..c9619c48b7 100644
--- a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableGroupJoin.java
+++ b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableGroupJoin.java
@@ -276,7 +276,7 @@ void drain() {
a.onNext(w);
BackpressureHelper.produced(requested, 1);
} else {
- fail(new MissingBackpressureException("Could not emit value due to lack of requests"), a, q);
+ fail(MissingBackpressureException.createDefault(), a, q);
return;
}
diff --git a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableInterval.java b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableInterval.java
index 536e36c2ea..98fbe6dee1 100644
--- a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableInterval.java
+++ b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableInterval.java
@@ -93,7 +93,7 @@ public void run() {
downstream.onNext(count++);
BackpressureHelper.produced(this, 1);
} else {
- downstream.onError(new MissingBackpressureException("Can't deliver value " + count + " due to lack of requests"));
+ downstream.onError(new MissingBackpressureException("Could not emit value " + count + " due to lack of requests"));
DisposableHelper.dispose(resource);
}
}
diff --git a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableIntervalRange.java b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableIntervalRange.java
index 7c556acdac..cfa605882d 100644
--- a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableIntervalRange.java
+++ b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableIntervalRange.java
@@ -114,7 +114,7 @@ public void run() {
decrementAndGet();
}
} else {
- downstream.onError(new MissingBackpressureException("Can't deliver value " + count + " due to lack of requests"));
+ downstream.onError(new MissingBackpressureException("Could not emit value " + count + " due to lack of requests"));
DisposableHelper.dispose(resource);
}
}
diff --git a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableJoin.java b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableJoin.java
index 2c269cee4d..9cda3d457d 100644
--- a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableJoin.java
+++ b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableJoin.java
@@ -260,7 +260,7 @@ void drain() {
e++;
} else {
- ExceptionHelper.addThrowable(error, new MissingBackpressureException("Could not emit value due to lack of requests"));
+ ExceptionHelper.addThrowable(error, MissingBackpressureException.createDefault());
q.clear();
cancelAll();
errorAll(a);
@@ -321,7 +321,7 @@ else if (mode == RIGHT_VALUE) {
e++;
} else {
- ExceptionHelper.addThrowable(error, new MissingBackpressureException("Could not emit value due to lack of requests"));
+ ExceptionHelper.addThrowable(error, MissingBackpressureException.createDefault());
q.clear();
cancelAll();
errorAll(a);
diff --git a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableObserveOn.java b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableObserveOn.java
index 4e9f83e41e..576debbf2b 100644
--- a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableObserveOn.java
+++ b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableObserveOn.java
@@ -113,7 +113,7 @@ public final void onNext(T t) {
if (!queue.offer(t)) {
upstream.cancel();
- error = new MissingBackpressureException("Queue is full?!");
+ error = new QueueOverflowException();
done = true;
}
trySchedule();
@@ -244,8 +244,7 @@ public final boolean isEmpty() {
}
}
- static final class ObserveOnSubscriber extends BaseObserveOnSubscriber
- implements FlowableSubscriber {
+ static final class ObserveOnSubscriber extends BaseObserveOnSubscriber {
private static final long serialVersionUID = -4547113800637756442L;
diff --git a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableOnBackpressureBuffer.java b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableOnBackpressureBuffer.java
index 8261df29dc..db58b68a10 100644
--- a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableOnBackpressureBuffer.java
+++ b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableOnBackpressureBuffer.java
@@ -20,7 +20,7 @@
import io.reactivex.rxjava3.annotations.Nullable;
import io.reactivex.rxjava3.core.*;
import io.reactivex.rxjava3.exceptions.*;
-import io.reactivex.rxjava3.functions.Action;
+import io.reactivex.rxjava3.functions.*;
import io.reactivex.rxjava3.internal.subscriptions.*;
import io.reactivex.rxjava3.internal.util.BackpressureHelper;
import io.reactivex.rxjava3.operators.*;
@@ -30,19 +30,21 @@ public final class FlowableOnBackpressureBuffer extends AbstractFlowableWithU
final boolean unbounded;
final boolean delayError;
final Action onOverflow;
+ final Consumer super T> onDropped;
public FlowableOnBackpressureBuffer(Flowable source, int bufferSize, boolean unbounded,
- boolean delayError, Action onOverflow) {
+ boolean delayError, Action onOverflow, Consumer super T> onDropped) {
super(source);
this.bufferSize = bufferSize;
this.unbounded = unbounded;
this.delayError = delayError;
this.onOverflow = onOverflow;
+ this.onDropped = onDropped;
}
@Override
protected void subscribeActual(Subscriber super T> s) {
- source.subscribe(new BackpressureBufferSubscriber<>(s, bufferSize, unbounded, delayError, onOverflow));
+ source.subscribe(new BackpressureBufferSubscriber<>(s, bufferSize, unbounded, delayError, onOverflow, onDropped));
}
static final class BackpressureBufferSubscriber extends BasicIntQueueSubscription implements FlowableSubscriber {
@@ -53,6 +55,7 @@ static final class BackpressureBufferSubscriber extends BasicIntQueueSubscrip
final SimplePlainQueue queue;
final boolean delayError;
final Action onOverflow;
+ final Consumer super T> onDropped;
Subscription upstream;
@@ -66,10 +69,11 @@ static final class BackpressureBufferSubscriber extends BasicIntQueueSubscrip
boolean outputFused;
BackpressureBufferSubscriber(Subscriber super T> actual, int bufferSize,
- boolean unbounded, boolean delayError, Action onOverflow) {
+ boolean unbounded, boolean delayError, Action onOverflow, Consumer super T> onDropped) {
this.downstream = actual;
this.onOverflow = onOverflow;
this.delayError = delayError;
+ this.onDropped = onDropped;
SimplePlainQueue q;
@@ -98,6 +102,7 @@ public void onNext(T t) {
MissingBackpressureException ex = new MissingBackpressureException("Buffer is full");
try {
onOverflow.run();
+ onDropped.accept(t);
} catch (Throwable e) {
Exceptions.throwIfFatal(e);
ex.initCause(e);
diff --git a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableOnBackpressureBufferStrategy.java b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableOnBackpressureBufferStrategy.java
index 9b0b8a7b20..7963fb7d40 100644
--- a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableOnBackpressureBufferStrategy.java
+++ b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableOnBackpressureBufferStrategy.java
@@ -20,7 +20,7 @@
import io.reactivex.rxjava3.core.*;
import io.reactivex.rxjava3.exceptions.*;
-import io.reactivex.rxjava3.functions.Action;
+import io.reactivex.rxjava3.functions.*;
import io.reactivex.rxjava3.internal.subscriptions.SubscriptionHelper;
import io.reactivex.rxjava3.internal.util.BackpressureHelper;
import io.reactivex.rxjava3.plugins.RxJavaPlugins;
@@ -38,17 +38,21 @@ public final class FlowableOnBackpressureBufferStrategy extends AbstractFlowa
final BackpressureOverflowStrategy strategy;
+ final Consumer super T> onDropped;
+
public FlowableOnBackpressureBufferStrategy(Flowable source,
- long bufferSize, Action onOverflow, BackpressureOverflowStrategy strategy) {
+ long bufferSize, Action onOverflow, BackpressureOverflowStrategy strategy,
+ Consumer super T> onDropped) {
super(source);
this.bufferSize = bufferSize;
this.onOverflow = onOverflow;
this.strategy = strategy;
+ this.onDropped = onDropped;
}
@Override
protected void subscribeActual(Subscriber super T> s) {
- source.subscribe(new OnBackpressureBufferStrategySubscriber<>(s, onOverflow, strategy, bufferSize));
+ source.subscribe(new OnBackpressureBufferStrategySubscriber<>(s, onOverflow, strategy, bufferSize, onDropped));
}
static final class OnBackpressureBufferStrategySubscriber
@@ -61,6 +65,8 @@ static final class OnBackpressureBufferStrategySubscriber
final Action onOverflow;
+ final Consumer super T> onDropped;
+
final BackpressureOverflowStrategy strategy;
final long bufferSize;
@@ -77,13 +83,15 @@ static final class OnBackpressureBufferStrategySubscriber
Throwable error;
OnBackpressureBufferStrategySubscriber(Subscriber super T> actual, Action onOverflow,
- BackpressureOverflowStrategy strategy, long bufferSize) {
+ BackpressureOverflowStrategy strategy, long bufferSize,
+ Consumer super T> onDropped) {
this.downstream = actual;
this.onOverflow = onOverflow;
this.strategy = strategy;
this.bufferSize = bufferSize;
this.requested = new AtomicLong();
this.deque = new ArrayDeque<>();
+ this.onDropped = onDropped;
}
@Override
@@ -104,44 +112,60 @@ public void onNext(T t) {
}
boolean callOnOverflow = false;
boolean callError = false;
+ boolean callDrain = false;
Deque dq = deque;
+ T toDrop = null;
synchronized (dq) {
if (dq.size() == bufferSize) {
switch (strategy) {
case DROP_LATEST:
- dq.pollLast();
+ toDrop = dq.pollLast();
dq.offer(t);
callOnOverflow = true;
break;
case DROP_OLDEST:
- dq.poll();
+ toDrop = dq.poll();
dq.offer(t);
callOnOverflow = true;
break;
default:
// signal error
+ toDrop = t;
callError = true;
break;
}
} else {
dq.offer(t);
+ callDrain = true;
}
}
- if (callOnOverflow) {
- if (onOverflow != null) {
- try {
- onOverflow.run();
- } catch (Throwable ex) {
- Exceptions.throwIfFatal(ex);
- upstream.cancel();
- onError(ex);
- }
+ if (callOnOverflow && onOverflow != null) {
+ try {
+ onOverflow.run();
+ } catch (Throwable ex) {
+ Exceptions.throwIfFatal(ex);
+ upstream.cancel();
+ onError(ex);
+ }
+ }
+
+ if (onDropped != null && toDrop != null) {
+ try {
+ onDropped.accept(toDrop);
+ } catch (Throwable ex) {
+ Exceptions.throwIfFatal(ex);
+ upstream.cancel();
+ onError(ex);
}
- } else if (callError) {
+ }
+
+ if (callError) {
upstream.cancel();
- onError(new MissingBackpressureException());
- } else {
+ onError(MissingBackpressureException.createDefault());
+ }
+
+ if (callDrain) {
drain();
}
}
diff --git a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableOnBackpressureError.java b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableOnBackpressureError.java
index 03e668b2ba..acaf165069 100644
--- a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableOnBackpressureError.java
+++ b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableOnBackpressureError.java
@@ -66,7 +66,7 @@ public void onNext(T t) {
BackpressureHelper.produced(this, 1);
} else {
upstream.cancel();
- onError(new MissingBackpressureException("could not emit value due to lack of requests"));
+ onError(MissingBackpressureException.createDefault());
}
}
diff --git a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableOnBackpressureLatest.java b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableOnBackpressureLatest.java
index 1a98831bd2..155e284e93 100644
--- a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableOnBackpressureLatest.java
+++ b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableOnBackpressureLatest.java
@@ -14,30 +14,48 @@
package io.reactivex.rxjava3.internal.operators.flowable;
import io.reactivex.rxjava3.core.Flowable;
+import io.reactivex.rxjava3.exceptions.Exceptions;
+import io.reactivex.rxjava3.functions.Consumer;
import org.reactivestreams.Subscriber;
public final class FlowableOnBackpressureLatest extends AbstractFlowableWithUpstream {
- public FlowableOnBackpressureLatest(Flowable source) {
+ final Consumer super T> onDropped;
+
+ public FlowableOnBackpressureLatest(Flowable source, Consumer super T> onDropped) {
super(source);
+ this.onDropped = onDropped;
}
@Override
protected void subscribeActual(Subscriber super T> s) {
- source.subscribe(new BackpressureLatestSubscriber<>(s));
+ source.subscribe(new BackpressureLatestSubscriber<>(s, onDropped));
}
static final class BackpressureLatestSubscriber extends AbstractBackpressureThrottlingSubscriber {
private static final long serialVersionUID = 163080509307634843L;
- BackpressureLatestSubscriber(Subscriber super T> downstream) {
+ final Consumer super T> onDropped;
+
+ BackpressureLatestSubscriber(Subscriber super T> downstream,
+ Consumer super T> onDropped) {
super(downstream);
+ this.onDropped = onDropped;
}
@Override
public void onNext(T t) {
- current.lazySet(t);
+ T oldValue = current.getAndSet(t);
+ if (onDropped != null && oldValue != null) {
+ try {
+ onDropped.accept(oldValue);
+ } catch (Throwable ex) {
+ Exceptions.throwIfFatal(ex);
+ upstream.cancel();
+ downstream.onError(ex);
+ }
+ }
drain();
}
}
diff --git a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowablePublish.java b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowablePublish.java
index 87f43b762b..b05bc8b748 100644
--- a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowablePublish.java
+++ b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowablePublish.java
@@ -226,7 +226,7 @@ public void onSubscribe(Subscription s) {
public void onNext(T t) {
// we expect upstream to honor backpressure requests
if (sourceMode == QueueSubscription.NONE && !queue.offer(t)) {
- onError(new MissingBackpressureException("Prefetch queue is full?!"));
+ onError(new QueueOverflowException());
return;
}
// since many things can happen concurrently, we have a common dispatch
diff --git a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowablePublishMulticast.java b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowablePublishMulticast.java
index c97b8b6bbf..08d0e40058 100644
--- a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowablePublishMulticast.java
+++ b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowablePublishMulticast.java
@@ -212,7 +212,7 @@ public void onNext(T t) {
}
if (sourceMode == QueueSubscription.NONE && !queue.offer(t)) {
upstream.get().cancel();
- onError(new MissingBackpressureException());
+ onError(MissingBackpressureException.createDefault());
return;
}
drain();
diff --git a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableSamplePublisher.java b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableSamplePublisher.java
index 778f3faf21..19474d2cc0 100644
--- a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableSamplePublisher.java
+++ b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableSamplePublisher.java
@@ -129,7 +129,7 @@ void emit() {
BackpressureHelper.produced(requested, 1);
} else {
cancel();
- downstream.onError(new MissingBackpressureException("Couldn't emit value due to lack of requests!"));
+ downstream.onError(MissingBackpressureException.createDefault());
}
}
}
diff --git a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableSampleTimed.java b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableSampleTimed.java
index e17ad04a66..40551f4a8e 100644
--- a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableSampleTimed.java
+++ b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableSampleTimed.java
@@ -16,6 +16,8 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.*;
+import io.reactivex.rxjava3.exceptions.Exceptions;
+import io.reactivex.rxjava3.functions.Consumer;
import org.reactivestreams.*;
import io.reactivex.rxjava3.core.*;
@@ -29,24 +31,25 @@ public final class FlowableSampleTimed extends AbstractFlowableWithUpstream onDropped;
- public FlowableSampleTimed(Flowable source, long period, TimeUnit unit, Scheduler scheduler, boolean emitLast) {
+ public FlowableSampleTimed(Flowable source, long period, TimeUnit unit, Scheduler scheduler, boolean emitLast, Consumer super T> onDropped) {
super(source);
this.period = period;
this.unit = unit;
this.scheduler = scheduler;
this.emitLast = emitLast;
+ this.onDropped = onDropped;
}
@Override
protected void subscribeActual(Subscriber super T> s) {
SerializedSubscriber serial = new SerializedSubscriber<>(s);
if (emitLast) {
- source.subscribe(new SampleTimedEmitLast<>(serial, period, unit, scheduler));
+ source.subscribe(new SampleTimedEmitLast<>(serial, period, unit, scheduler, onDropped));
} else {
- source.subscribe(new SampleTimedNoLast<>(serial, period, unit, scheduler));
+ source.subscribe(new SampleTimedNoLast<>(serial, period, unit, scheduler, onDropped));
}
}
@@ -58,6 +61,7 @@ abstract static class SampleTimedSubscriber extends AtomicReference implem
final long period;
final TimeUnit unit;
final Scheduler scheduler;
+ final Consumer super T> onDropped;
final AtomicLong requested = new AtomicLong();
@@ -65,11 +69,12 @@ abstract static class SampleTimedSubscriber extends AtomicReference implem
Subscription upstream;
- SampleTimedSubscriber(Subscriber super T> actual, long period, TimeUnit unit, Scheduler scheduler) {
+ SampleTimedSubscriber(Subscriber super T> actual, long period, TimeUnit unit, Scheduler scheduler, Consumer super T> onDropped) {
this.downstream = actual;
this.period = period;
this.unit = unit;
this.scheduler = scheduler;
+ this.onDropped = onDropped;
}
@Override
@@ -84,7 +89,17 @@ public void onSubscribe(Subscription s) {
@Override
public void onNext(T t) {
- lazySet(t);
+ T oldValue = getAndSet(t);
+ if (oldValue != null && onDropped != null) {
+ try {
+ onDropped.accept(oldValue);
+ } catch (Throwable throwable) {
+ Exceptions.throwIfFatal(throwable);
+ cancelTimer();
+ upstream.cancel();
+ downstream.onError(throwable);
+ }
+ }
}
@Override
@@ -125,7 +140,7 @@ void emit() {
BackpressureHelper.produced(requested, 1);
} else {
cancel();
- downstream.onError(new MissingBackpressureException("Couldn't emit value due to lack of requests!"));
+ downstream.onError(MissingBackpressureException.createDefault());
}
}
}
@@ -137,8 +152,8 @@ static final class SampleTimedNoLast extends SampleTimedSubscriber {
private static final long serialVersionUID = -7139995637533111443L;
- SampleTimedNoLast(Subscriber super T> actual, long period, TimeUnit unit, Scheduler scheduler) {
- super(actual, period, unit, scheduler);
+ SampleTimedNoLast(Subscriber super T> actual, long period, TimeUnit unit, Scheduler scheduler, Consumer super T> onDropped) {
+ super(actual, period, unit, scheduler, onDropped);
}
@Override
@@ -158,8 +173,8 @@ static final class SampleTimedEmitLast extends SampleTimedSubscriber {
final AtomicInteger wip;
- SampleTimedEmitLast(Subscriber super T> actual, long period, TimeUnit unit, Scheduler scheduler) {
- super(actual, period, unit, scheduler);
+ SampleTimedEmitLast(Subscriber super T> actual, long period, TimeUnit unit, Scheduler scheduler, Consumer super T> onDropped) {
+ super(actual, period, unit, scheduler, onDropped);
this.wip = new AtomicInteger(1);
}
diff --git a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableSequenceEqual.java b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableSequenceEqual.java
index 0ba38698d6..8dadbe0161 100644
--- a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableSequenceEqual.java
+++ b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableSequenceEqual.java
@@ -300,7 +300,7 @@ public void onSubscribe(Subscription s) {
public void onNext(T t) {
if (sourceMode == QueueSubscription.NONE) {
if (!queue.offer(t)) {
- onError(new MissingBackpressureException());
+ onError(MissingBackpressureException.createDefault());
return;
}
}
diff --git a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableSwitchMap.java b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableSwitchMap.java
index 1c820570dc..4d4521c655 100644
--- a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableSwitchMap.java
+++ b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableSwitchMap.java
@@ -381,7 +381,7 @@ public void onNext(R t) {
SwitchMapSubscriber p = parent;
if (index == p.unique) {
if (fusionMode == QueueSubscription.NONE && !queue.offer(t)) {
- onError(new MissingBackpressureException("Queue full?!"));
+ onError(new QueueOverflowException());
return;
}
p.drain();
diff --git a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableThrottleFirstTimed.java b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableThrottleFirstTimed.java
index c12399e1d1..44499ec255 100644
--- a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableThrottleFirstTimed.java
+++ b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableThrottleFirstTimed.java
@@ -16,6 +16,8 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
+import io.reactivex.rxjava3.exceptions.Exceptions;
+import io.reactivex.rxjava3.functions.Consumer;
import org.reactivestreams.*;
import io.reactivex.rxjava3.core.*;
@@ -32,44 +34,53 @@ public final class FlowableThrottleFirstTimed extends AbstractFlowableWithUps
final long timeout;
final TimeUnit unit;
final Scheduler scheduler;
+ final Consumer super T> onDropped;
- public FlowableThrottleFirstTimed(Flowable source, long timeout, TimeUnit unit, Scheduler scheduler) {
+ public FlowableThrottleFirstTimed(Flowable source,
+ long timeout,
+ TimeUnit unit,
+ Scheduler scheduler,
+ Consumer super T> onDropped) {
super(source);
this.timeout = timeout;
this.unit = unit;
this.scheduler = scheduler;
+ this.onDropped = onDropped;
}
@Override
protected void subscribeActual(Subscriber super T> s) {
source.subscribe(new DebounceTimedSubscriber<>(
new SerializedSubscriber<>(s),
- timeout, unit, scheduler.createWorker()));
+ timeout, unit, scheduler.createWorker(),
+ onDropped));
}
static final class DebounceTimedSubscriber
extends AtomicLong
implements FlowableSubscriber, Subscription, Runnable {
-
private static final long serialVersionUID = -9102637559663639004L;
+
final Subscriber super T> downstream;
final long timeout;
final TimeUnit unit;
final Scheduler.Worker worker;
-
+ final Consumer super T> onDropped;
Subscription upstream;
-
final SequentialDisposable timer = new SequentialDisposable();
-
volatile boolean gate;
-
boolean done;
- DebounceTimedSubscriber(Subscriber super T> actual, long timeout, TimeUnit unit, Worker worker) {
+ DebounceTimedSubscriber(Subscriber super T> actual,
+ long timeout,
+ TimeUnit unit,
+ Worker worker,
+ Consumer super T> onDropped) {
this.downstream = actual;
this.timeout = timeout;
this.unit = unit;
this.worker = worker;
+ this.onDropped = onDropped;
}
@Override
@@ -94,9 +105,10 @@ public void onNext(T t) {
downstream.onNext(t);
BackpressureHelper.produced(this, 1);
} else {
+ upstream.cancel();
done = true;
- cancel();
- downstream.onError(new MissingBackpressureException("Could not deliver value due to lack of requests"));
+ downstream.onError(MissingBackpressureException.createDefault());
+ worker.dispose();
return;
}
@@ -106,6 +118,16 @@ public void onNext(T t) {
}
timer.replace(worker.schedule(this, timeout, unit));
+ } else if (onDropped != null) {
+ try {
+ onDropped.accept(t);
+ } catch (Throwable ex) {
+ Exceptions.throwIfFatal(ex);
+ upstream.cancel();
+ done = true;
+ downstream.onError(ex);
+ worker.dispose();
+ }
}
}
diff --git a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableThrottleLatest.java b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableThrottleLatest.java
index e6cb5ffdcc..e28e5f09df 100644
--- a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableThrottleLatest.java
+++ b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableThrottleLatest.java
@@ -19,9 +19,11 @@
import org.reactivestreams.*;
import io.reactivex.rxjava3.core.*;
-import io.reactivex.rxjava3.exceptions.MissingBackpressureException;
+import io.reactivex.rxjava3.exceptions.*;
+import io.reactivex.rxjava3.functions.Consumer;
import io.reactivex.rxjava3.internal.subscriptions.SubscriptionHelper;
import io.reactivex.rxjava3.internal.util.BackpressureHelper;
+import io.reactivex.rxjava3.plugins.RxJavaPlugins;
/**
* Emits the next or latest item when the given time elapses.
@@ -44,19 +46,24 @@ public final class FlowableThrottleLatest extends AbstractFlowableWithUpstrea
final boolean emitLast;
+ final Consumer super T> onDropped;
+
public FlowableThrottleLatest(Flowable source,
- long timeout, TimeUnit unit, Scheduler scheduler,
- boolean emitLast) {
+ long timeout, TimeUnit unit,
+ Scheduler scheduler,
+ boolean emitLast,
+ Consumer super T> onDropped) {
super(source);
this.timeout = timeout;
this.unit = unit;
this.scheduler = scheduler;
this.emitLast = emitLast;
+ this.onDropped = onDropped;
}
@Override
protected void subscribeActual(Subscriber super T> s) {
- source.subscribe(new ThrottleLatestSubscriber<>(s, timeout, unit, scheduler.createWorker(), emitLast));
+ source.subscribe(new ThrottleLatestSubscriber<>(s, timeout, unit, scheduler.createWorker(), emitLast, onDropped));
}
static final class ThrottleLatestSubscriber
@@ -79,6 +86,8 @@ static final class ThrottleLatestSubscriber
final AtomicLong requested;
+ final Consumer super T> onDropped;
+
Subscription upstream;
volatile boolean done;
@@ -93,8 +102,10 @@ static final class ThrottleLatestSubscriber
boolean timerRunning;
ThrottleLatestSubscriber(Subscriber super T> downstream,
- long timeout, TimeUnit unit, Scheduler.Worker worker,
- boolean emitLast) {
+ long timeout, TimeUnit unit,
+ Scheduler.Worker worker,
+ boolean emitLast,
+ Consumer super T> onDropped) {
this.downstream = downstream;
this.timeout = timeout;
this.unit = unit;
@@ -102,6 +113,7 @@ static final class ThrottleLatestSubscriber
this.emitLast = emitLast;
this.latest = new AtomicReference<>();
this.requested = new AtomicLong();
+ this.onDropped = onDropped;
}
@Override
@@ -115,7 +127,17 @@ public void onSubscribe(Subscription s) {
@Override
public void onNext(T t) {
- latest.set(t);
+ T old = latest.getAndSet(t);
+ if (onDropped != null && old != null) {
+ try {
+ onDropped.accept(old);
+ } catch (Throwable ex) {
+ Exceptions.throwIfFatal(ex);
+ upstream.cancel();
+ error = ex;
+ done = true;
+ }
+ }
drain();
}
@@ -145,6 +167,22 @@ public void cancel() {
upstream.cancel();
worker.dispose();
if (getAndIncrement() == 0) {
+ clear();
+ }
+ }
+
+ void clear() {
+ if (onDropped != null) {
+ T v = latest.getAndSet(null);
+ if (v != null) {
+ try {
+ onDropped.accept(v);
+ } catch (Throwable ex) {
+ Exceptions.throwIfFatal(ex);
+ RxJavaPlugins.onError(ex);
+ }
+ }
+ } else {
latest.lazySet(null);
}
}
@@ -170,14 +208,27 @@ void drain() {
for (;;) {
if (cancelled) {
- latest.lazySet(null);
+ clear();
return;
}
boolean d = done;
+ Throwable error = this.error;
if (d && error != null) {
- latest.lazySet(null);
+ if (onDropped != null) {
+ T v = latest.getAndSet(null);
+ if (v != null) {
+ try {
+ onDropped.accept(v);
+ } catch (Throwable ex) {
+ Exceptions.throwIfFatal(ex);
+ error = new CompositeException(error, ex);
+ }
+ }
+ } else {
+ latest.lazySet(null);
+ }
downstream.onError(error);
worker.dispose();
return;
@@ -187,19 +238,31 @@ void drain() {
boolean empty = v == null;
if (d) {
- if (!empty && emitLast) {
+ if (!empty) {
v = latest.getAndSet(null);
- long e = emitted;
- if (e != requested.get()) {
- emitted = e + 1;
- downstream.onNext(v);
- downstream.onComplete();
+ if (emitLast) {
+ long e = emitted;
+ if (e != requested.get()) {
+ emitted = e + 1;
+ downstream.onNext(v);
+ downstream.onComplete();
+ } else {
+ tryDropAndSignalMBE(v);
+ }
} else {
- downstream.onError(new MissingBackpressureException(
- "Could not emit final value due to lack of requests"));
+ if (onDropped != null) {
+ try {
+ onDropped.accept(v);
+ } catch (Throwable ex) {
+ Exceptions.throwIfFatal(ex);
+ downstream.onError(ex);
+ worker.dispose();
+ return;
+ }
+ }
+ downstream.onComplete();
}
} else {
- latest.lazySet(null);
downstream.onComplete();
}
worker.dispose();
@@ -222,8 +285,7 @@ void drain() {
emitted = e + 1;
} else {
upstream.cancel();
- downstream.onError(new MissingBackpressureException(
- "Could not emit value due to lack of requests"));
+ tryDropAndSignalMBE(v);
worker.dispose();
return;
}
@@ -242,5 +304,18 @@ void drain() {
}
}
}
+
+ void tryDropAndSignalMBE(T valueToDrop) {
+ Throwable errorToSignal = MissingBackpressureException.createDefault();
+ if (onDropped != null) {
+ try {
+ onDropped.accept(valueToDrop);
+ } catch (Throwable ex) {
+ Exceptions.throwIfFatal(ex);
+ errorToSignal = new CompositeException(errorToSignal, ex);
+ }
+ }
+ downstream.onError(errorToSignal);
+ }
}
}
diff --git a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableTimer.java b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableTimer.java
index dd33daea0f..db45fe2e98 100644
--- a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableTimer.java
+++ b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableTimer.java
@@ -78,7 +78,7 @@ public void run() {
downstream.onComplete();
} else {
lazySet(EmptyDisposable.INSTANCE);
- downstream.onError(new MissingBackpressureException("Can't deliver value due to lack of requests"));
+ downstream.onError(MissingBackpressureException.createDefault());
}
}
}
diff --git a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableToList.java b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableToList.java
index 2b7c19e269..3f03d93112 100644
--- a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableToList.java
+++ b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableToList.java
@@ -46,7 +46,7 @@ protected void subscribeActual(Subscriber super U> s) {
static final class ToListSubscriber>
extends DeferredScalarSubscription
- implements FlowableSubscriber, Subscription {
+ implements FlowableSubscriber {
private static final long serialVersionUID = -8134157938864266736L;
Subscription upstream;
diff --git a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableWindowBoundary.java b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableWindowBoundary.java
index 1dcfd4e529..2aad69dcb5 100644
--- a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableWindowBoundary.java
+++ b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableWindowBoundary.java
@@ -248,7 +248,7 @@ void drain() {
} else {
SubscriptionHelper.cancel(upstream);
boundarySubscriber.dispose();
- errors.tryAddThrowableOrReport(new MissingBackpressureException("Could not deliver a window due to lack of requests"));
+ errors.tryAddThrowableOrReport(MissingBackpressureException.createDefault());
done = true;
}
}
diff --git a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableWindowBoundarySelector.java b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableWindowBoundarySelector.java
index 394315cd9b..d6bffae2e1 100644
--- a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableWindowBoundarySelector.java
+++ b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableWindowBoundarySelector.java
@@ -272,7 +272,7 @@ void drain() {
upstream.cancel();
startSubscriber.cancel();
resources.dispose();
- error.tryAddThrowableOrReport(new MissingBackpressureException(FlowableWindowTimed.missingBackpressureMessage(emitted)));
+ error.tryAddThrowableOrReport(FlowableWindowTimed.missingBackpressureMessage(emitted));
upstreamDone = true;
}
}
diff --git a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableWindowTimed.java b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableWindowTimed.java
index 23ef916fef..39e7de59ad 100644
--- a/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableWindowTimed.java
+++ b/src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableWindowTimed.java
@@ -213,7 +213,7 @@ void createFirstWindow() {
upstream.request(Long.MAX_VALUE);
} else {
upstream.cancel();
- downstream.onError(new MissingBackpressureException(missingBackpressureMessage(emitted)));
+ downstream.onError(missingBackpressureMessage(emitted));
cleanupResources();
upstreamCancelled = true;
@@ -282,7 +282,7 @@ else if (!isEmpty) {
cleanupResources();
upstreamCancelled = true;
- downstream.onError(new MissingBackpressureException(missingBackpressureMessage(emitted)));
+ downstream.onError(missingBackpressureMessage(emitted));
} else {
emitted++;
@@ -386,7 +386,7 @@ void createFirstWindow() {
upstream.request(Long.MAX_VALUE);
} else {
upstream.cancel();
- downstream.onError(new MissingBackpressureException(missingBackpressureMessage(emitted)));
+ downstream.onError(missingBackpressureMessage(emitted));
cleanupResources();
upstreamCancelled = true;
@@ -499,7 +499,7 @@ UnicastProcessor createNewWindow(UnicastProcessor window) {
cleanupResources();
upstreamCancelled = true;
- downstream.onError(new MissingBackpressureException(missingBackpressureMessage(emitted)));
+ downstream.onError(missingBackpressureMessage(emitted));
} else {
this.emitted = ++emitted;
@@ -584,7 +584,7 @@ void createFirstWindow() {
upstream.request(Long.MAX_VALUE);
} else {
upstream.cancel();
- downstream.onError(new MissingBackpressureException(missingBackpressureMessage(emitted)));
+ downstream.onError(missingBackpressureMessage(emitted));
cleanupResources();
upstreamCancelled = true;
@@ -654,7 +654,7 @@ void drain() {
}
} else {
upstream.cancel();
- Throwable ex = new MissingBackpressureException(missingBackpressureMessage(emitted));
+ Throwable ex = missingBackpressureMessage(emitted);
for (UnicastProcessor window : windows) {
window.onError(ex);
}
@@ -717,8 +717,8 @@ public void run() {
}
}
- static String missingBackpressureMessage(long index) {
- return "Unable to emit the next window (#" + index + ") due to lack of requests. Please make sure the downstream is ready to consume windows.";
+ static MissingBackpressureException missingBackpressureMessage(long index) {
+ return new MissingBackpressureException("Unable to emit the next window (#" + index + ") due to lack of requests. Please make sure the downstream is ready to consume windows.");
}
}
diff --git a/src/main/java/io/reactivex/rxjava3/internal/operators/mixed/ConcatMapXMainSubscriber.java b/src/main/java/io/reactivex/rxjava3/internal/operators/mixed/ConcatMapXMainSubscriber.java
index bd701de40c..03e4c568d6 100644
--- a/src/main/java/io/reactivex/rxjava3/internal/operators/mixed/ConcatMapXMainSubscriber.java
+++ b/src/main/java/io/reactivex/rxjava3/internal/operators/mixed/ConcatMapXMainSubscriber.java
@@ -18,7 +18,7 @@
import org.reactivestreams.Subscription;
import io.reactivex.rxjava3.core.FlowableSubscriber;
-import io.reactivex.rxjava3.exceptions.MissingBackpressureException;
+import io.reactivex.rxjava3.exceptions.*;
import io.reactivex.rxjava3.internal.subscriptions.SubscriptionHelper;
import io.reactivex.rxjava3.internal.util.*;
import io.reactivex.rxjava3.operators.QueueFuseable;
@@ -99,7 +99,7 @@ public final void onNext(T t) {
if (t != null) {
if (!queue.offer(t)) {
upstream.cancel();
- onError(new MissingBackpressureException("queue full?!"));
+ onError(new QueueOverflowException());
return;
}
}
diff --git a/src/main/java/io/reactivex/rxjava3/internal/operators/observable/ObservableBufferExactBoundary.java b/src/main/java/io/reactivex/rxjava3/internal/operators/observable/ObservableBufferExactBoundary.java
index 5892e092b9..bb65058908 100644
--- a/src/main/java/io/reactivex/rxjava3/internal/operators/observable/ObservableBufferExactBoundary.java
+++ b/src/main/java/io/reactivex/rxjava3/internal/operators/observable/ObservableBufferExactBoundary.java
@@ -43,7 +43,7 @@ protected void subscribeActual(Observer super U> t) {
}
static final class BufferExactBoundaryObserver, B>
- extends QueueDrainObserver implements Observer, Disposable {
+ extends QueueDrainObserver implements Disposable {
final Supplier bufferSupplier;
final ObservableSource boundary;
diff --git a/src/main/java/io/reactivex/rxjava3/internal/operators/observable/ObservableDebounceTimed.java b/src/main/java/io/reactivex/rxjava3/internal/operators/observable/ObservableDebounceTimed.java
index 248d00ea23..f2db191229 100644
--- a/src/main/java/io/reactivex/rxjava3/internal/operators/observable/ObservableDebounceTimed.java
+++ b/src/main/java/io/reactivex/rxjava3/internal/operators/observable/ObservableDebounceTimed.java
@@ -19,6 +19,8 @@
import io.reactivex.rxjava3.core.*;
import io.reactivex.rxjava3.core.Scheduler.Worker;
import io.reactivex.rxjava3.disposables.Disposable;
+import io.reactivex.rxjava3.exceptions.Exceptions;
+import io.reactivex.rxjava3.functions.Consumer;
import io.reactivex.rxjava3.internal.disposables.DisposableHelper;
import io.reactivex.rxjava3.observers.SerializedObserver;
import io.reactivex.rxjava3.plugins.RxJavaPlugins;
@@ -27,19 +29,20 @@ public final class ObservableDebounceTimed extends AbstractObservableWithUpst
final long timeout;
final TimeUnit unit;
final Scheduler scheduler;
+ final Consumer super T> onDropped;
- public ObservableDebounceTimed(ObservableSource source, long timeout, TimeUnit unit, Scheduler scheduler) {
+ public ObservableDebounceTimed(ObservableSource source, long timeout, TimeUnit unit, Scheduler scheduler, Consumer super T> onDropped) {
super(source);
this.timeout = timeout;
this.unit = unit;
this.scheduler = scheduler;
+ this.onDropped = onDropped;
}
@Override
public void subscribeActual(Observer super T> t) {
source.subscribe(new DebounceTimedObserver<>(
- new SerializedObserver<>(t),
- timeout, unit, scheduler.createWorker()));
+ new SerializedObserver<>(t), timeout, unit, scheduler.createWorker(), onDropped));
}
static final class DebounceTimedObserver
@@ -48,20 +51,22 @@ static final class DebounceTimedObserver
final long timeout;
final TimeUnit unit;
final Scheduler.Worker worker;
+ final Consumer super T> onDropped;
Disposable upstream;
- Disposable timer;
+ DebounceEmitter timer;
volatile long index;
boolean done;
- DebounceTimedObserver(Observer super T> actual, long timeout, TimeUnit unit, Worker worker) {
+ DebounceTimedObserver(Observer super T> actual, long timeout, TimeUnit unit, Worker worker, Consumer super T> onDropped) {
this.downstream = actual;
this.timeout = timeout;
this.unit = unit;
this.worker = worker;
+ this.onDropped = onDropped;
}
@Override
@@ -80,15 +85,25 @@ public void onNext(T t) {
long idx = index + 1;
index = idx;
- Disposable d = timer;
- if (d != null) {
- d.dispose();
+ DebounceEmitter currentEmitter = timer;
+ if (currentEmitter != null) {
+ currentEmitter.dispose();
}
- DebounceEmitter de = new DebounceEmitter<>(t, idx, this);
- timer = de;
- d = worker.schedule(de, timeout, unit);
- de.setResource(d);
+ if (onDropped != null && currentEmitter != null) {
+ try {
+ onDropped.accept(timer.value);
+ } catch (Throwable ex) {
+ Exceptions.throwIfFatal(ex);
+ upstream.dispose();
+ downstream.onError(ex);
+ done = true;
+ }
+ }
+
+ DebounceEmitter newEmitter = new DebounceEmitter<>(t, idx, this);
+ timer = newEmitter;
+ newEmitter.setResource(worker.schedule(newEmitter, timeout, unit));
}
@Override
@@ -113,15 +128,13 @@ public void onComplete() {
}
done = true;
- Disposable d = timer;
+ DebounceEmitter d = timer;
if (d != null) {
d.dispose();
}
- @SuppressWarnings("unchecked")
- DebounceEmitter de = (DebounceEmitter)d;
- if (de != null) {
- de.run();
+ if (d != null) {
+ d.run();
}
downstream.onComplete();
worker.dispose();
diff --git a/src/main/java/io/reactivex/rxjava3/internal/operators/observable/ObservableDelay.java b/src/main/java/io/reactivex/rxjava3/internal/operators/observable/ObservableDelay.java
index 1801cce1f2..7c01c23f90 100644
--- a/src/main/java/io/reactivex/rxjava3/internal/operators/observable/ObservableDelay.java
+++ b/src/main/java/io/reactivex/rxjava3/internal/operators/observable/ObservableDelay.java
@@ -111,7 +111,9 @@ final class OnNext implements Runnable {
@Override
public void run() {
- downstream.onNext(t);
+ if (!w.isDisposed()) {
+ downstream.onNext(t);
+ }
}
}
diff --git a/src/main/java/io/reactivex/rxjava3/internal/operators/observable/ObservableSampleTimed.java b/src/main/java/io/reactivex/rxjava3/internal/operators/observable/ObservableSampleTimed.java
index f439339e15..f264b8e76d 100644
--- a/src/main/java/io/reactivex/rxjava3/internal/operators/observable/ObservableSampleTimed.java
+++ b/src/main/java/io/reactivex/rxjava3/internal/operators/observable/ObservableSampleTimed.java
@@ -18,6 +18,8 @@
import io.reactivex.rxjava3.core.*;
import io.reactivex.rxjava3.disposables.Disposable;
+import io.reactivex.rxjava3.exceptions.Exceptions;
+import io.reactivex.rxjava3.functions.Consumer;
import io.reactivex.rxjava3.internal.disposables.DisposableHelper;
import io.reactivex.rxjava3.observers.SerializedObserver;
@@ -25,24 +27,30 @@ public final class ObservableSampleTimed extends AbstractObservableWithUpstre
final long period;
final TimeUnit unit;
final Scheduler scheduler;
-
+ final Consumer super T> onDropped;
final boolean emitLast;
- public ObservableSampleTimed(ObservableSource