diff --git a/src/main/java/io/reactivex/rxjava4/internal/operators/streamable/StreamableFromStream.java b/src/main/java/io/reactivex/rxjava4/internal/operators/streamable/StreamableFromStream.java index 58b8672d3b..1d33347afe 100644 --- a/src/main/java/io/reactivex/rxjava4/internal/operators/streamable/StreamableFromStream.java +++ b/src/main/java/io/reactivex/rxjava4/internal/operators/streamable/StreamableFromStream.java @@ -13,7 +13,7 @@ package io.reactivex.rxjava4.internal.operators.streamable; -import java.util.Objects; +import java.util.*; import java.util.stream.Stream; import io.reactivex.rxjava4.annotations.NonNull; @@ -27,8 +27,22 @@ public record StreamableFromStream(@NonNull Stream items) implem @SuppressWarnings("unchecked") @Override public @NonNull Streamer<@NonNull T> stream(@NonNull StreamerCancellation cancellation) { - var iterator = Objects.requireNonNull(items.iterator(), "iterator is null"); - if (!iterator.hasNext()) { + Iterator iterator; + boolean hasNext; + try { + iterator = Objects.requireNonNull(items.iterator(), "iterator is null"); + hasNext = iterator.hasNext(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + try { + items.close(); + } catch (Throwable exc) { + Exceptions.throwIfFatal(exc); + ex.addSuppressed(exc); + } + return StreamableError.createFailed(ex); + } + if (!hasNext) { try { items.close(); } catch (Throwable ex) { diff --git a/src/test/java/io/reactivex/rxjava4/internal/operators/streamable/StreamableFromStreamTest.java b/src/test/java/io/reactivex/rxjava4/internal/operators/streamable/StreamableFromStreamTest.java index d721c264ab..0fc3a9e1b1 100644 --- a/src/test/java/io/reactivex/rxjava4/internal/operators/streamable/StreamableFromStreamTest.java +++ b/src/test/java/io/reactivex/rxjava4/internal/operators/streamable/StreamableFromStreamTest.java @@ -13,16 +13,16 @@ package io.reactivex.rxjava4.internal.operators.streamable; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; import java.util.*; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.*; import java.util.stream.Stream; import org.junit.jupiter.api.Test; -import io.reactivex.rxjava4.core.Streamable; +import io.reactivex.rxjava4.core.*; import io.reactivex.rxjava4.exceptions.TestException; public class StreamableFromStreamTest extends StreamableBaseTest { @@ -120,4 +120,30 @@ public void ensureCloses2Crash() { assertTrue(hasClosed.get(), "Close not called"); } + + @Test + public void firstItemCrashCloses() { + var hasClosed = new AtomicBoolean(); + Streamable.fromStream(Stream.of(1, 2, 3).map(_ -> { throw new TestException(); }) + .onClose(() -> hasClosed.set(true))) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertFailure(TestException.class); + + assertTrue(hasClosed.get(), "Close not called"); + } + + @Test + public void alreadyConsumedStreamSink() throws Throwable { + var stream = Stream.of(1, 2, 3); + stream.forEach(_ -> { }); + + var error = new AtomicReference(); + Streamable.fromStream(stream) + .subscribe(StreamSink.create(_ -> Streamer.NEXT_TRUE, e -> { error.set(e); return Streamer.FINISHED; })) + .toCompletableFuture() + .get(5, TimeUnit.SECONDS); + + assertInstanceOf(IllegalStateException.class, error.get()); + } }