Skip to content

Commit 79a67f1

Browse files
authored
4.x: Streamable: +delay +timeout +zip +onErrorResumeNext, cleanup (#8216)
* 4.x: Streamable: +delay +timeout +zip +onErrorResumeNext, cleanup * Try fixing StreamableGroupByTest.groupDisposeTest * Disable flaky test
1 parent 580b39c commit 79a67f1

27 files changed

Lines changed: 1511 additions & 97 deletions
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright (c) 2016-present, RxJava Contributors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
5+
* compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is
10+
* distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
11+
* the License for the specific language governing permissions and limitations under the License.
12+
*/
13+
14+
package io.reactivex.rxjava4.core;
15+
16+
import java.util.concurrent.CompletionStage;
17+
import java.util.concurrent.Flow.Processor;
18+
19+
import io.reactivex.rxjava4.annotations.NonNull;
20+
21+
/**
22+
* A {@link Processor}-like interface combining the {@code Streamable} interface and the
23+
* {@link StreamerInput} interface to establish a push-pull bridge based on {@link CompletionStage}-based
24+
* asynchronous processing and dispatching of values and errors.
25+
* @param <In> the element type of the input side
26+
* @param <Out> the element type of the output side
27+
* @since 4.0.0
28+
*/
29+
public interface StreamProcessor<@NonNull In, @NonNull Out> extends Streamable<Out>, StreamerInput<In> {
30+
31+
}

src/main/java/io/reactivex/rxjava4/core/Streamable.java

Lines changed: 90 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import java.util.*;
1717
import java.util.concurrent.*;
1818
import java.util.concurrent.atomic.AtomicLong;
19-
import java.util.stream.Stream;
19+
import java.util.stream.*;
2020

2121
import io.reactivex.rxjava4.annotations.*;
2222
import io.reactivex.rxjava4.core.config.*;
@@ -539,10 +539,56 @@ static Streamable<Long> timer(long delay, TimeUnit unit, ExecutorService executo
539539
return RxJavaPlugins.onAssembly(new StreamableTimer(delay, unit, null, executor));
540540
}
541541

542+
/**
543+
* Takes the next element from each source {@code Streamable} and emits them a a single
544+
* row of {@link List}.
545+
* <p>
546+
* If any of the sources is shorter than the rest or any of them fails, the
547+
* sequence is completed early normally or with an exception, respectively.
548+
* @param <T> the common element type of the sequences
549+
* @param sources the iterable sequence of the source {@code Streamable}s
550+
* @return the new {@code Streamable} instance
551+
* @throws NullPointerException if {@code sources} is {@&ode null}
552+
*/
553+
static <T> Streamable<List<T>> zip(Iterable<? extends Streamable<? extends T>> sources) {
554+
Objects.requireNonNull(sources, "sources is null");
555+
return RxJavaPlugins.onAssembly(new StreamableZip<>(sources));
556+
}
557+
542558
// oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
543559
// Operators
544560
// oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
545561

562+
/**
563+
* Collects all upstream values via the use of a {@link Collector} configuration
564+
* and emits its resulting value as a single item of the returned {@code Streamable}.
565+
* <p>
566+
* See {@link Collectors} for the most typical collector standard implementations.
567+
* @param <A> the accumulator type of the collector
568+
* @param <R> the result type of the collector and the returned {@code Streamamble}
569+
* @param collector the Java collector instance to use
570+
* @return the new {@code Streamable} instance
571+
* @throws NullPointerException if {@code collector} is {@code null}
572+
*/
573+
default <A, R> Streamable<R> collect(Collector<T, A, R> collector) {
574+
Objects.requireNonNull(collector, "collector is null");
575+
return RxJavaPlugins.onAssembly(new StreamableCollector<>(this, collector));
576+
}
577+
578+
/**
579+
* Delays the delivery of each upstream item by the given time amount.
580+
* @param time the delay time
581+
* @param unit the time unit
582+
* @param scheduler the scheduler where the timed wait is happening
583+
* @return the new {@code Streamable} instance
584+
* @throws NullPointerException if {@code unit} or {@code scheduler} is {@code null}
585+
*/
586+
default Streamable<T> delay(long time, TimeUnit unit, Scheduler scheduler) {
587+
Objects.requireNonNull(unit, "unit is null");
588+
Objects.requireNonNull(scheduler, "scheduler is null");
589+
return RxJavaPlugins.onAssembly(new StreamableDelay<>(this, time, unit, scheduler));
590+
}
591+
546592
/**
547593
* Calls the given consumer whenever an upstream item becomes available.
548594
* @param consumer the callback to invoke with the next item from upstream
@@ -667,6 +713,19 @@ default Streamable<T> intercept(StreamableInterceptConfig<T> config) {
667713
return RxJavaPlugins.onAssembly(new StreamableMapOptional<>(this, mapper));
668714
}
669715

716+
/**
717+
* When the upstream fails, the sequence is resumed by the {@code Streamable} that is returned for the
718+
* failure {@link Throwable}.
719+
* @param fallbackMapper the function that receives the upstream error and should return a
720+
* {@code Streamable} to resume the sequence with
721+
* @return the new {@code Streamable} instance
722+
* @throws NullPointerException if {@code fallbackMapper} is {@code null}
723+
*/
724+
default Streamable<T> onErrorResumeNext(Function<? super Throwable, ? extends Streamable<? extends T>> fallbackMapper) {
725+
Objects.requireNonNull(fallbackMapper, "fallbackMapper is null");
726+
return RxJavaPlugins.onAssembly(new StreamableOnErrorResumeNext<>(this, fallbackMapper));
727+
}
728+
670729
/**
671730
* Takes at most the given number of items from the upstream and relays it to the downstream,
672731
* then cancels the rest of the sequence.
@@ -689,6 +748,21 @@ default Streamable<T> take(long n) {
689748
});
690749
}
691750

751+
/**
752+
* Relays items from this {@code Streamable} until the other {@code Streamable} signals
753+
* an item or completes.
754+
* @param <U> the element type of the other {@code Streamable}
755+
* @param other the {@code Streamable} expected to signal when to stop taking items from this {@code Streamable}
756+
* @return the new {@code Streamable} instance
757+
* @throws NullPointerException if {@code other} is {@code null}
758+
*/
759+
@CheckReturnValue
760+
@NonNull
761+
default <U> Streamable<T> takeUntil(@NonNull Streamable<U> other) {
762+
Objects.requireNonNull(other, "other is null");
763+
return RxJavaPlugins.onAssembly(new StreamableTakeUntil<>(this, other));
764+
}
765+
692766
/**
693767
* Relays items from this {@code Streamable} while the predicate returns {@code true}
694768
* @param predicate the predicate to test if the sequence should keep going
@@ -703,18 +777,21 @@ default Streamable<T> takeWhile(@NonNull Predicate<? super T> predicate) {
703777
}
704778

705779
/**
706-
* Relays items from this {@code Streamable} until the other {@code Streamable} signals
707-
* an item or completes.
708-
* @param <U> the element type of the other {@code Streamable}
709-
* @param other the {@code Streamable} expected to signal when to stop taking items from this {@code Streamable}
780+
* Applies a timeout to each upstream item and switches to the fallback {@code Streamable}
781+
* if the upstream doesn't produce an item within the given timeout period.
782+
* @param timeout the time to wait for each upstream item
783+
* @param unit the time unit
784+
* @param scheduler the scheduler where to wait for the next upstream item
785+
* @param fallback the {@code Streamable} to switch to if the upstream doesn't produce an item
786+
* within the time limit
710787
* @return the new {@code Streamable} instance
711-
* @throws NullPointerException if {@code other} is {@code null}
788+
* @throws NullPointerException if {@code unit} or {@code scheduler} or {@code fallback} is {@code null}
712789
*/
713-
@CheckReturnValue
714-
@NonNull
715-
default <U> Streamable<T> takeUntil(@NonNull Streamable<U> other) {
716-
Objects.requireNonNull(other, "other is null");
717-
return RxJavaPlugins.onAssembly(new StreamableTakeUntil<>(this, other));
790+
default Streamable<T> timeout(long timeout, TimeUnit unit, Scheduler scheduler, Streamable<T> fallback) {
791+
Objects.requireNonNull(unit, "unit is null");
792+
Objects.requireNonNull(scheduler, "scheduler is null");
793+
Objects.requireNonNull(fallback, "fallback is null");
794+
return RxJavaPlugins.onAssembly(new StreamableTimeout<>(this, timeout, unit, scheduler, fallback));
718795
}
719796

720797
/**
@@ -891,8 +968,8 @@ default CompletionStageDisposable<Void> forEach(
891968
default void subscribe(@NonNull Flow.Subscriber<? super T> subscriber, @NonNull ExecutorService executor) {
892969
final Streamable<T> me = this;
893970
Flowable.<T>virtualCreate(emitter -> {
894-
me.forEach(emitter::emit, emitter.canceller().derive(), executor)
895-
.await();
971+
var cf = me.forEach(emitter::emit, emitter.canceller().derive(), executor);
972+
cf.await();
896973
}, executor)
897974
.subscribe(subscriber);
898975
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright (c) 2016-present, RxJava Contributors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
5+
* compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is
10+
* distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
11+
* the License for the specific language governing permissions and limitations under the License.
12+
*/
13+
14+
package io.reactivex.rxjava4.core;
15+
16+
import java.util.concurrent.CompletionStage;
17+
import java.util.concurrent.Flow.Subscriber;
18+
19+
import io.reactivex.rxjava4.annotations.*;
20+
21+
/**
22+
* An interface to submit items and terminal events to a consumer that indacates when the processing of
23+
* said item or terminal event has completed, similar to how {@link Subscriber} can receive events.
24+
* <p>
25+
* The general contract is to call {@link #next(Object)} zero or more times, then
26+
* call {@link #finish(Throwable)} at most once, all in a non-overlapping fashion and only if the
27+
* returned {@link CompletionStage} has completed in some fashion.
28+
* @param <T> the item type to be offered
29+
* @since 4.0.0
30+
*/
31+
public interface StreamerInput<@NonNull T> {
32+
33+
/**
34+
* Offer the next item.
35+
* @param item the item being offered
36+
* @return a {@link CompletionStage} that should complete with {@code true} if the
37+
* item was accepted, or {@code false} if the item could not be accepted at this time,
38+
* or via a {@code Throwable} indicating some error.
39+
*/
40+
CompletionStage<Boolean> next(T item);
41+
42+
/**
43+
* Offer the final, terminal event.
44+
* @param throwable the optional throwable to signal error, null to signal normal completion
45+
* @return the {@link CompletionStage} that should complete normally if the terminal event was accepted,
46+
* or via a {@code Throwable} indicating an error
47+
*/
48+
CompletionStage<Void> finish(@Nullable Throwable throwable);
49+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/*
2+
* Copyright (c) 2016-present, RxJava Contributors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
5+
* compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is
10+
* distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
11+
* the License for the specific language governing permissions and limitations under the License.
12+
*/
13+
14+
package io.reactivex.rxjava4.internal.operators.streamable;
15+
16+
import java.util.concurrent.*;
17+
import java.util.concurrent.atomic.AtomicInteger;
18+
import java.util.function.*;
19+
import java.util.stream.Collector;
20+
21+
import io.reactivex.rxjava4.annotations.NonNull;
22+
import io.reactivex.rxjava4.core.*;
23+
import io.reactivex.rxjava4.disposables.DisposableContainer;
24+
import io.reactivex.rxjava4.internal.fuseable.HasUpstreamStreamableSource;
25+
26+
public record StreamableCollector<T, A, R>(
27+
Streamable<T> source,
28+
Collector<T, A, R> collector
29+
) implements Streamable<R>, HasUpstreamStreamableSource<T> {
30+
31+
@Override
32+
public @NonNull Streamer<@NonNull R> stream(@NonNull DisposableContainer cancellation) {
33+
return new CollectorStreamable<>(
34+
source.stream(cancellation),
35+
collector.supplier().get(),
36+
collector.accumulator(),
37+
collector.finisher());
38+
}
39+
40+
static final class CollectorStreamable<T, A, R>
41+
implements Streamer<R>, BiConsumer<Object, Throwable> {
42+
43+
final AtomicInteger wip;
44+
45+
final Streamer<T> upstream;
46+
47+
final A storage;
48+
49+
final BiConsumer<A, T> accumulator;
50+
51+
final Function<A, R> finisher;
52+
53+
final CompletableFuture<Boolean> nextReady;
54+
55+
final CompletableFuture<Void> finishReady;
56+
57+
R current;
58+
59+
int stage;
60+
61+
volatile boolean done;
62+
63+
CollectorStreamable(Streamer<T> upstream, A storage, BiConsumer<A, T> accumulator, Function<A, R> finisher) {
64+
this.upstream = upstream;
65+
this.wip = new AtomicInteger();
66+
this.storage = storage;
67+
this.accumulator = accumulator;
68+
this.finisher = finisher;
69+
this.nextReady = new CompletableFuture<>();
70+
this.finishReady = new CompletableFuture<>();
71+
}
72+
73+
@Override
74+
public @NonNull CompletionStage<Boolean> next() {
75+
if (stage++ == 0) {
76+
drain();
77+
return nextReady;
78+
}
79+
return NEXT_FALSE;
80+
}
81+
82+
@Override
83+
public @NonNull R current() {
84+
return current;
85+
}
86+
87+
@Override
88+
public @NonNull CompletionStage<Void> finish() {
89+
done = true;
90+
drain();
91+
return finishReady;
92+
}
93+
94+
void drain() {
95+
if (wip.getAndIncrement() != 0) {
96+
return;
97+
}
98+
99+
do {
100+
if (done) {
101+
upstream.finish().whenComplete(this);
102+
break;
103+
} else {
104+
upstream.next().whenComplete(this);
105+
}
106+
} while (wip.decrementAndGet() != 0);
107+
}
108+
109+
@Override
110+
public void accept(Object t, Throwable u) {
111+
if (done) {
112+
if (u != null) {
113+
finishReady.completeExceptionally(u);
114+
} else {
115+
finishReady.complete(null);
116+
}
117+
} else {
118+
if (u != null) {
119+
nextReady.completeExceptionally(u);
120+
} else
121+
if ((Boolean)t) {
122+
accumulator.accept(storage, upstream.current());
123+
drain();
124+
} else {
125+
current = finisher.apply(storage);
126+
nextReady.complete(true);
127+
}
128+
}
129+
}
130+
}
131+
}

0 commit comments

Comments
 (0)