Align lazy and future operators #26446

This commit is contained in:
Johan Andrén 2019-10-16 17:02:12 +02:00 committed by GitHub
parent 4a72985e48
commit 74adecb4e7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
59 changed files with 2091 additions and 384 deletions

View file

@ -717,3 +717,78 @@ This also means that custom `GraphStage` implementations should be changed to pa
cancellation cause when downstream cancels by implementing the `OutHandler.onDownstreamFinish` signature
taking a `cause` parameter and calling `cancelStage(cause)` to pass the cause upstream. The old zero-argument
`onDownstreamFinish` method has been deprecated.
### Lazy and async stream operator changes
The operators that provide support for lazy and @scala[`Future`]@java[`CompletionStage`] stream construction were revised
to be more consistent.
The materialized value is now no longer wrapped in an @scala[`Option`]@java[`Optional`], instead the @scala[`Future`]@java[`CompletionStage`]
is failed with a `akka.stream.NeverMaterializedException` in the cases that would previously lead to @scala[`None`]@java[an empty `Optional`]
A deferred creation of the stream based on the initial element like how the deprecated `lazyInit` worked can be achieved by combining
@scala[`future(Flow|Sink)`] @java[`completionStage(Flow|Sink)`] with `prefixAndTail`. See example in @scala[@ref:[futureFlow](../stream/operators/Flow/futureFlow.md)]
@java[@ref:[completionStageFlow](../stream/operators/Flow/completionStageFlow.md)].
#### javadsl.Flow
| old | new |
------------------------|----------------
| lazyInit | @ref:[lazyCompletionStageFlow](../stream/operators/Flow/lazyCompletionStageFlow.md) in combination with `prefixAndTail(1)` |
| lazyInitAsync | @ref:[lazyCompletionStageFlow](../stream/operators/Flow/lazyCompletionStageFlow.md) |
| | @ref:[completionStageFlow](../stream/operators/Flow/completionStageFlow.md) |
| | @ref:[lazyFlow](../stream/operators/Flow/lazyFlow.md) |
### javadsl.Sink
| old | new |
------------------------|----------------
| lazyInit | @ref:[lazyCompletionStageSink](../stream/operators/Sink/lazyCompletionStageSink.md) in combination with `Flow.prefixAndTail(1)` |
| lazyInitAsync | @ref:[lazyCompletionStageSink](../stream/operators/Sink/lazyCompletionStageSink.md) |
| | @ref:[completionStageSink](../stream/operators/Sink/completionStageSink.md) |
| | @ref:[lazySink](../stream/operators/Sink/lazySink.md) |
### javadsl.Source
| old | new |
--------------------------|----------------
| fromFuture | @ref:[future](../stream/operators/Source/future.md) |
| fromCompletionStage | @ref:[completionStage](../stream/operators/Source/completionStage.md) |
| fromFutureSource | @ref:[futureSource](../stream/operators/Source/futureSource.md) |
| fromSourceCompletionStage | @ref:[completionStageSource](../stream/operators/Source/completionStageSource.md) |
| lazily | @ref:[lazySource](../stream/operators/Source/lazySource.md) |
| lazilyAsync | @ref:[lazyCompletionStage](../stream/operators/Source/lazyCompletionStage.md) |
| | @ref:[lazySingle](../stream/operators/Source/lazySingle.md) |
| | @ref:[lazyCompletionStageSource](../stream/operators/Source/lazyCompletionStageSource.md) |
### scaladsl.Flow
| old | new |
--------------------------|----------------
| lazyInit | @ref:[lazyFutureFlow](../stream/operators/Flow/lazyFutureFlow.md) |
| lazyInitAsync | @ref:[lazyFutureFlow](../stream/operators/Flow/lazyFutureFlow.md) |
| | @ref:[futureFlow](../stream/operators/Flow/futureFlow.md) |
| | @ref:[lazyFlow](../stream/operators/Flow/lazyFlow.md) |
### scaladsl.Sink
| old | new |
------------------------|----------------
| lazyInit | @ref:[lazyFutureSink](../stream/operators/Sink/lazyFutureSink.md) in combination with `Flow.prefixAndTail(1)` |
| lazyInitAsync | @ref:[lazyFutureSink](../stream/operators/Sink/lazyFutureSink.md) |
| | @ref:[futureSink](../stream/operators/Sink/futureSink.md) |
| | @ref:[lazySink](../stream/operators/Sink/lazySink.md) |
### scaladsl.Source
| old | new |
--------------------------|----------------
| fromFuture | @ref:[future](../stream/operators/Source/future.md) |
| fromCompletionStage | @ref:[completionStage](../stream/operators/Source/completionStage.md) |
| fromFutureSource | @ref:[futureSource](../stream/operators/Source/futureSource.md) |
| fromSourceCompletionStage | |
| lazily | @ref:[lazySource](../stream/operators/Source/lazySource.md) |
| lazilyAsync | @ref:[lazyFuture](../stream/operators/Source/lazyFuture.md) |
| | @ref:[lazySingle](../stream/operators/Source/lazySingle.md) |
| | @ref:[lazyFutureSource](../stream/operators/Source/lazyFutureSource.md) |

View file

@ -0,0 +1,42 @@
# Flow.completionStageFlow
Streams the elements through the given future flow once it successfully completes.
@ref[Simple operators](../index.md#simple-operators)
@@@div { .group-scala }
## Signature
@@signature [Flow.scala](/akka-stream/src/main/scala/akka/stream/scaladsl/Flow.scala) { #futureFlow }
@@@
## Description
Streams the elements through the given flow once the `CompletionStage` successfully completes.
If the future fails the stream is failed.
## Examples
A deferred creation of the stream based on the initial element by combining `completionStageFlow`
with `prefixAndTail` like so:
Scala
: @@snip [FutureFlow.java](/akka-docs/src/test/java/jdocs/stream/operators/flow/FutureFlow.java) { #base-on-first-element }
## Reactive Streams semantics
@@@div { .callout }
**emits** when the internal flow is successfully created and it emits
**backpressures** when the internal flow is successfully created and it backpressures
**completes** when upstream completes and all elements have been emitted from the internal flow
**completes** when upstream completes and all futures have been completed and all elements have been emitted
@@@

View file

@ -0,0 +1,43 @@
# Flow.futureFlow
Streams the elements through the given future flow once it successfully completes.
@ref[Simple operators](../index.md#simple-operators)
@@@div { .group-scala }
## Signature
@@signature [Flow.scala](/akka-stream/src/main/scala/akka/stream/scaladsl/Flow.scala) { #futureFlow }
@@@
## Description
Streams the elements through the given future flow once it successfully completes.
If the future fails the stream is failed.
## Examples
A deferred creation of the stream based on the initial element can be achieved by combining `futureFlow`
with `prefixAndTail` like so:
Scala
: @@snip [FutureFlow.scala](/akka-docs/src/test/scala/docs/stream/operators/flow/FutureFlow.scala) { #base-on-first-element }
## Reactive Streams semantics
@@@div { .callout }
**emits** when the internal flow is successfully created and it emits
**backpressures** when the internal flow is successfully created and it backpressures
**completes** when upstream completes and all elements have been emitted from the internal flow
**completes** when upstream completes and all futures have been completed and all elements have been emitted
@@@

View file

@ -0,0 +1,35 @@
# Flow.lazyCompletionStageFlow
Defers creation and materialization of a `Flow` until there is a first element.
@ref[Simple operators](../index.md#simple-operators)
## Description
When the first element comes from upstream the actual `CompletionStage<Flow>` is created and when that completes it is materialized
and inserted in the stream.
The internal `Flow` will not be created if there are no elements on completion or failure of up or downstream.
The materialized value of the `Flow` will be the materialized value of the created internal flow if it is materialized
and failed with a `akka.stream.NeverMaterializedException` if the stream fails or completes without the flow being materialized.
See also @ref:[lazyFlow](lazyFlow.md).
Can be combined with `prefixAndTail(1)` to base the flow construction on the initial element triggering creation.
See @ref:[lazyFlow](lazyFlow.md) for sample.
## Reactive Streams semantics
@@@div { .callout }
**emits** when the internal flow is successfully created and it emits
**backpressures** when the internal flow is successfully created and it backpressures
**completes** when upstream completes and all elements have been emitted from the internal flow
**completes** when upstream completes and all futures have been completed and all elements have been emitted
@@@

View file

@ -0,0 +1,36 @@
# Flow.lazyFlow
Defers creation and materialization of a `Flow` until there is a first element.
@ref[Simple operators](../index.md#simple-operators)
@@@div { .group-scala }
## Signature
@@signature [Flow.scala](/akka-stream/src/main/scala/akka/stream/scaladsl/Flow.scala) { #lazyFlow }
@@@
## Description
When the first element comes from upstream the actual `Flow` is created and materialized.
The internal `Flow` will not be created if there are no elements on completion or failure of up or downstream.
The materialized value of the `Flow` will be the materialized value of the created internal flow if it is materialized
and failed with a `akka.stream.NeverMaterializedException` if the stream fails or completes without the flow being materialized.
## Reactive Streams semantics
@@@div { .callout }
**emits** when the internal flow is successfully created and it emits
**backpressures** when the internal flow is successfully created and it backpressures
**completes** when upstream completes and all elements have been emitted from the internal flow
**completes** when upstream completes and all futures have been completed and all elements have been emitted
@@@

View file

@ -0,0 +1,42 @@
# Flow.lazyFutureFlow
Defers creation and materialization of a `Flow` until there is a first element.
@ref[Simple operators](../index.md#simple-operators)
@@@div { .group-scala }
## Signature
@@signature [Flow.scala](/akka-stream/src/main/scala/akka/stream/scaladsl/Flow.scala) { #lazyFlow }
@@@
## Description
When the first element comes from upstream the actual `Future[Flow]` is created and when that completes it is materialized
and inserted in the stream.
The internal `Flow` will not be created if there are no elements on completion or failure of up or downstream.
The materialized value of the `Flow` will be the materialized value of the created internal flow if it is materialized
and failed with a `akka.stream.NeverMaterializedException` if the stream fails or completes without the flow being materialized.
See also @ref:[lazyFlow](lazyFlow.md).
Can be combined with `prefixAndTail(1)` to base the flow construction on the initial element triggering creation.
See @ref:[lazyFlow](lazyFlow.md) for sample.
## Reactive Streams semantics
@@@div { .callout }
**emits** when the internal flow is successfully created and it emits
**backpressures** when the internal flow is successfully created and it backpressures
**completes** when upstream completes and all elements have been emitted from the internal flow
**completes** when upstream completes and all futures have been completed and all elements have been emitted
@@@

View file

@ -1,6 +1,6 @@
# Flow.lazyInitAsync
Creates a real `Flow` upon receiving the first element by calling relevant `flowFactory` given as an argument.
`lazyInitAsync` has been deprecated in 2.6.0 use `Flow.lazyFutureFlow` in combination with `prefixAndTail` instead.
@ref[Simple operators](../index.md#simple-operators)
@ -14,17 +14,9 @@ Creates a real `Flow` upon receiving the first element by calling relevant `flow
## Description
Creates a real `Flow` upon receiving the first element by calling relevant `flowFactory` given as an argument.
Internal `Flow` will not be created if there are no elements, because of completion or error.
The materialized value of the `Flow` will be the materialized value of the created internal flow.
`fromCompletionStage` has been deprecated in 2.6.0 use @ref:[lazyFutureFlow](lazyFutureFlow.md) in combination with `prefixAndTail` instead.
The materialized value of the `Flow` is a @scala[`Future[Option[M]]`]@java[`CompletionStage<Optional<M>>`] that is
completed with @scala[`Some(mat)`]@java[`Optional.of(mat)`] when the internal flow gets materialized or with @scala[`None`]
@java[an empty optional] when there where no elements. If the flow materialization (including the call of the `flowFactory`)
fails then the future is completed with a failure.
Adheres to the @scala[@scaladoc[`ActorAttributes.SupervisionStrategy`](akka.stream.ActorAttributes$$SupervisionStrategy)]
@java[`ActorAttributes.SupervisionStrategy`] attribute.
Defers creation until a first element arrives.
## Reactive Streams semantics

View file

@ -0,0 +1,23 @@
# Sink.completionStageSink
Streams the elements to the given future sink once it successfully completes.
@ref[Sink operators](../index.md#sink-operators)
## Description
Streams the elements through the given future flow once it successfully completes.
If the future fails the stream is failed.
## Reactive Streams semantics
@@@div { .callout }
**cancels** if the future fails or if the created sink cancels
**backpressures** when initialized and when created sink backpressures
@@@

View file

@ -0,0 +1,30 @@
# Sink.futureSink
Streams the elements to the given future sink once it successfully completes.
@ref[Sink operators](../index.md#sink-operators)
@@@div { .group-scala }
## Signature
@@signature [Sink.scala](/akka-stream/src/main/scala/akka/stream/scaladsl/Sink.scala) { #futureSink }
@@@
## Description
Streams the elements through the given future flow once it successfully completes.
If the future fails the stream is failed.
## Reactive Streams semantics
@@@div { .callout }
**cancels** if the future fails or if the created sink cancels
**backpressures** when initialized and when created sink backpressures
@@@

View file

@ -0,0 +1,31 @@
# Sink.lazyCompletionStageSink
Defers creation and materialization of a `Sink` until there is a first element.
@ref[Sink operators](../index.md#sink-operators)
## Description
When the first element comes from upstream the `CompletionStage<Sink>` is created. When that completes successfully with a sink
that is materialized and inserted in the stream.
The internal `Sink` will not be created if the stream completes of fails before any element got through.
The materialized value of the `Sink` will be the materialized value of the created internal flow if it is materialized
and failed with a `akka.stream.NeverMaterializedException` if the stream fails or completes without the flow being materialized.
Can be combined with @ref:[prefixAndTail](../Source-or-Flow/prefixAndTail.md) to base the sink on the first element.
See also @ref:[lazySink](lazySink.md).
## Reactive Streams semantics
@@@div { .callout }
**cancels** if the future fails or if the created sink cancels
**backpressures** when initialized and when created sink backpressures
@@@

View file

@ -0,0 +1,38 @@
# Sink.lazyFutureSink
Defers creation and materialization of a `Sink` until there is a first element.
@ref[Sink operators](../index.md#sink-operators)
@@@div { .group-scala }
## Signature
@@signature [Sink.scala](/akka-stream/src/main/scala/akka/stream/scaladsl/Sink.scala) { #lazySink }
@@@
## Description
When the first element comes from upstream the `Future[Sink]` is created. When that completes successfully with a sink
that is materialized and inserted in the stream.
The internal `Sink` will not be created if the stream completes of fails before any element got through.
The materialized value of the `Sink` will be the materialized value of the created internal flow if it is materialized
and failed with a `akka.stream.NeverMaterializedException` if the stream fails or completes without the flow being materialized.
Can be combined with @ref:[prefixAndTail](../Source-or-Flow/prefixAndTail.md) to base the sink on the first element.
See also @ref:[lazySink](lazySink.md).
## Reactive Streams semantics
@@@div { .callout }
**cancels** if the future fails or if the created sink cancels
**backpressures** when initialized and when created sink backpressures
@@@

View file

@ -1,6 +1,6 @@
# Sink.lazyInitAsync
Creates a real `Sink` upon receiving the first element.
`lazyInitAsync` has been deprecated in 2.6.0, use `Sink.lazyFutureSink`
@ref[Sink operators](../index.md#sink-operators)
@ -14,6 +14,8 @@ Creates a real `Sink` upon receiving the first element.
## Description
`lazyInitAsync` has been deprecated in 2.6.0, use @ref:[lazyFutureSink](lazyFutureSink.md) instead.
Creates a real `Sink` upon receiving the first element. Internal `Sink` will not be created if there are no elements,
because of completion or error.

View file

@ -0,0 +1,37 @@
# Sink.lazySink
Defers creation and materialization of a `Sink` until there is a first element.
@ref[Sink operators](../index.md#sink-operators)
@@@div { .group-scala }
## Signature
@@signature [Sink.scala](/akka-stream/src/main/scala/akka/stream/scaladsl/Sink.scala) { #lazySink }
@@@
## Description
When the first element comes from upstream the actual `Sink` is created and materialized.
The internal `Sink` will not be created if the stream completes of fails before any element got through.
The materialized value of the `Sink` will be the materialized value of the created internal flow if it is materialized
and failed with a `akka.stream.NeverMaterializedException` if the stream fails or completes without the flow being materialized.
Can be combined with @ref[prefixAndTail](../Source-or-Flow/prefixAndTail.md) to base the sink on the first element.
See also @ref:[lazyFutureSink](lazyFutureSink.md) and @ref:[lazyCompletionStageSink](lazyCompletionStageSink.md).
## Reactive Streams semantics
@@@div { .callout }
**cancels** if the future fails or if the created sink cancels
**backpressures** when initialized and when created sink backpressures
@@@

View file

@ -0,0 +1,32 @@
# completionStage
Send the single value of the `CompletionStage` when it completes and there is demand.
@ref[Source operators](../index.md#source-operators)
@@@div { .group-scala }
## Signature
@@signature [Source.scala](/akka-stream/src/main/scala/akka/stream/scaladsl/Source.scala) { #completionStage }
@@@
## Description
Send the single value of the `CompletionStage` when it completes and there is demand.
If the `CompletionStage` completes with `null` stage is completed without emitting a value.
If the `CompletionStage` fails the stream is failed with that exception.
For the corresponding operator for the Scala standard library `Future` see @ref:[future](future.md).
## Reactive Streams semantics
@@@div { .callout }
**emits** the future completes
**completes** after the future has completed
@@@

View file

@ -0,0 +1,23 @@
# completionStageSource
Streams the elements of an asynchronous source once its given *completion* operator completes.
@ref[Source operators](../index.md#source-operators)
## Signature
## Description
Streams the elements of an asynchronous source once its given *completion* operator completes.
If the *completion* fails the stream is failed with that exception.
## Reactive Streams semantics
@@@div { .callout }
**emits** the next value from the asynchronous source, once its *completion operator* has completed
**completes** after the asynchronous source completes
@@@

View file

@ -1,6 +1,6 @@
# fromCompletionStage
Send the single value of the `CompletionStage` when it completes and there is demand.
`fromCompletionStage` has been deprecated in 2.6.0, use `Source.completionStage`
@ref[Source operators](../index.md#source-operators)
@ -14,6 +14,8 @@ Send the single value of the `CompletionStage` when it completes and there is de
## Description
`fromCompletionStage` has been deprecated in 2.6.0, use @ref:[completionStage](completionStage.md) instead.
Send the single value of the `CompletionStage` when it completes and there is demand.
If the `CompletionStage` completes with `null` stage is completed without emitting a value.
If the `CompletionStage` fails the stream is failed with that exception.

View file

@ -1,6 +1,6 @@
# fromFuture
Send the single value of the `Future` when it completes and there is demand.
`fromFuture` has been deprecated in 2.6.0, use `Source.future` instead.
@ref[Source operators](../index.md#source-operators)
@ -14,6 +14,8 @@ Send the single value of the `Future` when it completes and there is demand.
## Description
`fromFuture` has been deprecated in 2.6.0, use @ref:[future](future.md) instead.
Send the single value of the `Future` when it completes and there is demand.
If the future fails the stream is failed with that exception.
@ -28,6 +30,4 @@ If the future fails the stream is failed with that exception.
@@@
## Example
Scala
: @@snip [SourceFromFuture.scala](/akka-docs/src/test/scala/docs/stream/operators/SourceOperators.scala) { #sourceFromFuture }

View file

@ -1,6 +1,6 @@
# fromFutureSource
Streams the elements of the given future source once it successfully completes.
`fromFutureSource` has been deprecated in 2.6.0, use `Source.futureSource` instead.
@ref[Source operators](../index.md#source-operators)
@ -14,6 +14,8 @@ Streams the elements of the given future source once it successfully completes.
## Description
`fromFutureSource` has been deprecated in 2.6.0, use @ref:[futureSource](futureSource.md) instead.
Streams the elements of the given future source once it successfully completes.
If the future fails the stream is failed.

View file

@ -1,6 +1,6 @@
# fromSourceCompletionStage
Streams the elements of an asynchronous source once its given *completion* operator completes.
`fromSourceCompletionStage` has been deprecated in 2.6.0, use `Source.completionStageSource` instead.
@ref[Source operators](../index.md#source-operators)
@ -8,6 +8,8 @@ Streams the elements of an asynchronous source once its given *completion* opera
## Description
`fromSourceCompletionStage` has been deprecated in 2.6.0, use @ref:[completionStageSource](completionStageSource.md) instead.
Streams the elements of an asynchronous source once its given *completion* operator completes.
If the *completion* fails the stream is failed with that exception.

View file

@ -0,0 +1,35 @@
# fromFuture
Send the single value of the `Future` when it completes and there is demand.
@ref[Source operators](../index.md#source-operators)
@@@div { .group-scala }
## Signature
@@signature [Source.scala](/akka-stream/src/main/scala/akka/stream/scaladsl/Source.scala) { #future }
@@@
## Description
Send the single value of the `Future` when it completes and there is demand.
If the future fails the stream is failed with that exception.
For the corresponding operator for the Java standard library `CompletionStage` see @ref:[completionStage](completionStage.md).
## Reactive Streams semantics
@@@div { .callout }
**emits** the future completes
**completes** after the future has completed
@@@
## Example
Scala
: @@snip [SourceFromFuture.scala](/akka-docs/src/test/scala/docs/stream/operators/SourceOperators.scala) { #sourceFromFuture }

View file

@ -0,0 +1,29 @@
# futureSource
Streams the elements of the given future source once it successfully completes.
@ref[Source operators](../index.md#source-operators)
@@@div { .group-scala }
## Signature
@@signature [Source.scala](/akka-stream/src/main/scala/akka/stream/scaladsl/Source.scala) { #futureSource }
@@@
## Description
Streams the elements of the given future source once it successfully completes.
If the future fails the stream is failed.
## Reactive Streams semantics
@@@div { .callout }
**emits** the next value from the *future* source, once it has completed
**completes** after the *future* source completes
@@@

View file

@ -1,6 +1,6 @@
# lazily
Defers creation and materialization of a `Source` until there is demand.
`lazily` has been deprecated in 2.6.0, use `Source.lazySource` instead.
@ref[Source operators](../index.md#source-operators)
@ -14,6 +14,8 @@ Defers creation and materialization of a `Source` until there is demand.
## Description
`lazily` has been deprecated in 2.6.0, use @ref:[lazySource](lazySource.md) instead.
Defers creation and materialization of a `Source` until there is demand.
## Reactive Streams semantics

View file

@ -1,6 +1,6 @@
# lazilyAsync
Defers creation and materialization of a `CompletionStage` until there is demand.
`lazily` has been deprecated in 2.6.0, use `Source.lazyFutureSource` instead.
@ref[Source operators](../index.md#source-operators)
@ -8,6 +8,8 @@ Defers creation and materialization of a `CompletionStage` until there is demand
## Description
`lazily` has been deprecated in 2.6.0, use @ref:[lazyFutureSource](lazyFutureSource.md) instead.
Defers creation and materialization of a `CompletionStage` until there is demand.
## Reactive Streams semantics

View file

@ -0,0 +1,25 @@
# lazyCompletionStage
Defers creation of a future of a single element source until there is demand.
@ref[Source operators](../index.md#source-operators)
## Description
Invokes the user supplied factory when the first downstream demand arrives. When the returned future completes
successfully the value is emitted downstream as a single stream element. If the future or the factory fails the
stream is failed.
Note that asynchronous boundaries (and other operators) in the stream may do pre-fetching which counter acts
the laziness and will trigger the factory immediately.
## Reactive Streams semantics
@@@div { .callout }
**emits** when there is downstream demand and the element factory returned future has completed
**completes** after emitting the single element
@@@

View file

@ -0,0 +1,27 @@
# lazyCompletionStageSource
Defers creation of a future source until there is demand.
@ref[Source operators](../index.md#source-operators)
## Description
Invokes the user supplied factory when the first downstream demand arrives. When the returned `CompletionStage` completes
successfully the source switches over to the new source and emits downstream just like if it had been created up front. If the future or the factory fails the
stream is failed.
Note that asynchronous boundaries (and other operators) in the stream may do pre-fetching which counter acts
the laziness and will trigger the factory immediately.
See also @ref:[lazySource](lazySource.md).
## Reactive Streams semantics
@@@div { .callout }
**emits** depends on the wrapped `Source`
**completes** depends on the wrapped `Source`
@@@

View file

@ -0,0 +1,33 @@
# lazyFuture
Defers creation of a future of a single element source until there is demand.
@ref[Source operators](../index.md#source-operators)
@@@div { .group-scala }
## Signature
@@signature [Source.scala](/akka-stream/src/main/scala/akka/stream/scaladsl/Source.scala) { #lazyFuture }
@@@
## Description
Invokes the user supplied factory when the first downstream demand arrives. When the returned future completes
successfully the value is emitted downstream as a single stream element. If the future or the factory fails the
stream is failed.
Note that asynchronous boundaries (and other operators) in the stream may do pre-fetching which counter acts
the laziness and will trigger the factory immediately.
## Reactive Streams semantics
@@@div { .callout }
**emits** when there is downstream demand and the element factory returned future has completed
**completes** after emitting the single element
@@@

View file

@ -0,0 +1,34 @@
# lazyFutureSource
Defers creation and materialization of a `Source` until there is demand.
@ref[Source operators](../index.md#source-operators)
@@@div { .group-scala }
## Signature
@@signature [Source.scala](/akka-stream/src/main/scala/akka/stream/scaladsl/Source.scala) { #lazyFutureSource }
@@@
## Description
Invokes the user supplied factory when the first downstream demand arrives. When the returned future completes
successfully the source switches over to the new source and emits downstream just like if it had been created up front.
Note that asynchronous boundaries (and other operators) in the stream may do pre-fetching which counter acts
the laziness and will trigger the factory immediately.
See also @ref:[lazySource](lazySource.md).
## Reactive Streams semantics
@@@div { .callout }
**emits** depends on the wrapped `Source`
**completes** depends on the wrapped `Source`
@@@

View file

@ -0,0 +1,32 @@
# lazySingle
Defers creation of a single element source until there is demand.
@ref[Source operators](../index.md#source-operators)
@@@div { .group-scala }
## Signature
@@signature [Source.scala](/akka-stream/src/main/scala/akka/stream/scaladsl/Source.scala) { #lazySingle }
@@@
## Description
Invokes the user supplied factory when the first downstream demand arrives, then emits the returned single value
downstream and completes the stream.
Note that asynchronous boundaries (and other operators) in the stream may do pre-fetching which counter acts
the laziness and will trigger the factory immediately.
## Reactive Streams semantics
@@@div { .callout }
**emits** when there is downstream demand and the element factory has completed
**completes** after emitting the single element
@@@

View file

@ -0,0 +1,35 @@
# lazySource
Defers creation and materialization of a `Source` until there is demand.
@ref[Source operators](../index.md#source-operators)
@@@div { .group-scala }
## Signature
@@signature [Source.scala](/akka-stream/src/main/scala/akka/stream/scaladsl/Source.scala) { #lazySource }
@@@
## Description
Defers creation and materialization of a `Source` until there is demand, then emits the elements from the source
downstream just like if it had been created up front.
See also @ref:[lazyFutureSource](lazyFutureSource.md).
Note that asynchronous boundaries (and other operators) in the stream may do pre-fetching which counter acts
the laziness and will trigger the factory immediately.
## Reactive Streams semantics
@@@div { .callout }
**emits** depends on the wrapped `Source`
**completes** depends on the wrapped `Source`
@@@

View file

@ -12,18 +12,28 @@ These built-in sources are available from @scala[`akka.stream.scaladsl.Source`]
|Source|<a name="assourcewithcontext"></a>@ref[asSourceWithContext](Source/asSourceWithContext.md)|Turns a Source into a SourceWithContext which can propagate a context per element along a stream.|
|Source|<a name="assubscriber"></a>@ref[asSubscriber](Source/asSubscriber.md)|Integration with Reactive Streams, materializes into a `org.reactivestreams.Subscriber`.|
|Source|<a name="combine"></a>@ref[combine](Source/combine.md)|Combine several sources, using a given strategy such as merge or concat, into one source.|
|Source|<a name="completionstage"></a>@ref[completionStage](Source/completionStage.md)|Send the single value of the `CompletionStage` when it completes and there is demand.|
|Source|<a name="completionstagesource"></a>@ref[completionStageSource](Source/completionStageSource.md)|Streams the elements of an asynchronous source once its given *completion* operator completes.|
|Source|<a name="cycle"></a>@ref[cycle](Source/cycle.md)|Stream iterator in cycled manner.|
|Source|<a name="empty"></a>@ref[empty](Source/empty.md)|Complete right away without ever emitting any elements.|
|Source|<a name="failed"></a>@ref[failed](Source/failed.md)|Fail directly with a user specified exception.|
|Source|<a name="from"></a>@ref[@scala[apply]@java[from]](Source/from.md)|Stream the values of an @scala[`immutable.Seq`]@java[`Iterable`].|
|Source|<a name="fromcompletionstage"></a>@ref[fromCompletionStage](Source/fromCompletionStage.md)|Send the single value of the `CompletionStage` when it completes and there is demand.|
|Source|<a name="fromfuture"></a>@ref[fromFuture](Source/fromFuture.md)|Send the single value of the `Future` when it completes and there is demand.|
|Source|<a name="fromfuturesource"></a>@ref[fromFutureSource](Source/fromFutureSource.md)|Streams the elements of the given future source once it successfully completes.|
|Source|<a name="fromcompletionstage"></a>@ref[fromCompletionStage](Source/fromCompletionStage.md)|`fromCompletionStage` has been deprecated in 2.6.0, use `Source.completionStage`|
|Source|<a name="fromfuture"></a>@ref[fromFuture](Source/fromFuture.md)|`fromFuture` has been deprecated in 2.6.0, use `Source.future` instead.|
|Source|<a name="fromfuturesource"></a>@ref[fromFutureSource](Source/fromFutureSource.md)|`fromFutureSource` has been deprecated in 2.6.0, use `Source.futureSource` instead.|
|Source|<a name="fromiterator"></a>@ref[fromIterator](Source/fromIterator.md)|Stream the values from an `Iterator`, requesting the next value when there is demand.|
|Source|<a name="frompublisher"></a>@ref[fromPublisher](Source/fromPublisher.md)|Integration with Reactive Streams, subscribes to a `org.reactivestreams.Publisher`.|
|Source|<a name="fromsourcecompletionstage"></a>@ref[fromSourceCompletionStage](Source/fromSourceCompletionStage.md)|Streams the elements of an asynchronous source once its given *completion* operator completes.|
|Source|<a name="lazily"></a>@ref[lazily](Source/lazily.md)|Defers creation and materialization of a `Source` until there is demand.|
|Source|<a name="lazilyasync"></a>@ref[lazilyAsync](Source/lazilyAsync.md)|Defers creation and materialization of a `CompletionStage` until there is demand.|
|Source|<a name="fromsourcecompletionstage"></a>@ref[fromSourceCompletionStage](Source/fromSourceCompletionStage.md)|`fromSourceCompletionStage` has been deprecated in 2.6.0, use `Source.completionStageSource` instead.|
|Source|<a name="future"></a>@ref[future](Source/future.md)|Send the single value of the `Future` when it completes and there is demand.|
|Source|<a name="futuresource"></a>@ref[futureSource](Source/futureSource.md)|Streams the elements of the given future source once it successfully completes.|
|Source|<a name="lazily"></a>@ref[lazily](Source/lazily.md)|`lazily` has been deprecated in 2.6.0, use `Source.lazySource` instead.|
|Source|<a name="lazilyasync"></a>@ref[lazilyAsync](Source/lazilyAsync.md)|`lazily` has been deprecated in 2.6.0, use `Source.lazyFutureSource` instead.|
|Source|<a name="lazycompletionstage"></a>@ref[lazyCompletionStage](Source/lazyCompletionStage.md)|Defers creation of a future of a single element source until there is demand.|
|Source|<a name="lazycompletionstagesource"></a>@ref[lazyCompletionStageSource](Source/lazyCompletionStageSource.md)|Defers creation of a future source until there is demand.|
|Source|<a name="lazyfuture"></a>@ref[lazyFuture](Source/lazyFuture.md)|Defers creation of a future of a single element source until there is demand.|
|Source|<a name="lazyfuturesource"></a>@ref[lazyFutureSource](Source/lazyFutureSource.md)|Defers creation and materialization of a `Source` until there is demand.|
|Source|<a name="lazysingle"></a>@ref[lazySingle](Source/lazySingle.md)|Defers creation of a single element source until there is demand.|
|Source|<a name="lazysource"></a>@ref[lazySource](Source/lazySource.md)|Defers creation and materialization of a `Source` until there is demand.|
|Source|<a name="maybe"></a>@ref[maybe](Source/maybe.md)|Create a source that emits once the materialized @scala[`Promise`] @java[`CompletableFuture`] is completed with a value.|
|Source|<a name="queue"></a>@ref[queue](Source/queue.md)|Materialize a `SourceQueue` onto which elements can be pushed for emitting from the source. |
|Source|<a name="range"></a>@ref[range](Source/range.md)|Emit each integer in a range, with an option to take bigger steps than 1.|
@ -49,18 +59,23 @@ These built-in sinks are available from @scala[`akka.stream.scaladsl.Sink`] @jav
|Sink|<a name="aspublisher"></a>@ref[asPublisher](Sink/asPublisher.md)|Integration with Reactive Streams, materializes into a `org.reactivestreams.Publisher`.|
|Sink|<a name="cancelled"></a>@ref[cancelled](Sink/cancelled.md)|Immediately cancel the stream|
|Sink|<a name="combine"></a>@ref[combine](Sink/combine.md)|Combine several sinks into one using a user specified strategy|
|Sink|<a name="completionstagesink"></a>@ref[completionStageSink](Sink/completionStageSink.md)|Streams the elements to the given future sink once it successfully completes. |
|Sink|<a name="fold"></a>@ref[fold](Sink/fold.md)|Fold over emitted element with a function, where each invocation will get the new element and the result from the previous fold invocation.|
|Sink|<a name="foreach"></a>@ref[foreach](Sink/foreach.md)|Invoke a given procedure for each element received.|
|Sink|<a name="foreachasync"></a>@ref[foreachAsync](Sink/foreachAsync.md)|Invoke a given procedure asynchronously for each element received.|
|Sink|<a name="foreachparallel"></a>@ref[foreachParallel](Sink/foreachParallel.md)|Like `foreach` but allows up to `parallellism` procedure calls to happen in parallel.|
|Sink|<a name="frommaterializer"></a>@ref[fromMaterializer](Sink/fromMaterializer.md)|Defer the creation of a `Sink` until materialization and access `Materializer` and `Attributes`|
|Sink|<a name="fromsubscriber"></a>@ref[fromSubscriber](Sink/fromSubscriber.md)|Integration with Reactive Streams, wraps a `org.reactivestreams.Subscriber` as a sink.|
|Sink|<a name="futuresink"></a>@ref[futureSink](Sink/futureSink.md)|Streams the elements to the given future sink once it successfully completes. |
|Sink|<a name="head"></a>@ref[head](Sink/head.md)|Materializes into a @scala[`Future`] @java[`CompletionStage`] which completes with the first value arriving, after this the stream is canceled.|
|Sink|<a name="headoption"></a>@ref[headOption](Sink/headOption.md)|Materializes into a @scala[`Future[Option[T]]`] @java[`CompletionStage<Optional<T>>`] which completes with the first value arriving wrapped in @scala[`Some`] @java[`Optional`], or @scala[a `None`] @java[an empty Optional] if the stream completes without any elements emitted.|
|Sink|<a name="ignore"></a>@ref[ignore](Sink/ignore.md)|Consume all elements but discards them.|
|Sink|<a name="last"></a>@ref[last](Sink/last.md)|Materializes into a @scala[`Future`] @java[`CompletionStage`] which will complete with the last value emitted when the stream completes.|
|Sink|<a name="lastoption"></a>@ref[lastOption](Sink/lastOption.md)|Materialize a @scala[`Future[Option[T]]`] @java[`CompletionStage<Optional<T>>`] which completes with the last value emitted wrapped in an @scala[`Some`] @java[`Optional`] when the stream completes.|
|Sink|<a name="lazyinitasync"></a>@ref[lazyInitAsync](Sink/lazyInitAsync.md)|Creates a real `Sink` upon receiving the first element. |
|Sink|<a name="lazycompletionstagesink"></a>@ref[lazyCompletionStageSink](Sink/lazyCompletionStageSink.md)|Defers creation and materialization of a `Sink` until there is a first element.|
|Sink|<a name="lazyfuturesink"></a>@ref[lazyFutureSink](Sink/lazyFutureSink.md)|Defers creation and materialization of a `Sink` until there is a first element.|
|Sink|<a name="lazyinitasync"></a>@ref[lazyInitAsync](Sink/lazyInitAsync.md)|`lazyInitAsync` has been deprecated in 2.6.0, use `Sink.lazyFutureSink` |
|Sink|<a name="lazysink"></a>@ref[lazySink](Sink/lazySink.md)|Defers creation and materialization of a `Sink` until there is a first element.|
|Sink|<a name="oncomplete"></a>@ref[onComplete](Sink/onComplete.md)|Invoke a callback when the stream has completed or failed.|
|Sink|<a name="prematerialize"></a>@ref[preMaterialize](Sink/preMaterialize.md)|Materializes this Sink, immediately returning (1) its materialized value, and (2) a new Sink that can be consume elements 'into' the pre-materialized one.|
|Sink|<a name="queue"></a>@ref[queue](Sink/queue.md)|Materialize a `SinkQueue` that can be pulled to trigger demand through the sink.|
@ -129,6 +144,7 @@ depending on being backpressured by downstream or not.
|Flow|<a name="asflowwithcontext"></a>@ref[asFlowWithContext](Flow/asFlowWithContext.md)|Turns a Flow into a FlowWithContext which can propagate a context per element along a stream.|
|Source/Flow|<a name="collect"></a>@ref[collect](Source-or-Flow/collect.md)|Apply a partial function to each incoming element, if the partial function is defined for a value the returned value is passed downstream.|
|Source/Flow|<a name="collecttype"></a>@ref[collectType](Source-or-Flow/collectType.md)|Transform this stream by testing the type of each of the elements on which the element is an instance of the provided type as they pass through this processing step.|
|Flow|<a name="completionstageflow"></a>@ref[completionStageFlow](Flow/completionStageFlow.md)|Streams the elements through the given future flow once it successfully completes.|
|Source/Flow|<a name="detach"></a>@ref[detach](Source-or-Flow/detach.md)|Detach upstream demand from downstream demand without detaching the stream rates.|
|Source/Flow|<a name="drop"></a>@ref[drop](Source-or-Flow/drop.md)|Drop `n` elements and then pass any subsequent element downstream.|
|Source/Flow|<a name="dropwhile"></a>@ref[dropWhile](Source-or-Flow/dropWhile.md)|Drop elements as long as a predicate function return true for the element|
@ -137,9 +153,13 @@ depending on being backpressured by downstream or not.
|Source/Flow|<a name="fold"></a>@ref[fold](Source-or-Flow/fold.md)|Start with current value `zero` and then apply the current and next value to the given function. When upstream completes, the current value is emitted downstream.|
|Source/Flow|<a name="foldasync"></a>@ref[foldAsync](Source-or-Flow/foldAsync.md)|Just like `fold` but receives a function that results in a @scala[`Future`] @java[`CompletionStage`] to the next value.|
|Source/Flow|<a name="frommaterializer"></a>@ref[fromMaterializer](Source-or-Flow/fromMaterializer.md)|Defer the creation of a `Source/Flow` until materialization and access `Materializer` and `Attributes`|
|Flow|<a name="futureflow"></a>@ref[futureFlow](Flow/futureFlow.md)|Streams the elements through the given future flow once it successfully completes.|
|Source/Flow|<a name="grouped"></a>@ref[grouped](Source-or-Flow/grouped.md)|Accumulate incoming events until the specified number of elements have been accumulated and then pass the collection of elements downstream.|
|Source/Flow|<a name="intersperse"></a>@ref[intersperse](Source-or-Flow/intersperse.md)|Intersperse stream with provided element similar to `List.mkString`.|
|Flow|<a name="lazyinitasync"></a>@ref[lazyInitAsync](Flow/lazyInitAsync.md)|Creates a real `Flow` upon receiving the first element by calling relevant `flowFactory` given as an argument.|
|Flow|<a name="lazycompletionstageflow"></a>@ref[lazyCompletionStageFlow](Flow/lazyCompletionStageFlow.md)|Defers creation and materialization of a `Flow` until there is a first element.|
|Flow|<a name="lazyflow"></a>@ref[lazyFlow](Flow/lazyFlow.md)|Defers creation and materialization of a `Flow` until there is a first element.|
|Flow|<a name="lazyfutureflow"></a>@ref[lazyFutureFlow](Flow/lazyFutureFlow.md)|Defers creation and materialization of a `Flow` until there is a first element.|
|Flow|<a name="lazyinitasync"></a>@ref[lazyInitAsync](Flow/lazyInitAsync.md)|`lazyInitAsync` has been deprecated in 2.6.0 use `Flow.lazyFutureFlow` in combination with `prefixAndTail` instead.|
|Source/Flow|<a name="limit"></a>@ref[limit](Source-or-Flow/limit.md)|Limit number of element from upstream to given `max` number.|
|Source/Flow|<a name="limitweighted"></a>@ref[limitWeighted](Source-or-Flow/limitWeighted.md)|Ensure stream boundedness by evaluating the cost of incoming elements using a cost function.|
|Source/Flow|<a name="log"></a>@ref[log](Source-or-Flow/log.md)|Log elements flowing through the stream as well as completion and erroring.|
@ -337,6 +357,13 @@ For more background see the @ref[Error Handling in Streams](../stream-error.md)
* [failed](Source/failed.md)
* [lazily](Source/lazily.md)
* [lazilyAsync](Source/lazilyAsync.md)
* [future](Source/future.md)
* [completionStage](Source/completionStage.md)
* [futureSource](Source/futureSource.md)
* [lazySingle](Source/lazySingle.md)
* [lazyFuture](Source/lazyFuture.md)
* [lazySource](Source/lazySource.md)
* [lazyFutureSource](Source/lazyFutureSource.md)
* [asSubscriber](Source/asSubscriber.md)
* [actorRef](Source/actorRef.md)
* [actorRefWithBackpressure](Source/actorRefWithBackpressure.md)
@ -347,6 +374,9 @@ For more background see the @ref[Error Handling in Streams](../stream-error.md)
* [unfoldResourceAsync](Source/unfoldResourceAsync.md)
* [@scala[apply]@java[from]](Source/from.md)
* [range](Source/range.md)
* [completionStageSource](Source/completionStageSource.md)
* [lazyCompletionStage](Source/lazyCompletionStage.md)
* [lazyCompletionStageSource](Source/lazyCompletionStageSource.md)
* [concat](Source-or-Flow/concat.md)
* [prepend](Source-or-Flow/prepend.md)
* [orElse](Source-or-Flow/orElse.md)
@ -427,6 +457,11 @@ For more background see the @ref[Error Handling in Streams](../stream-error.md)
* [fromSinkAndSource](Flow/fromSinkAndSource.md)
* [fromSinkAndSourceCoupled](Flow/fromSinkAndSourceCoupled.md)
* [lazyInitAsync](Flow/lazyInitAsync.md)
* [futureFlow](Flow/futureFlow.md)
* [lazyFlow](Flow/lazyFlow.md)
* [lazyFutureFlow](Flow/lazyFutureFlow.md)
* [completionStageFlow](Flow/completionStageFlow.md)
* [lazyCompletionStageFlow](Flow/lazyCompletionStageFlow.md)
* [preMaterialize](Sink/preMaterialize.md)
* [fromMaterializer](Sink/fromMaterializer.md)
* [setup](Sink/setup.md)
@ -451,6 +486,11 @@ For more background see the @ref[Error Handling in Streams](../stream-error.md)
* [actorRefWithBackpressure](Sink/actorRefWithBackpressure.md)
* [queue](Sink/queue.md)
* [lazyInitAsync](Sink/lazyInitAsync.md)
* [futureSink](Sink/futureSink.md)
* [lazySink](Sink/lazySink.md)
* [lazyFutureSink](Sink/lazyFutureSink.md)
* [completionStageSink](Sink/completionStageSink.md)
* [lazyCompletionStageSink](Sink/lazyCompletionStageSink.md)
* [fromInputStream](StreamConverters/fromInputStream.md)
* [asOutputStream](StreamConverters/asOutputStream.md)
* [fromOutputStream](StreamConverters/fromOutputStream.md)

View file

@ -0,0 +1,44 @@
/*
* Copyright (C) 2009-2019 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.stream.operators.flow;
import akka.NotUsed;
import akka.actor.ActorSystem;
import akka.stream.javadsl.Flow;
import akka.stream.javadsl.Source;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
public class FutureFlow {
private ActorSystem system = null;
// #base-on-first-element
CompletionStage<Flow<Integer, String, NotUsed>> processingFlow(int id) {
return CompletableFuture.completedFuture(
Flow.of(Integer.class).map(n -> "id: " + id + " value: " + n));
}
// #base-on-first-element
public void compileOnlyBaseOnFirst() {
// #base-on-first-element
Source<String, NotUsed> source =
Source.range(1, 10)
.prefixAndTail(1)
.flatMapConcat(
(pair) -> {
List<Integer> head = pair.first();
Source<Integer, NotUsed> tail = pair.second();
int id = head.get(0);
return tail.via(Flow.completionStageFlow(processingFlow(id)));
});
// #base-on-first-element
}
}

View file

@ -20,7 +20,7 @@ object SourceOperators {
import scala.concurrent.Future
val source: Source[Int, NotUsed] = Source.fromFuture(Future.successful(10))
val source: Source[Int, NotUsed] = Source.future(Future.successful(10))
val sink: Sink[Int, Future[Done]] = Sink.foreach((i: Int) => println(i))
val done: Future[Done] = source.runWith(sink) //10

View file

@ -0,0 +1,35 @@
/*
* Copyright (C) 2009-2019 Lightbend Inc. <https://www.lightbend.com>
*/
package docs.stream.operators.flow
import akka.NotUsed
import akka.actor.ActorSystem
import akka.stream.scaladsl.Flow
import akka.stream.scaladsl.Source
import scala.concurrent.Future
class FutureFlow {
implicit val system: ActorSystem = ???
import system.dispatcher
def compileOnlyBaseOnFirst(): Unit = {
// #base-on-first-element
def processingFlow(id: Int): Future[Flow[Int, String, NotUsed]] =
Future {
Flow[Int].map(n => s"id: $id, value: $n")
}
val source: Source[String, NotUsed] =
Source(1 to 10).prefixAndTail(1).flatMapConcat {
case (List(id), tail) =>
// base the Future flow creation on the first element
tail.via(Flow.futureFlow(processingFlow(id)))
}
// #base-on-first-element
}
}

View file

@ -150,7 +150,7 @@ private[remote] class ArteryTcpTransport(
def flow(controlIdleKillSwitch: OptionVal[SharedKillSwitch]) =
Flow[ByteString]
.via(Flow.lazyInitAsync(() => {
.via(Flow.lazyFlow(() => {
// only open the actual connection if any new messages are sent
afr.loFreq(
TcpOutbound_Connected,
@ -158,10 +158,8 @@ private[remote] class ArteryTcpTransport(
s"/ ${streamName(streamId)}")
if (controlIdleKillSwitch.isDefined)
outboundContext.asInstanceOf[Association].setControlIdleKillSwitch(controlIdleKillSwitch)
Future.successful(
Flow[ByteString]
.prepend(Source.single(TcpFraming.encodeConnectionHeader(streamId)))
.via(connectionFlow))
Flow[ByteString].prepend(Source.single(TcpFraming.encodeConnectionHeader(streamId))).via(connectionFlow)
}))
.recoverWithRetries(1, { case ArteryTransport.ShutdownSignal => Source.empty })
.log(name = s"outbound connection to [${outboundContext.remoteAddress}], ${streamName(streamId)} stream")

View file

@ -14,7 +14,7 @@ class FuturePublisherTest extends AkkaPublisherVerification[Int] {
def createPublisher(elements: Long): Publisher[Int] = {
val p = Promise[Int]()
val pub = Source.fromFuture(p.future).runWith(Sink.asPublisher(false))
val pub = Source.future(p.future).runWith(Sink.asPublisher(false))
p.success(0)
pub
}

View file

@ -0,0 +1,67 @@
/*
* Copyright (C) 2019 Lightbend Inc. <https://www.lightbend.com>
*/
package akka.stream.javadsl;
import akka.NotUsed;
import akka.stream.StreamTest;
import akka.testkit.AkkaJUnitActorSystemResource;
import akka.testkit.AkkaSpec;
import org.junit.ClassRule;
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals;
public class LazyAndFutureFlowTest extends StreamTest {
@ClassRule
public static AkkaJUnitActorSystemResource actorSystemResource =
new AkkaJUnitActorSystemResource("LazyAndFutureFlowTest", AkkaSpec.testConf());
public LazyAndFutureFlowTest() {
super(actorSystemResource);
}
// note these are minimal happy path tests to cover API, more thorough tests are on the Scala side
@Test
public void completionStageFlow() throws Exception {
CompletionStage<List<String>> result =
Source.single("one")
.via(
Flow.completionStageFlow(
CompletableFuture.completedFuture(Flow.fromFunction(str -> str))))
.runWith(Sink.seq(), system);
assertEquals(Arrays.asList("one"), result.toCompletableFuture().get(3, TimeUnit.SECONDS));
}
@Test
public void lazyFlow() throws Exception {
CompletionStage<List<String>> result =
Source.single("one")
.via(Flow.lazyFlow(() -> Flow.fromFunction(str -> str)))
.runWith(Sink.seq(), system);
assertEquals(Arrays.asList("one"), result.toCompletableFuture().get(3, TimeUnit.SECONDS));
}
@Test
public void lazyCompletionStageFlow() throws Exception {
CompletionStage<List<String>> result =
Source.single("one")
.via(
Flow.lazyCompletionStageFlow(
() -> CompletableFuture.completedFuture(Flow.fromFunction(str -> str))))
.runWith(Sink.seq(), system);
assertEquals(Arrays.asList("one"), result.toCompletableFuture().get(3, TimeUnit.SECONDS));
}
}

View file

@ -0,0 +1,106 @@
/*
* Copyright (C) 2009-2019 Lightbend Inc. <https://www.lightbend.com>
*/
package akka.stream.javadsl;
import akka.Done;
import akka.NotUsed;
import akka.japi.Pair;
import akka.stream.StreamTest;
import akka.testkit.AkkaJUnitActorSystemResource;
import akka.testkit.AkkaSpec;
import org.junit.ClassRule;
import org.junit.Test;
import scala.concurrent.Future;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals;
public class LazyAndFutureSourcesTest extends StreamTest {
@ClassRule
public static AkkaJUnitActorSystemResource actorSystemResource =
new AkkaJUnitActorSystemResource("LazyAndFutureSourcesTest", AkkaSpec.testConf());
public LazyAndFutureSourcesTest() {
super(actorSystemResource);
}
// note these are minimal happy path tests to cover API, more thorough tests are on the Scala side
@Test
public void future() throws Exception {
CompletionStage<List<String>> result =
Source.future(Future.successful("one")).runWith(Sink.seq(), system);
assertEquals(Arrays.asList("one"), result.toCompletableFuture().get(3, TimeUnit.SECONDS));
}
@Test
public void completionStage() throws Exception {
CompletionStage<String> one = CompletableFuture.completedFuture("one");
CompletionStage<List<String>> result = Source.completionStage(one).runWith(Sink.seq(), system);
assertEquals(Arrays.asList("one"), result.toCompletableFuture().get(3, TimeUnit.SECONDS));
}
@Test
public void completionStageSource() throws Exception {
Pair<CompletionStage<NotUsed>, CompletionStage<List<String>>> result =
Source.completionStageSource(CompletableFuture.completedFuture(Source.single("one")))
.toMat(Sink.seq(), Keep.both())
.run(system);
CompletionStage<NotUsed> nestedMatVal = result.first();
CompletionStage<List<String>> list = result.second();
assertEquals(Arrays.asList("one"), list.toCompletableFuture().get(3, TimeUnit.SECONDS));
assertEquals(true, nestedMatVal.toCompletableFuture().isDone());
}
@Test
public void lazySingle() throws Exception {
CompletionStage<List<String>> list = Source.lazySingle(() -> "one").runWith(Sink.seq(), system);
assertEquals(Arrays.asList("one"), list.toCompletableFuture().get(3, TimeUnit.SECONDS));
}
@Test
public void lazyCompletionStage() throws Exception {
CompletionStage<List<String>> list =
Source.lazyCompletionStage(() -> CompletableFuture.completedFuture("one"))
.runWith(Sink.seq(), system);
assertEquals(Arrays.asList("one"), list.toCompletableFuture().get(3, TimeUnit.SECONDS));
}
@Test
public void lazySource() throws Exception {
Pair<CompletionStage<NotUsed>, CompletionStage<List<String>>> result =
Source.lazySource(() -> Source.single("one")).toMat(Sink.seq(), Keep.both()).run(system);
CompletionStage<NotUsed> nestedMatVal = result.first();
CompletionStage<List<String>> list = result.second();
assertEquals(Arrays.asList("one"), list.toCompletableFuture().get(3, TimeUnit.SECONDS));
assertEquals(true, nestedMatVal.toCompletableFuture().isDone());
}
@Test
public void lazyCompletionStageSource() throws Exception {
Pair<CompletionStage<NotUsed>, CompletionStage<List<String>>> result =
Source.lazyCompletionStageSource(
() -> CompletableFuture.completedFuture(Source.single("one")))
.toMat(Sink.seq(), Keep.both())
.run(system);
CompletionStage<NotUsed> nestedMatVal = result.first();
CompletionStage<List<String>> list = result.second();
assertEquals(Arrays.asList("one"), list.toCompletableFuture().get(3, TimeUnit.SECONDS));
assertEquals(true, nestedMatVal.toCompletableFuture().isDone());
}
}

View file

@ -21,7 +21,16 @@ class DslFactoriesConsistencySpec extends WordSpec with Matchers {
"toString",
"getClass",
"shape",
"identityTraversalBuilder")
"identityTraversalBuilder",
// futures in scaladsl vs completion stage in javadsl
"lazyFutureSource", // lazyCompletionStageSource
"futureSource", // completionStageSource
"lazyFuture", // lazyCompletionStage
"lazyFutureFlow", // lazyCompletionStageFlow
"futureFlow", // completionStageFlow
"futureSink", // completionStageSink
"lazyFutureSink" // lazyCompletionStageSink
)
val javaIgnore =
Set("adapt") // the scaladsl -> javadsl bridge

View file

@ -120,7 +120,7 @@ class FlowConcatSpec extends BaseTwoStreamsSetup {
"correctly handle async errors in secondary upstream" in assertAllStagesStopped {
val promise = Promise[Int]()
val subscriber = TestSubscriber.manualProbe[Int]()
Source(List(1, 2, 3)).concat(Source.fromFuture(promise.future)).runWith(Sink.fromSubscriber(subscriber))
Source(List(1, 2, 3)).concat(Source.future(promise.future)).runWith(Sink.fromSubscriber(subscriber))
val subscription = subscriber.expectSubscription()
subscription.request(4)

View file

@ -27,7 +27,7 @@ class FlowFlattenMergeSpec extends StreamSpec {
import system.dispatcher
def src10(i: Int) = Source(i until (i + 10))
def blocked = Source.fromFuture(Promise[Int].future)
def blocked = Source.future(Promise[Int].future)
val toSeq = Flow[Int].grouped(1000).toMat(Sink.head)(Keep.right)
val toSet = toSeq.mapMaterializedValue(_.map(_.toSet))
@ -108,7 +108,7 @@ class FlowFlattenMergeSpec extends StreamSpec {
val p1, p2 = TestPublisher.probe[Int]()
val ex = new Exception("buh")
val p = Promise[Source[Int, NotUsed]]
(Source(List(Source.fromPublisher(p1), Source.fromPublisher(p2))) ++ Source.fromFuture(p.future))
(Source(List(Source.fromPublisher(p1), Source.fromPublisher(p2))) ++ Source.future(p.future))
.flatMapMerge(5, identity)
.runWith(Sink.head)
p1.expectRequest()
@ -122,7 +122,7 @@ class FlowFlattenMergeSpec extends StreamSpec {
val p1, p2 = TestPublisher.probe[Int]()
val ex = new Exception("buh")
val p = Promise[Int]
Source(List(Source.fromPublisher(p1), Source.fromPublisher(p2), Source.fromFuture(p.future)))
Source(List(Source.fromPublisher(p1), Source.fromPublisher(p2), Source.future(p.future)))
.flatMapMerge(5, identity)
.runWith(Sink.head)
p1.expectRequest()

View file

@ -6,12 +6,14 @@ package akka.stream.scaladsl
import akka.stream.testkit._
import akka.stream.testkit.scaladsl.StreamTestKit._
import com.github.ghik.silencer.silent
import scala.concurrent.duration._
import scala.concurrent.Future
import scala.concurrent.Promise
import scala.util.control.NoStackTrace
@silent("deprecated") // testing deprecated API
class FlowFromFutureSpec extends StreamSpec {
"A Flow based on a Future" must {

View file

@ -1,206 +0,0 @@
/*
* Copyright (C) 2015-2019 Lightbend Inc. <https://www.lightbend.com>
*/
package akka.stream.scaladsl
import java.util.concurrent.{ CompletableFuture, TimeUnit }
import akka.stream._
import akka.stream.stage.{ GraphStageLogic, GraphStageWithMaterializedValue }
import akka.stream.testkit.Utils.TE
import akka.stream.testkit.{ StreamSpec, TestPublisher, TestSubscriber }
import akka.stream.testkit.scaladsl.StreamTestKit._
import akka.testkit.TestLatch
import scala.concurrent.{ Await, Future, Promise }
class FutureFlattenSourceSpec extends StreamSpec {
implicit def ec = system.dispatcher
"Future source" must {
val underlying: Source[Int, String] =
Source(List(1, 2, 3)).mapMaterializedValue(_ => "foo")
"emit the elements of the already successful future source" in assertAllStagesStopped {
val (sourceMatVal, sinkMatVal) =
Source.fromFutureSource(Future.successful(underlying)).toMat(Sink.seq)(Keep.both).run()
// should complete as soon as inner source has been materialized
sourceMatVal.futureValue should ===("foo")
sinkMatVal.futureValue should ===(List(1, 2, 3))
}
"emit no elements before the future of source successful" in assertAllStagesStopped {
val c = TestSubscriber.manualProbe[Int]()
val sourcePromise = Promise[Source[Int, String]]()
Source.fromFutureSource(sourcePromise.future).runWith(Sink.asPublisher(true)).subscribe(c)
val sub = c.expectSubscription()
import scala.concurrent.duration._
c.expectNoMessage(100.millis)
sub.request(3)
c.expectNoMessage(100.millis)
sourcePromise.success(underlying)
c.expectNext(1)
c.expectNext(2)
c.expectNext(3)
c.expectComplete()
}
"emit the elements of the future source" in assertAllStagesStopped {
val sourcePromise = Promise[Source[Int, String]]()
val (sourceMatVal, sinkMatVal) =
Source.fromFutureSource(sourcePromise.future).toMat(Sink.seq)(Keep.both).run()
sourcePromise.success(underlying)
// should complete as soon as inner source has been materialized
sourceMatVal.futureValue should ===("foo")
sinkMatVal.futureValue should ===(List(1, 2, 3))
}
"emit the elements from a source in a completion stage" in assertAllStagesStopped {
val (sourceMatVal, sinkMatVal) =
Source
.fromSourceCompletionStage(
// can't be inferred
CompletableFuture.completedFuture[Graph[SourceShape[Int], String]](underlying))
.toMat(Sink.seq)(Keep.both)
.run()
sourceMatVal.toCompletableFuture.get(remainingOrDefault.toMillis, TimeUnit.MILLISECONDS) should ===("foo")
sinkMatVal.futureValue should ===(List(1, 2, 3))
}
"handle downstream cancelling before the underlying Future completes" in assertAllStagesStopped {
val sourcePromise = Promise[Source[Int, String]]()
val probe = TestSubscriber.probe[Int]()
val sourceMatVal =
Source.fromFutureSource(sourcePromise.future).toMat(Sink.fromSubscriber(probe))(Keep.left).run()
// wait for cancellation to occur
probe.ensureSubscription()
probe.request(1)
probe.cancel()
// try to avoid a race between probe cancel and completing the promise
Thread.sleep(100)
// even though canceled the underlying matval should arrive
sourcePromise.success(underlying)
val failure = sourceMatVal.failed.futureValue
failure shouldBe a[StreamDetachedException]
failure.getMessage should ===("Stream cancelled before Source Future completed")
}
"fail if the underlying Future is failed" in assertAllStagesStopped {
val failure = TE("foo")
val underlying = Future.failed[Source[Int, String]](failure)
val (sourceMatVal, sinkMatVal) = Source.fromFutureSource(underlying).toMat(Sink.seq)(Keep.both).run()
sourceMatVal.failed.futureValue should ===(failure)
sinkMatVal.failed.futureValue should ===(failure)
}
"fail as the underlying Future fails after outer source materialization" in assertAllStagesStopped {
val failure = TE("foo")
val sourcePromise = Promise[Source[Int, String]]()
val materializationLatch = TestLatch(1)
val (sourceMatVal, sinkMatVal) =
Source
.fromFutureSource(sourcePromise.future)
.mapMaterializedValue { value =>
materializationLatch.countDown()
value
}
.toMat(Sink.seq)(Keep.both)
.run()
// we don't know that materialization completed yet (this is still a bit racy)
Await.ready(materializationLatch, remainingOrDefault)
Thread.sleep(100)
sourcePromise.failure(failure)
sourceMatVal.failed.futureValue should ===(failure)
sinkMatVal.failed.futureValue should ===(failure)
}
"fail as the underlying Future fails after outer source materialization with no demand" in assertAllStagesStopped {
val failure = TE("foo")
val sourcePromise = Promise[Source[Int, String]]()
val testProbe = TestSubscriber.probe[Int]()
val sourceMatVal =
Source.fromFutureSource(sourcePromise.future).to(Sink.fromSubscriber(testProbe)).run()
testProbe.expectSubscription()
sourcePromise.failure(failure)
sourceMatVal.failed.futureValue should ===(failure)
}
"handle back-pressure when the future completes" in assertAllStagesStopped {
val subscriber = TestSubscriber.probe[Int]()
val publisher = TestPublisher.probe[Int]()
val sourcePromise = Promise[Source[Int, String]]()
val matVal = Source.fromFutureSource(sourcePromise.future).to(Sink.fromSubscriber(subscriber)).run()
subscriber.ensureSubscription()
sourcePromise.success(Source.fromPublisher(publisher).mapMaterializedValue(_ => "woho"))
// materialized value completes but still no demand
matVal.futureValue should ===("woho")
// then demand and let an element through to see it works
subscriber.ensureSubscription()
subscriber.request(1)
publisher.expectRequest()
publisher.sendNext(1)
subscriber.expectNext(1)
publisher.sendComplete()
subscriber.expectComplete()
}
"carry through cancellation to later materialized source" in assertAllStagesStopped {
val subscriber = TestSubscriber.probe[Int]()
val publisher = TestPublisher.probe[Int]()
val sourcePromise = Promise[Source[Int, String]]()
val matVal = Source.fromFutureSource(sourcePromise.future).to(Sink.fromSubscriber(subscriber)).run()
subscriber.ensureSubscription()
sourcePromise.success(Source.fromPublisher(publisher).mapMaterializedValue(_ => "woho"))
// materialized value completes but still no demand
matVal.futureValue should ===("woho")
// cancelling the outer source should carry through to the internal one
subscriber.ensureSubscription()
subscriber.cancel()
publisher.expectCancellation()
}
class FailingMatGraphStage extends GraphStageWithMaterializedValue[SourceShape[Int], String] {
val out = Outlet[Int]("whatever")
override val shape: SourceShape[Int] = SourceShape(out)
override def createLogicAndMaterializedValue(inheritedAttributes: Attributes): (GraphStageLogic, String) = {
throw TE("INNER_FAILED")
}
}
"fail when the future source materialization fails" in assertAllStagesStopped {
val inner = Future.successful(Source.fromGraph(new FailingMatGraphStage))
val (innerSourceMat: Future[String], outerSinkMat: Future[Seq[Int]]) =
Source.fromFutureSource(inner).toMat(Sink.seq)(Keep.both).run()
outerSinkMat.failed.futureValue should ===(TE("INNER_FAILED"))
innerSourceMat.failed.futureValue should ===(TE("INNER_FAILED"))
}
}
}

View file

@ -137,7 +137,7 @@ class GraphConcatSpec extends TwoStreamsSetup {
.fromGraph(GraphDSL.create() { implicit b =>
val concat = b.add(Concat[Int]())
Source(List(1, 2, 3)) ~> concat.in(0)
Source.fromFuture(promise.future) ~> concat.in(1)
Source.future(promise.future) ~> concat.in(1)
concat.out ~> Sink.fromSubscriber(subscriber)
ClosedShape
})

View file

@ -11,10 +11,12 @@ import akka.stream.testkit.scaladsl.StreamTestKit._
import akka.stream.testkit.StreamSpec
import akka.stream.testkit.TestSubscriber
import akka.testkit.DefaultTimeout
import com.github.ghik.silencer.silent
import org.scalatest.concurrent.ScalaFutures
import scala.concurrent.Future
@silent("deprecated") // tests deprecated methods
class LazilyAsyncSpec extends StreamSpec with DefaultTimeout with ScalaFutures {
import system.dispatcher

View file

@ -5,18 +5,24 @@
package akka.stream.scaladsl
import akka.NotUsed
import akka.stream.AbruptStageTerminationException
import akka.stream.Materializer
import akka.stream.NeverMaterializedException
import akka.stream.testkit.StreamSpec
import akka.stream.testkit.TestPublisher
import akka.stream.testkit.Utils._
import akka.stream.testkit.scaladsl.StreamTestKit._
import akka.stream.testkit.scaladsl.TestSink
import akka.stream.testkit.scaladsl.TestSource
import akka.stream.testkit.StreamSpec
import akka.stream.testkit.TestPublisher
import org.scalatest.concurrent.ScalaFutures
import akka.testkit.TestProbe
import com.github.ghik.silencer.silent
import scala.concurrent.duration._
import scala.collection.immutable
import scala.concurrent.Future
import scala.concurrent.Promise
import scala.concurrent.duration._
@silent("deprecated") // tests deprecated API as well
class LazyFlowSpec extends StreamSpec("""
akka.stream.materializer.initial-input-buffer-size = 1
akka.stream.materializer.max-input-buffer-size = 1
@ -24,7 +30,184 @@ class LazyFlowSpec extends StreamSpec("""
val ex = TE("")
"A LazyFlow" must {
"Flow.lazyFlow" must {
// more complete test coverage is for lazyFutureFlow since this is composition of that
"work in the happy case" in assertAllStagesStopped {
val result: (Future[NotUsed], Future[immutable.Seq[String]]) =
Source(List(1, 2, 3))
.viaMat(Flow.lazyFlow(() => Flow.fromFunction((n: Int) => n.toString)))(Keep.right)
.toMat(Sink.seq)(Keep.both)
.run()
val deferredMatVal = result._1
val list = result._2
list.futureValue should equal(Seq("1", "2", "3"))
deferredMatVal.isCompleted should ===(true)
}
}
"Flow.futureFlow" must {
// more complete test coverage is for lazyFutureFlow since this is composition of that
"work in the happy case" in assertAllStagesStopped {
val result: (Future[NotUsed], Future[immutable.Seq[String]]) =
Source(List(1, 2, 3))
.viaMat(Flow.futureFlow(Future.successful(Flow.fromFunction((n: Int) => n.toString))))(Keep.right)
.toMat(Sink.seq)(Keep.both)
.run()
val deferredMatVal = result._1
val list = result._2
list.futureValue should equal(Seq("1", "2", "3"))
deferredMatVal.isCompleted should ===(true)
}
}
"Flow.lazyFutureFlow" must {
"work in the happy case" in assertAllStagesStopped {
val result: (Future[NotUsed], Future[immutable.Seq[String]]) =
Source(List(1, 2, 3))
.viaMat(Flow.lazyFutureFlow(() => Future.successful(Flow.fromFunction((n: Int) => n.toString))))(Keep.right)
.toMat(Sink.seq)(Keep.both)
.run()
val deferredMatVal = result._1
val list = result._2
list.futureValue should equal(Seq("1", "2", "3"))
deferredMatVal.isCompleted should ===(true)
}
"complete without creating internal flow when there was no elements in the stream" in assertAllStagesStopped {
val probe = TestProbe()
val result: (Future[NotUsed], Future[immutable.Seq[Int]]) = Source
.empty[Int]
.viaMat(Flow.lazyFutureFlow { () =>
probe.ref ! "constructed"
Future.successful(Flow[Int])
})(Keep.right)
.toMat(Sink.seq)(Keep.both)
.run()
val deferredMatVal = result._1
val list = result._2
list.futureValue should equal(Seq.empty)
// and failing the matval
deferredMatVal.failed.futureValue shouldBe a[NeverMaterializedException]
probe.expectNoMessage(30.millis) // would have gotten it by now
}
"complete without creating internal flow when the stream failed with no elements" in assertAllStagesStopped {
val probe = TestProbe()
val result: (Future[NotUsed], Future[immutable.Seq[Int]]) = Source
.failed[Int](TE("no-elements"))
.viaMat(Flow.lazyFutureFlow { () =>
probe.ref ! "constructed"
Future.successful(Flow[Int])
})(Keep.right)
.toMat(Sink.seq)(Keep.both)
.run()
val deferredMatVal = result._1
val list = result._2
list.failed.futureValue shouldBe a[TE]
// and failing the matval
deferredMatVal.failed.futureValue shouldBe a[NeverMaterializedException]
probe.expectNoMessage(30.millis) // would have gotten it by now
}
"fail the flow when the factory function fails" in assertAllStagesStopped {
val result: (Future[NotUsed], Future[immutable.Seq[String]]) =
Source(List(1, 2, 3))
.viaMat(Flow.lazyFutureFlow(() => throw TE("no-flow-for-you")))(Keep.right)
.toMat(Sink.seq)(Keep.both)
.run()
val deferredMatVal = result._1
val list = result._2
list.failed.futureValue shouldBe a[TE]
deferredMatVal.failed.futureValue shouldBe a[TE]
}
"fail the flow when the future is initially failed" in assertAllStagesStopped {
val result: (Future[NotUsed], Future[immutable.Seq[String]]) =
Source(List(1, 2, 3))
.viaMat(Flow.lazyFutureFlow(() => Future.failed(TE("no-flow-for-you"))))(Keep.right)
.toMat(Sink.seq)(Keep.both)
.run()
val deferredMatVal = result._1
val list = result._2
list.failed.futureValue shouldBe a[TE]
deferredMatVal.failed.futureValue shouldBe a[TE]
}
"fail the flow when the future is failed after the fact" in assertAllStagesStopped {
val promise = Promise[Flow[Int, String, NotUsed]]()
val result: (Future[NotUsed], Future[immutable.Seq[String]]) =
Source(List(1, 2, 3))
.viaMat(Flow.lazyFutureFlow(() => promise.future))(Keep.right)
.toMat(Sink.seq)(Keep.both)
.run()
val deferredMatVal = result._1
val list = result._2
promise.failure(TE("later-no-flow-for-you"))
list.failed.futureValue shouldBe a[TE]
deferredMatVal.failed.futureValue shouldBe a[TE]
}
"fail the flow when the future materialization fails" in assertAllStagesStopped {
val result: (Future[NotUsed], Future[immutable.Seq[String]]) =
Source(List(1, 2, 3))
.viaMat(Flow.lazyFutureFlow(() =>
Future.successful(Flow[Int].map(_.toString).mapMaterializedValue(_ => throw TE("mat-failed")))))(Keep.right)
.toMat(Sink.seq)(Keep.both)
.run()
val deferredMatVal = result._1
val list = result._2
list.failed.futureValue shouldBe a[TE]
deferredMatVal.failed.futureValue shouldBe a[TE]
}
"fail the flow when there was elements but the inner flow failed" in assertAllStagesStopped {
val result: (Future[NotUsed], Future[immutable.Seq[String]]) =
Source(List(1, 2, 3))
.viaMat(Flow.lazyFutureFlow(() => Future.successful(Flow[Int].map(_ => throw TE("inner-stream-fail")))))(
Keep.right)
.toMat(Sink.seq)(Keep.both)
.run()
val deferredMatVal = result._1
val list = result._2
list.failed.futureValue shouldBe a[TE]
deferredMatVal.futureValue should ===(NotUsed) // inner materialization did succeed
}
"fail the mat val when the stream is abruptly terminated before it got materialized" in assertAllStagesStopped {
val expendableMaterializer = Materializer(system)
val promise = Promise[Flow[Int, String, NotUsed]]()
val result: (Future[NotUsed], Future[immutable.Seq[String]]) =
Source
.maybe[Int]
.viaMat(Flow.lazyFutureFlow(() => promise.future))(Keep.right)
.toMat(Sink.seq)(Keep.both)
.run()(expendableMaterializer)
val deferredMatVal = result._1
val list = result._2
expendableMaterializer.shutdown()
list.failed.futureValue shouldBe an[AbruptStageTerminationException]
deferredMatVal.failed.futureValue shouldBe an[AbruptStageTerminationException]
}
}
"The deprecated LazyFlow ops" must {
def mapF(e: Int): () => Future[Flow[Int, String, NotUsed]] =
() => Future.successful(Flow.fromFunction[Int, String](i => (i * e).toString))
val flowF = Future.successful(Flow[Int])
@ -91,7 +274,7 @@ class LazyFlowSpec extends StreamSpec("""
sub.expectComplete()
}
"fail gracefully when flow factory method failed" in assertAllStagesStopped {
"fail gracefully when flow factory function failed" in assertAllStagesStopped {
val sourceProbe = TestPublisher.manualProbe[Int]()
val probe = Source
.fromPublisher(sourceProbe)
@ -147,20 +330,6 @@ class LazyFlowSpec extends StreamSpec("""
probe.cancel()
sourceSub.expectCancellation()
}
"fail correctly when factory throw error" in assertAllStagesStopped {
val msg = "fail!"
val matFail = TE(msg)
val result = Source
.single("whatever")
.viaMat(Flow.lazyInitAsync(() => throw matFail))(Keep.right)
.toMat(Sink.ignore)(Keep.left)
.run()
ScalaFutures.whenReady(result.failed) { e =>
e.getMessage shouldBe msg
}
}
}
}

View file

@ -16,6 +16,7 @@ import akka.stream.testkit.TestSubscriber.Probe
import akka.stream.testkit.Utils._
import akka.stream.testkit.scaladsl.StreamTestKit._
import akka.stream.testkit.scaladsl.TestSink
import com.github.ghik.silencer.silent
import scala.collection.immutable
import scala.concurrent.Await
@ -23,6 +24,7 @@ import scala.concurrent.Future
import scala.concurrent.Promise
import scala.concurrent.duration._
@silent("deprecated")
class LazySinkSpec extends StreamSpec("""
akka.stream.materializer.initial-input-buffer-size = 1
akka.stream.materializer.max-input-buffer-size = 1

View file

@ -6,8 +6,7 @@ package akka.stream.scaladsl
import java.util.concurrent.atomic.AtomicBoolean
import akka.Done
import akka.stream.impl.LazySource
import akka.stream._
import akka.stream.stage.GraphStage
import akka.stream.stage.GraphStageLogic
import akka.stream.testkit.Utils.TE
@ -15,18 +14,112 @@ import akka.stream.testkit.scaladsl.StreamTestKit._
import akka.stream.testkit.StreamSpec
import akka.stream.testkit.TestPublisher
import akka.stream.testkit.TestSubscriber
import akka.stream.{ Attributes, KillSwitches, Outlet, SourceShape }
import akka.testkit.{ DefaultTimeout, TestProbe }
import akka.testkit.DefaultTimeout
import akka.testkit.TestProbe
import akka.Done
import akka.NotUsed
import org.scalatest.concurrent.ScalaFutures
import scala.collection.immutable.Seq
import scala.concurrent.Future
import scala.concurrent.Promise
class LazySourceSpec extends StreamSpec with DefaultTimeout with ScalaFutures {
"A lazy source" should {
import system.dispatcher
"Source.lazySingle" must {
"work like a normal source, happy path" in assertAllStagesStopped {
val result = Source.fromGraph(LazySource(() => Source(List(1, 2, 3)))).runWith(Sink.seq)
val seq = Source.lazySingle(() => 1).runWith(Sink.seq)
seq.futureValue should ===(Seq(1))
}
"never construct the source when there was no demand" in assertAllStagesStopped {
val probe = TestSubscriber.probe[Int]()
val constructed = new AtomicBoolean(false)
Source
.lazySingle { () =>
constructed.set(true)
1
}
.toMat(Sink.fromSubscriber(probe))(Keep.left)
.run()
probe.cancel()
constructed.get() should ===(false)
}
"fail correctly when factory function fails" in assertAllStagesStopped {
val failure = TE("couldn't create")
val termination: Future[Done] =
Source.lazySingle(() => throw failure).watchTermination()(Keep.right).toMat(Sink.ignore)(Keep.left).run()
termination.failed.futureValue should ===(failure)
}
}
"Source.lazyFuture" must {
"work like a normal source, happy path, already completed future" in assertAllStagesStopped {
val seq = Source.lazyFuture(() => Future.successful(1)).runWith(Sink.seq)
seq.futureValue should ===(Seq(1))
}
"work like a normal source, happy path, completing future" in assertAllStagesStopped {
val promise = Promise[Int]()
val seq = Source.lazyFuture(() => promise.future).runWith(Sink.seq)
promise.success(1)
seq.futureValue should ===(Seq(1))
}
"never construct the source when there was no demand" in assertAllStagesStopped {
val probe = TestSubscriber.probe[Int]()
val constructed = new AtomicBoolean(false)
Source
.lazySingle { () =>
constructed.set(true)
1
}
.runWith(Sink.fromSubscriber(probe))
probe.cancel()
constructed.get() should ===(false)
}
"fail correctly when factory function fails" in assertAllStagesStopped {
val failure = TE("couldn't create")
val termination =
Source.lazyFuture(() => throw failure).watchTermination()(Keep.right).toMat(Sink.ignore)(Keep.left).run()
termination.failed.futureValue should ===(failure)
}
"fail correctly when factory function returns a failed future" in assertAllStagesStopped {
val failure = TE("couldn't create")
val termination =
Source
.lazyFuture(() => Future.failed(failure))
.watchTermination()(Keep.right)
.toMat(Sink.ignore)(Keep.left)
.run()
termination.failed.futureValue should ===(failure)
}
"fail correctly when factory function returns a future that fails" in assertAllStagesStopped {
val failure = TE("couldn't create")
val promise = Promise[Int]()
val termination =
Source.lazyFuture(() => promise.future).watchTermination()(Keep.right).toMat(Sink.ignore)(Keep.left).run()
promise.failure(failure)
termination.failed.futureValue should ===(failure)
}
}
"Source.lazySource" must {
"work like a normal source, happy path" in assertAllStagesStopped {
val result = Source.lazySource(() => Source(List(1, 2, 3))).runWith(Sink.seq)
result.futureValue should ===(Seq(1, 2, 3))
}
@ -34,29 +127,29 @@ class LazySourceSpec extends StreamSpec with DefaultTimeout with ScalaFutures {
"never construct the source when there was no demand" in assertAllStagesStopped {
val probe = TestSubscriber.probe[Int]()
val constructed = new AtomicBoolean(false)
Source
.fromGraph(LazySource { () =>
val result = Source
.lazySource { () =>
constructed.set(true); Source(List(1, 2, 3))
})
.runWith(Sink.fromSubscriber(probe))
}
.toMat(Sink.fromSubscriber(probe))(Keep.left)
.run()
probe.cancel()
constructed.get() should ===(false)
result.isCompleted should ===(false)
}
"fail the materialized value when downstream cancels without ever consuming any element" in assertAllStagesStopped {
val matF = Source.fromGraph(LazySource(() => Source(List(1, 2, 3)))).toMat(Sink.cancelled)(Keep.left).run()
val lazyMatVal = Source.lazySource(() => Source(List(1, 2, 3))).toMat(Sink.cancelled)(Keep.left).run()
intercept[RuntimeException] {
matF.futureValue
}
lazyMatVal.failed.futureValue shouldBe a[NeverMaterializedException]
}
"stop consuming when downstream has cancelled" in assertAllStagesStopped {
val outProbe = TestSubscriber.probe[Int]()
val inProbe = TestPublisher.probe[Int]()
Source.fromGraph(LazySource(() => Source.fromPublisher(inProbe))).runWith(Sink.fromSubscriber(outProbe))
Source.lazySource(() => Source.fromPublisher(inProbe)).runWith(Sink.fromSubscriber(outProbe))
outProbe.request(1)
inProbe.expectRequest()
@ -70,9 +163,9 @@ class LazySourceSpec extends StreamSpec with DefaultTimeout with ScalaFutures {
val probe = TestSubscriber.probe[Int]()
val matF: Future[Done] = Source
.fromGraph(LazySource { () =>
.lazySource { () =>
Source(List(1, 2, 3)).mapMaterializedValue(_ => Done)
})
}
.to(Sink.fromSubscriber(probe))
.run()
@ -84,11 +177,60 @@ class LazySourceSpec extends StreamSpec with DefaultTimeout with ScalaFutures {
probe.cancel()
}
"fail stage when upstream fails" in assertAllStagesStopped {
val outProbe = TestSubscriber.probe[Int]()
val inProbe = TestPublisher.probe[Int]()
val lazyMatVal =
Source.lazySource(() => Source.fromPublisher(inProbe)).toMat(Sink.fromSubscriber(outProbe))(Keep.left).run()
outProbe.request(1)
inProbe.expectRequest()
lazyMatVal.futureValue should ===(NotUsed) // was completed
inProbe.sendNext(27)
outProbe.expectNext(27)
val failure = TE("OMG Who set that on fire!?!")
inProbe.sendError(failure)
outProbe.expectError() should ===(failure)
}
"fail when lazy source is failed" in assertAllStagesStopped {
val failure = TE("OMG Who set that on fire!?!")
val result = Source.lazySource(() => Source.failed(failure)).runWith(Sink.seq)
result.failed.futureValue should ===(failure)
}
"fail correctly when factory function fails" in assertAllStagesStopped {
val failure = TE("couldn't create")
val lazyMatVal = Source.lazySource(() => throw failure).toMat(Sink.ignore)(Keep.left).run()
lazyMatVal.failed.futureValue should ===(failure)
}
"fail correctly when materialization of inner source fails" in assertAllStagesStopped {
val matFail = TE("fail!")
object FailingInnerMat extends GraphStage[SourceShape[String]] {
val out = Outlet[String]("out")
val shape = SourceShape(out)
override def createLogic(inheritedAttributes: Attributes): GraphStageLogic = new GraphStageLogic(shape) {
throw matFail
}
}
val (lazyMatVal, done) =
Source.lazySource(() => Source.fromGraph(FailingInnerMat)).toMat(Sink.ignore)(Keep.both).run()
done.failed.futureValue should ===(matFail)
lazyMatVal.failed.futureValue should ===(matFail)
}
"propagate downstream cancellation cause when inner source has been materialized" in {
val probe = TestProbe()
val (doneF, killswitch) =
Source
.lazily(() =>
.lazySource(() =>
Source.maybe[Int].watchTermination()(Keep.right).mapMaterializedValue { done =>
probe.ref ! Done
done
@ -102,19 +244,126 @@ class LazySourceSpec extends StreamSpec with DefaultTimeout with ScalaFutures {
killswitch.abort(boom)
doneF.failed.futureValue should ===(boom)
}
}
"fail stage when upstream fails" in assertAllStagesStopped {
"Source.lazyFutureSource" must {
"work like a normal source, happy path" in assertAllStagesStopped {
val result = Source.lazyFutureSource(() => Future { Source(List(1, 2, 3)) }).runWith(Sink.seq)
result.futureValue should ===(Seq(1, 2, 3))
}
"work like a normal source, happy path, already completed future" in assertAllStagesStopped {
val result = Source.lazyFutureSource(() => Future.successful { Source(List(1, 2, 3)) }).runWith(Sink.seq)
result.futureValue should ===(Seq(1, 2, 3))
}
"never construct the source when there was no demand" in assertAllStagesStopped {
val probe = TestSubscriber.probe[Int]()
val constructed = new AtomicBoolean(false)
val result = Source
.lazyFutureSource { () =>
Future {
constructed.set(true)
Source(List(1, 2, 3))
};
}
.toMat(Sink.fromSubscriber(probe))(Keep.left)
.run()
probe.cancel()
constructed.get() should ===(false)
result.isCompleted should ===(false)
}
"fail the materialized value when downstream cancels without ever consuming any element" in assertAllStagesStopped {
val lazyMatVal: Future[NotUsed] =
Source.lazyFutureSource(() => Future { Source(List(1, 2, 3)) }).toMat(Sink.cancelled)(Keep.left).run()
lazyMatVal.failed.futureValue shouldBe a[NeverMaterializedException]
}
"stop consuming when downstream has cancelled" in assertAllStagesStopped {
val outProbe = TestSubscriber.probe[Int]()
val inProbe = TestPublisher.probe[Int]()
Source.fromGraph(LazySource(() => Source.fromPublisher(inProbe))).runWith(Sink.fromSubscriber(outProbe))
Source.lazyFutureSource(() => Future { Source.fromPublisher(inProbe) }).runWith(Sink.fromSubscriber(outProbe))
outProbe.request(1)
inProbe.expectRequest()
inProbe.sendNext(27)
outProbe.expectNext(27)
inProbe.sendError(TE("OMG Who set that on fire!?!"))
outProbe.expectError() shouldEqual TE("OMG Who set that on fire!?!")
outProbe.cancel()
inProbe.expectCancellation()
}
"materialize when the source has been created" in assertAllStagesStopped {
val probe = TestSubscriber.probe[Int]()
val matF: Future[Done] = Source
.lazyFutureSource { () =>
Future {
Source(List(1, 2, 3)).mapMaterializedValue(_ => Done)
}
}
.to(Sink.fromSubscriber(probe))
.run()
matF.value shouldEqual None
probe.request(1)
probe.expectNext(1)
matF.futureValue should ===(Done)
probe.cancel()
}
"fail stage when upstream fails" in assertAllStagesStopped {
val outProbe = TestSubscriber.probe[Int]()
val inProbe = TestPublisher.probe[Int]()
val lazyMatVal: Future[NotUsed] =
Source
.lazyFutureSource(() =>
Future {
Source.fromPublisher(inProbe)
})
.toMat(Sink.fromSubscriber(outProbe))(Keep.left)
.run()
outProbe.request(1)
lazyMatVal.futureValue should ===(NotUsed) // but completed
inProbe.expectRequest()
inProbe.sendNext(27)
outProbe.expectNext(27)
val failure = TE("OMG Who set that on fire!?!")
inProbe.sendError(failure)
outProbe.expectError() should ===(failure)
}
"fail correctly when factory function fails" in assertAllStagesStopped {
val failure = TE("couldn't create")
val lazyMatVal: Future[NotUsed] =
Source.lazyFutureSource[Int, NotUsed](() => throw failure).toMat(Sink.ignore)(Keep.left).run()
lazyMatVal.failed.futureValue should ===(failure)
}
"fail correctly when factory function returns a failed future" in assertAllStagesStopped {
val failure = TE("couldn't create")
val lazyMatVal: Future[NotUsed] =
Source.lazyFutureSource[Int, NotUsed](() => Future.failed(failure)).toMat(Sink.ignore)(Keep.left).run()
lazyMatVal.failed.futureValue should ===(failure)
}
"fail correctly when factory function returns a future that fails" in assertAllStagesStopped {
val failure = TE("couldn't create")
val promise = Promise[Source[Int, NotUsed]]()
val lazyMatVal: Future[NotUsed] =
Source.lazyFutureSource(() => promise.future).toMat(Sink.ignore)(Keep.left).run()
promise.failure(failure)
lazyMatVal.failed.futureValue should ===(failure)
}
"fail correctly when materialization of inner source fails" in assertAllStagesStopped {
@ -127,10 +376,38 @@ class LazySourceSpec extends StreamSpec with DefaultTimeout with ScalaFutures {
}
}
val result = Source.lazily(() => Source.fromGraph(FailingInnerMat)).to(Sink.ignore).run()
val (lazyMatVal, done) =
Source
.lazyFutureSource(() =>
Future {
Source.fromGraph(FailingInnerMat)
})
.toMat(Sink.ignore)(Keep.both)
.run()
result.failed.futureValue should ===(matFail)
done.failed.futureValue should ===(matFail)
lazyMatVal.failed.futureValue should ===(matFail)
}
"propagate downstream cancellation cause when inner source has been materialized" in {
val probe = TestProbe()
val (terminationF, killswitch) =
Source
.lazyFutureSource(() =>
Future {
Source.maybe[Int].watchTermination()(Keep.right).mapMaterializedValue { done =>
probe.ref ! Done
done
}
})
.mapMaterializedValue(_.flatten)
.viaMat(KillSwitches.single)(Keep.both)
.to(Sink.ignore)
.run()
val boom = TE("boom")
probe.expectMsg(Done)
killswitch.abort(boom)
terminationF.failed.futureValue should ===(boom)
}
}

View file

@ -41,7 +41,7 @@ class MaterializerStateSpec extends StreamSpec {
"snapshot a running stream on the default dispatcher" in {
val promise = Promise[Int]()
Source.fromFuture(promise.future).map(_.toString).zipWithIndex.runWith(Sink.seq)
Source.future(promise.future).map(_.toString).zipWithIndex.runWith(Sink.seq)
awaitAssert({
val snapshot = MaterializerState.streamSnapshots(system).futureValue

View file

@ -0,0 +1,12 @@
/*
* Copyright (C) 2009-2019 Lightbend Inc. <https://www.lightbend.com>
*/
package akka.stream
final class NeverMaterializedException(cause: Throwable)
extends RuntimeException("Downstream canceled without triggering lazy source materialization", cause) {
def this() = this(null)
}

View file

@ -35,13 +35,18 @@ import scala.util.control.NonFatal
val logic = new GraphStageLogic(shape) with OutHandler {
override def onDownstreamFinish(cause: Throwable): Unit = {
matPromise.failure(
new RuntimeException("Downstream canceled without triggering lazy source materialization", cause))
matPromise.failure(new NeverMaterializedException(cause))
completeStage()
}
override def onPull(): Unit = {
val source = sourceFactory()
val source = try {
sourceFactory()
} catch {
case NonFatal(ex) =>
matPromise.tryFailure(ex)
throw ex
}
val subSink = new SubSinkInlet[T]("LazySource")
subSink.pull()
@ -76,7 +81,7 @@ import scala.util.control.NonFatal
setHandler(out, this)
override def postStop() = {
matPromise.tryFailure(new RuntimeException("LazySource stopped without completing the materialized future"))
if (!matPromise.isCompleted) matPromise.tryFailure(new AbruptStageTerminationException(this))
}
}

View file

@ -531,16 +531,16 @@ import scala.util.control.NonFatal
* INTERNAL API
*/
@InternalApi final private[stream] class LazySink[T, M](sinkFactory: T => Future[Sink[T, M]])
extends GraphStageWithMaterializedValue[SinkShape[T], Future[Option[M]]] {
extends GraphStageWithMaterializedValue[SinkShape[T], Future[M]] {
val in = Inlet[T]("lazySink.in")
override def initialAttributes = DefaultAttributes.lazySink
override val shape: SinkShape[T] = SinkShape.of(in)
override def toString: String = "LazySink"
override def createLogicAndMaterializedValue(inheritedAttributes: Attributes) = {
override def createLogicAndMaterializedValue(inheritedAttributes: Attributes): (GraphStageLogic, Future[M]) = {
val promise = Promise[Option[M]]()
val promise = Promise[M]()
val stageLogic = new GraphStageLogic(shape) with InHandler {
var switching = false
override def preStart(): Unit = pull(in)
@ -556,7 +556,7 @@ import scala.util.control.NonFatal
if (!promise.isCompleted) {
try {
val mat = switchTo(sink, element)
promise.success(Some(mat))
promise.success(mat)
setKeepGoing(true)
} catch {
case NonFatal(e) =>
@ -584,7 +584,7 @@ import scala.util.control.NonFatal
// there is a cached element -> the stage must not be shut down automatically because isClosed(in) is satisfied
setKeepGoing(true)
} else {
promise.success(None)
promise.failure(new NeverMaterializedException)
super.onUpstreamFinish()
}
}

View file

@ -335,6 +335,11 @@ import scala.concurrent.{ Future, Promise }
override def onUpstreamFinish(): Unit =
completeStage()
override def onDownstreamFinish(cause: Throwable): Unit = {
sinkIn.cancel(cause)
super.onDownstreamFinish(cause)
}
override def postStop(): Unit =
if (!sinkIn.isClosed) sinkIn.cancel()
@ -376,11 +381,13 @@ import scala.concurrent.{ Future, Promise }
override def createLogic(attr: Attributes) =
new GraphStageLogic(shape) with OutHandler {
def onPull(): Unit = {
if (future.isCompleted) {
onFutureCompleted(future.value.get)
} else {
val cb = getAsyncCallback[Try[T]](onFutureCompleted).invoke _
future.onComplete(cb)(ExecutionContexts.sameThreadExecutionContext)
future.value match {
case Some(completed) =>
// optimization if the future is already completed
onFutureCompleted(completed)
case None =>
val cb = getAsyncCallback[Try[T]](onFutureCompleted).invoke _
future.onComplete(cb)(ExecutionContexts.sameThreadExecutionContext)
}
def onFutureCompleted(result: Try[T]): Unit = {

View file

@ -2080,10 +2080,13 @@ private[stream] object Collect {
/**
* INTERNAL API
*/
@InternalApi final private[akka] class LazyFlow[I, O, M](flowFactory: I => Future[Flow[I, O, M]])
extends GraphStageWithMaterializedValue[FlowShape[I, O], Future[Option[M]]] {
val in = Inlet[I]("lazyFlow.in")
val out = Outlet[O]("lazyFlow.out")
@InternalApi private[akka] final class LazyFlow[I, O, M](flowFactory: I => Future[Flow[I, O, M]])
extends GraphStageWithMaterializedValue[FlowShape[I, O], Future[M]] {
// FIXME: when removing the deprecated I => Flow factories we can remove that complication from this stage
val in = Inlet[I]("LazyFlow.in")
val out = Outlet[O]("LazyFlow.out")
override def initialAttributes = DefaultAttributes.lazyFlow
@ -2091,78 +2094,84 @@ private[stream] object Collect {
override def toString: String = "LazyFlow"
override def createLogicAndMaterializedValue(inheritedAttributes: Attributes) = {
val matPromise = Promise[Option[M]]()
override def createLogicAndMaterializedValue(inheritedAttributes: Attributes): (GraphStageLogic, Future[M]) = {
val matPromise = Promise[M]()
val stageLogic = new GraphStageLogic(shape) with InHandler with OutHandler {
var switching = false
//
// implementation of handler methods in initial state
//
override def onPush(): Unit = {
val element = grab(in)
switching = true
val cb = getAsyncCallback[Try[Flow[I, O, M]]] {
case Success(flow) =>
// check if the stage is still in need for the lazy flow
// (there could have been an onUpstreamFailure or onDownstreamFinish in the meantime that has completed the promise)
if (!matPromise.isCompleted) {
try {
val mat = switchTo(flow, element)
matPromise.success(Some(mat))
} catch {
case NonFatal(e) =>
matPromise.failure(e)
failStage(e)
}
private def onFlowFutureComplete(firstElement: I)(result: Try[Flow[I, O, M]]) = result match {
case Success(flow) =>
// check if the stage is still in need for the lazy flow
// (there could have been an onUpstreamFailure or onDownstreamFinish in the meantime that has completed the promise)
if (!matPromise.isCompleted) {
try {
val mat = switchTo(flow, firstElement)
matPromise.success(mat)
} catch {
case NonFatal(e) =>
matPromise.failure(e)
failStage(e)
}
case Failure(e) =>
matPromise.failure(e)
failStage(e)
}
}
case Failure(e) =>
matPromise.failure(e)
failStage(e)
}
override def onPush(): Unit =
try {
flowFactory(element).onComplete(cb.invoke)(ExecutionContexts.sameThreadExecutionContext)
val element = grab(in)
switching = true
val futureFlow = flowFactory(element)
// optimization avoid extra scheduling if already completed
futureFlow.value match {
case Some(completed) =>
onFlowFutureComplete(element)(completed)
case None =>
val cb = getAsyncCallback[Try[Flow[I, O, M]]](onFlowFutureComplete(element))
futureFlow.onComplete(cb.invoke)(ExecutionContexts.sameThreadExecutionContext)
}
} catch {
case NonFatal(e) =>
matPromise.failure(e)
failStage(e)
}
}
override def onUpstreamFinish(): Unit = {
if (!matPromise.isCompleted)
matPromise.tryFailure(new NeverMaterializedException)
// ignore onUpstreamFinish while the stage is switching but setKeepGoing
if (switching) {
setKeepGoing(true)
} else {
matPromise.success(None)
super.onUpstreamFinish()
}
}
override def onUpstreamFailure(ex: Throwable): Unit = {
matPromise.failure(ex)
super.onUpstreamFailure(ex)
}
override def onDownstreamFinish(cause: Throwable): Unit = {
matPromise.success(None)
super.onDownstreamFinish(cause)
if (!matPromise.isCompleted)
matPromise.tryFailure(new NeverMaterializedException(ex))
}
override def onPull(): Unit = {
pull(in)
}
override def postStop(): Unit = {
if (!matPromise.isCompleted)
matPromise.tryFailure(new AbruptStageTerminationException(this))
}
setHandler(in, this)
setHandler(out, this)
private def switchTo(flow: Flow[I, O, M], firstElement: I): M = {
var firstElementPushed = false
//
// ports are wired in the following way:
//
@ -2174,6 +2183,7 @@ private[stream] object Collect {
val matVal = Source
.fromGraph(subOutlet.source)
.prepend(Source.single(firstElement))
.viaMat(flow)(Keep.right)
.toMat(subInlet.sink)(Keep.left)
.run()(interpreter.subFusingMaterializer)
@ -2203,10 +2213,8 @@ private[stream] object Collect {
subOutlet.push(grab(in))
}
override def onUpstreamFinish(): Unit = {
if (firstElementPushed) {
subOutlet.complete()
maybeCompleteStage()
}
subOutlet.complete()
maybeCompleteStage()
}
override def onUpstreamFailure(ex: Throwable): Unit = {
// propagate exception irrespective if the cached element has been pushed or not
@ -2227,19 +2235,7 @@ private[stream] object Collect {
subOutlet.setHandler(new OutHandler {
override def onPull(): Unit = {
if (firstElementPushed) {
pull(in)
} else {
// the demand can be satisfied right away by the cached element
firstElementPushed = true
subOutlet.push(firstElement)
// in.onUpstreamFinished was not propagated if it arrived before the cached element was pushed
// -> check if the completion must be propagated now
if (isClosed(in)) {
subOutlet.complete()
maybeCompleteStage()
}
}
pull(in)
}
override def onDownstreamFinish(cause: Throwable): Unit = {
if (!isClosed(in)) {

View file

@ -9,6 +9,7 @@ import java.util.function.BiFunction
import java.util.function.Supplier
import java.util.Comparator
import java.util.Optional
import java.util.concurrent.CompletableFuture
import akka.actor.ActorRef
import akka.actor.ClassicActorSystemProvider
@ -25,6 +26,7 @@ import akka.util.ConstantFun
import akka.util.Timeout
import akka.Done
import akka.NotUsed
import akka.japi.function.Creator
import com.github.ghik.silencer.silent
import org.reactivestreams.Processor
@ -253,10 +255,9 @@ object Flow {
*
* '''Cancels when''' downstream cancels
*/
@Deprecated
@deprecated(
"Use lazyInitAsync instead. (lazyInitAsync returns a flow with a more useful materialized value.)",
"2.5.12")
"Use 'Flow.completionStageFlow' in combination with prefixAndTail(1) instead, see `completionStageFlow` operator docs for details",
"2.6.0")
def lazyInit[I, O, M](
flowFactory: function.Function[I, CompletionStage[Flow[I, O, M]]],
fallback: function.Creator[M]): Flow[I, O, M] = {
@ -284,6 +285,7 @@ object Flow {
*
* '''Cancels when''' downstream cancels
*/
@deprecated("Use 'Flow.lazyCompletionStageFlow' instead", "2.6.0")
def lazyInitAsync[I, O, M](
flowFactory: function.Creator[CompletionStage[Flow[I, O, M]]]): Flow[I, O, CompletionStage[Optional[M]]] = {
import scala.compat.java8.FutureConverters._
@ -299,6 +301,63 @@ object Flow {
new Flow(sflow)
}
/**
* Turn a `CompletionStage<Flow>` into a flow that will consume the values of the source when the future completes successfully.
* If the `Future` is completed with a failure the stream is failed.
*
* The materialized completion stage value is completed with the materialized value of the future flow or failed with a
* [[NeverMaterializedException]] if upstream fails or downstream cancels before the completion stage has completed.
*/
def completionStageFlow[I, O, M](flow: CompletionStage[Flow[I, O, M]]): Flow[I, O, CompletionStage[M]] =
lazyCompletionStageFlow(() => flow)
/**
* Defers invoking the `create` function to create a future flow until there is downstream demand and passing
* that downstream demand upstream triggers the first element.
*
* Note that asynchronous boundaries (and other operators) in the stream may do pre-fetching which counter acts
* the laziness and can trigger the factory earlier than expected.
*
* '''Emits when''' the internal flow is successfully created and it emits
*
* '''Backpressures when''' the internal flow is successfully created and it backpressures or downstream backpressures
*
* '''Completes when''' upstream completes and all elements have been emitted from the internal flow
*
* '''Cancels when''' downstream cancels
*/
def lazyFlow[I, O, M](create: Creator[Flow[I, O, M]]): Flow[I, O, CompletionStage[M]] =
lazyCompletionStageFlow(() => CompletableFuture.completedFuture(create.create()))
/**
* Defers invoking the `create` function to create a future flow until there downstream demand has caused upstream
* to send a first element.
*
* The materialized future value is completed with the materialized value of the created flow when that has successfully
* been materialized.
*
* If the `create` function throws or returns a future that fails the stream is failed, in this case the materialized
* future value is failed with a [[NeverMaterializedException]].
*
* Note that asynchronous boundaries (and other operators) in the stream may do pre-fetching which counter acts
* the laziness and can trigger the factory earlier than expected.
*
* '''Emits when''' the internal flow is successfully created and it emits
*
* '''Backpressures when''' the internal flow is successfully created and it backpressures or downstream backpressures
*
* '''Completes when''' upstream completes and all elements have been emitted from the internal flow
*
* '''Cancels when''' downstream cancels
*/
def lazyCompletionStageFlow[I, O, M](
create: Creator[CompletionStage[Flow[I, O, M]]]): Flow[I, O, CompletionStage[M]] =
scaladsl.Flow
.lazyFutureFlow[I, O, M](() =>
create.create().toScala.map(_.asScala)(ExecutionContexts.sameThreadExecutionContext))
.mapMaterializedValue(_.toJava)
.asJava
/**
* Upcast a stream of elements to a stream of supertypes of that element. Useful in combination with
* fan-in operators where you do not want to pay the cost of casting each element in a `map`.

View file

@ -5,6 +5,7 @@
package akka.stream.javadsl
import java.util.Optional
import java.util.concurrent.CompletableFuture
import java.util.concurrent.CompletionStage
import java.util.function.BiFunction
@ -12,6 +13,7 @@ import akka.actor.{ ActorRef, ClassicActorSystemProvider, Status }
import akka.dispatch.ExecutionContexts
import akka._
import akka.japi.function
import akka.japi.function.Creator
import akka.stream.impl.LinearTraversalBuilder
import akka.stream.{ javadsl, scaladsl, _ }
import org.reactivestreams.{ Publisher, Subscriber }
@ -360,10 +362,7 @@ object Sink {
* sink fails then the `Future` is completed with the exception.
* Otherwise the `Future` is completed with the materialized value of the internal sink.
*/
@Deprecated
@deprecated(
"Use lazyInitAsync instead. (lazyInitAsync no more needs a fallback function and the materialized value more clearly indicates if the internal sink was materialized or not.)",
"2.5.11")
@deprecated("Use 'Sink.lazyCompletionStageSink' in combination with 'Flow.prefixAndTail(1)' instead", "2.6.0")
def lazyInit[T, M](
sinkFactory: function.Function[T, CompletionStage[Sink[T, M]]],
fallback: function.Creator[M]): Sink[T, CompletionStage[M]] =
@ -383,6 +382,7 @@ object Sink {
* sink fails then the `Future` is completed with the exception.
* Otherwise the `Future` is completed with the materialized value of the internal sink.
*/
@deprecated("Use 'Sink.lazyCompletionStageSink' instead", "2.6.0")
def lazyInitAsync[T, M](
sinkFactory: function.Creator[CompletionStage[Sink[T, M]]]): Sink[T, CompletionStage[Optional[M]]] = {
val sSink = scaladsl.Sink
@ -395,6 +395,42 @@ object Sink {
.toJava)
new Sink(sSink)
}
/**
* Turn a `Future[Sink]` into a Sink that will consume the values of the source when the future completes successfully.
* If the `Future` is completed with a failure the stream is failed.
*
* The materialized future value is completed with the materialized value of the future sink or failed with a
* [[NeverMaterializedException]] if upstream fails or downstream cancels before the future has completed.
*/
def completionStageSink[T, M](future: CompletionStage[Sink[T, M]]): Sink[T, CompletionStage[M]] =
lazyCompletionStageSink[T, M](() => future)
/**
* Defers invoking the `create` function to create a sink until there is a first element passed from upstream.
*
* The materialized future value is completed with the materialized value of the created sink when that has successfully
* been materialized.
*
* If the `create` function throws or returns or the stream fails to materialize, in this
* case the materialized future value is failed with a [[akka.stream.NeverMaterializedException]].
*/
def lazySink[T, M](create: Creator[Sink[T, M]]): Sink[T, CompletionStage[M]] =
lazyCompletionStageSink(() => CompletableFuture.completedFuture(create.create))
/**
* Defers invoking the `create` function to create a future sink until there is a first element passed from upstream.
*
* The materialized future value is completed with the materialized value of the created sink when that has successfully
* been materialized.
*
* If the `create` function throws or returns a future that is failed, or the stream fails to materialize, in this
* case the materialized future value is failed with a [[akka.stream.NeverMaterializedException]].
*/
def lazyCompletionStageSink[T, M](create: Creator[CompletionStage[Sink[T, M]]]): Sink[T, CompletionStage[M]] =
new Sink(scaladsl.Sink.lazyFutureSink { () =>
create.create().toScala.map(_.asScala)((ExecutionContexts.sameThreadExecutionContext))
}).mapMaterializedValue(_.toJava)
}
/**

View file

@ -10,7 +10,9 @@ import java.util.concurrent.{ CompletableFuture, CompletionStage }
import java.util.function.{ BiFunction, Supplier }
import akka.actor.{ ActorRef, Cancellable, ClassicActorSystemProvider }
import akka.dispatch.ExecutionContexts
import akka.event.LoggingAdapter
import akka.japi.function.Creator
import akka.japi.{ function, JavaPartialFunction, Pair, Util }
import akka.stream._
import akka.stream.impl.LinearTraversalBuilder
@ -171,8 +173,9 @@ object Source {
* may happen before or after materializing the `Flow`.
* The stream terminates with a failure if the `Future` is completed with a failure.
*/
@deprecated("Use 'Source.future' instead", "2.6.0")
def fromFuture[O](future: Future[O]): javadsl.Source[O, NotUsed] =
new Source(scaladsl.Source.fromFuture(future))
new Source(scaladsl.Source.future(future))
/**
* Starts a new `Source` from the given `CompletionStage`. The stream will consist of
@ -180,14 +183,16 @@ object Source {
* may happen before or after materializing the `Flow`.
* The stream terminates with a failure if the `CompletionStage` is completed with a failure.
*/
@deprecated("Use 'Source.completionStage' instead", "2.6.0")
def fromCompletionStage[O](future: CompletionStage[O]): javadsl.Source[O, NotUsed] =
new Source(scaladsl.Source.fromCompletionStage(future))
new Source(scaladsl.Source.completionStage(future))
/**
* Streams the elements of the given future source once it successfully completes.
* If the [[Future]] fails the stream is failed with the exception from the future. If downstream cancels before the
* stream completes the materialized [[Future]] will be failed with a [[StreamDetachedException]].
*/
@deprecated("Use 'Source.futureSource' (potentially together with `Source.fromGraph`) instead", "2.6.0")
def fromFutureSource[T, M](future: Future[_ <: Graph[SourceShape[T], M]]): javadsl.Source[T, Future[M]] =
new Source(scaladsl.Source.fromFutureSource(future))
@ -197,9 +202,10 @@ object Source {
* If downstream cancels before the stream completes the materialized [[CompletionStage]] will be failed
* with a [[StreamDetachedException]]
*/
@deprecated("Use 'Source.completionStageSource' (potentially together with `Source.fromGraph`) instead", "2.6.0")
def fromSourceCompletionStage[T, M](
completion: CompletionStage[_ <: Graph[SourceShape[T], M]]): javadsl.Source[T, CompletionStage[M]] =
new Source(scaladsl.Source.fromSourceCompletionStage(completion))
completionStageSource(completion.thenApply(fromGraph[T, M]))
/**
* Elements are emitted periodically with the specified interval.
@ -262,6 +268,7 @@ object Source {
* the materialized future is completed with its value, if downstream cancels or fails without any demand the
* `create` factory is never called and the materialized `CompletionStage` is failed.
*/
@deprecated("Use 'Source.lazySource' instead", "2.6.0")
def lazily[T, M](create: function.Creator[Source[T, M]]): Source[T, CompletionStage[M]] =
scaladsl.Source.lazily[T, M](() => create.create().asScala).mapMaterializedValue(_.toJava).asJava
@ -272,9 +279,115 @@ object Source {
*
* @see [[Source.lazily]]
*/
@deprecated("Use 'Source.lazyCompletionStage' instead", "2.6.0")
def lazilyAsync[T](create: function.Creator[CompletionStage[T]]): Source[T, Future[NotUsed]] =
scaladsl.Source.lazilyAsync[T](() => create.create().toScala).asJava
/**
* Emits a single value when the given Scala `Future` is successfully completed and then completes the stream.
* The stream fails if the `Future` is completed with a failure.
*
* Here for Java interoperability, the normal use from Java should be [[Source.completionStage]]
*/
def future[T](futureElement: Future[T]): Source[T, NotUsed] =
scaladsl.Source.future(futureElement).asJava
/**
* Emits a single value when the given `CompletionStage` is successfully completed and then completes the stream.
* If the `CompletionStage` is completed with a failure the stream is failed.
*/
def completionStage[T](completionStage: CompletionStage[T]): Source[T, NotUsed] =
future(completionStage.toScala)
/**
* Turn a `CompletionStage[Source]` into a source that will emit the values of the source when the future completes successfully.
* If the `CompletionStage` is completed with a failure the stream is failed.
*/
def completionStageSource[T, M](completionStageSource: CompletionStage[Source[T, M]]): Source[T, CompletionStage[M]] =
scaladsl.Source
.futureSource(completionStageSource.toScala.map(_.asScala)(ExecutionContexts.sameThreadExecutionContext))
.mapMaterializedValue(_.toJava)
.asJava
/**
* Defers invoking the `create` function to create a single element until there is downstream demand.
*
* If the `create` function fails when invoked the stream is failed.
*
* Note that asynchronous boundaries (and other operators) in the stream may do pre-fetching which counter acts
* the laziness and will trigger the factory immediately.
*
* The materialized future `Done` value is completed when the `create` function has successfully been invoked,
* if the function throws the future materialized value is failed with that exception.
* If downstream cancels or fails before the function is invoked the materialized value
* is failed with a [[akka.stream.NeverMaterializedException]]
*/
def lazySingle[T](create: Creator[T]): Source[T, NotUsed] =
lazySource(() => single(create.create())).mapMaterializedValue(_ => NotUsed)
/**
* Defers invoking the `create` function to create a future element until there is downstream demand.
*
* The returned future element will be emitted downstream when it completes, or fail the stream if the future
* is failed or the `create` function itself fails.
*
* Note that asynchronous boundaries (and other operators) in the stream may do pre-fetching which counter acts
* the laziness and will trigger the factory immediately.
*
* The materialized future `Done` value is completed when the `create` function has successfully been invoked and the future completes,
* if the function throws or the future fails the future materialized value is failed with that exception.
* If downstream cancels or fails before the function is invoked the materialized value
* is failed with a [[akka.stream.NeverMaterializedException]]
*/
def lazyCompletionStage[T](create: Creator[CompletionStage[T]]): Source[T, NotUsed] =
scaladsl.Source
.lazySource { () =>
val f = create.create().toScala
scaladsl.Source.future(f)
}
.mapMaterializedValue(_ => NotUsed.notUsed())
.asJava
/**
* Defers invoking the `create` function to create a future source until there is downstream demand.
*
* The returned source will emit downstream and behave just like it was the outer source. Downstream completes
* when the created source completes and fails when the created source fails.
*
* Note that asynchronous boundaries (and other operators) in the stream may do pre-fetching which counter acts
* the laziness and will trigger the factory immediately.
*
* The materialized future value is completed with the materialized value of the created source when
* it has been materialized. If the function throws or the source materialization fails the future materialized value
* is failed with the thrown exception.
*
* If downstream cancels or fails before the function is invoked the materialized value
* is failed with a [[akka.stream.NeverMaterializedException]]
*/
def lazySource[T, M](create: Creator[Source[T, M]]): Source[T, CompletionStage[M]] =
scaladsl.Source.lazySource(() => create.create().asScala).mapMaterializedValue(_.toJava).asJava
/**
* Defers invoking the `create` function to create a future source until there is downstream demand.
*
* The returned future source will emit downstream and behave just like it was the outer source when the `CompletionStage` completes
* successfully. Downstream completes when the created source completes and fails when the created source fails.
* If the `CompletionStage` or the `create` function fails the stream is failed.
*
* Note that asynchronous boundaries (and other operators) in the stream may do pre-fetching which counter acts
* the laziness and triggers the factory immediately.
*
* The materialized `CompletionStage` value is completed with the materialized value of the created source when
* it has been materialized. If the function throws or the source materialization fails the future materialized value
* is failed with the thrown exception.
*
* If downstream cancels or fails before the function is invoked the materialized value
* is failed with a [[akka.stream.NeverMaterializedException]]
*/
def lazyCompletionStageSource[T, M](create: Creator[CompletionStage[Source[T, M]]]): Source[T, CompletionStage[M]] =
lazySource[T, CompletionStage[M]](() => completionStageSource(create.create()))
.mapMaterializedValue(_.thenCompose(csm => csm))
/**
* Creates a `Source` that is materialized as a [[org.reactivestreams.Subscriber]]
*/

View file

@ -581,10 +581,9 @@ object Flow {
*
* '''Cancels when''' downstream cancels
*/
@Deprecated
@deprecated(
"Use lazyInitAsync instead. (lazyInitAsync returns a flow with a more useful materialized value.)",
"2.5.12")
"Use 'Flow.futureFlow' in combination with prefixAndTail(1) instead, see `futureFlow` operator docs for details",
"2.6.0")
def lazyInit[I, O, M](flowFactory: I => Future[Flow[I, O, M]], fallback: () => M): Flow[I, O, M] =
Flow.fromGraph(new LazyFlow[I, O, M](flowFactory)).mapMaterializedValue(_ => fallback())
@ -604,8 +603,71 @@ object Flow {
*
* '''Cancels when''' downstream cancels
*/
@deprecated("Use 'Flow.lazyFutureFlow' instead", "2.6.0")
def lazyInitAsync[I, O, M](flowFactory: () => Future[Flow[I, O, M]]): Flow[I, O, Future[Option[M]]] =
Flow.fromGraph(new LazyFlow[I, O, M](_ => flowFactory()))
Flow.fromGraph(new LazyFlow[I, O, M](_ => flowFactory())).mapMaterializedValue { v =>
implicit val ec = akka.dispatch.ExecutionContexts.sameThreadExecutionContext
v.map[Option[M]](Some.apply _).recover { case _: NeverMaterializedException => None }
}
/**
* Turn a `Future[Flow]` into a flow that will consume the values of the source when the future completes successfully.
* If the `Future` is completed with a failure the stream is failed.
*
* The materialized future value is completed with the materialized value of the future flow or failed with a
* [[NeverMaterializedException]] if upstream fails or downstream cancels before the future has completed.
*/
def futureFlow[I, O, M](flow: Future[Flow[I, O, M]]): Flow[I, O, Future[M]] =
lazyFutureFlow(() => flow)
/**
* Defers invoking the `create` function to create a future flow until there is downstream demand and passing
* that downstream demand upstream triggers the first element.
*
* The materialized future value is completed with the materialized value of the created flow when that has successfully
* been materialized.
*
* If the `create` function throws or returns a future that fails the stream is failed, in this case the materialized
* future value is failed with a [[NeverMaterializedException]].
*
* Note that asynchronous boundaries (and other operators) in the stream may do pre-fetching which counter acts
* the laziness and can trigger the factory earlier than expected.
*
* '''Emits when''' the internal flow is successfully created and it emits
*
* '''Backpressures when''' the internal flow is successfully created and it backpressures or downstream backpressures
*
* '''Completes when''' upstream completes and all elements have been emitted from the internal flow
*
* '''Cancels when''' downstream cancels
*/
def lazyFlow[I, O, M](create: () => Flow[I, O, M]): Flow[I, O, Future[M]] =
lazyFutureFlow(() => Future.successful(create()))
/**
* Defers invoking the `create` function to create a future flow until there downstream demand has caused upstream
* to send a first element.
*
* The materialized future value is completed with the materialized value of the created flow when that has successfully
* been materialized.
*
* If the `create` function throws or returns a future that fails the stream is failed, in this case the materialized
* future value is failed with a [[NeverMaterializedException]].
*
* Note that asynchronous boundaries (and other operators) in the stream may do pre-fetching which counter acts
* the laziness and can trigger the factory earlier than expected.
*
* '''Emits when''' the internal flow is successfully created and it emits
*
* '''Backpressures when''' the internal flow is successfully created and it backpressures or downstream backpressures
*
* '''Completes when''' upstream completes and all elements have been emitted from the internal flow
*
* '''Cancels when''' downstream cancels
*/
def lazyFutureFlow[I, O, M](create: () => Future[Flow[I, O, M]]): Flow[I, O, Future[M]] =
Flow.fromGraph(new LazyFlow(_ => create()))
}
object RunnableGraph {

View file

@ -583,14 +583,12 @@ object Sink {
* sink fails then the `Future` is completed with the exception.
* Otherwise the `Future` is completed with the materialized value of the internal sink.
*/
@Deprecated
@deprecated(
"Use lazyInitAsync instead. (lazyInitAsync no more needs a fallback function and the materialized value more clearly indicates if the internal sink was materialized or not.)",
"2.5.11")
@deprecated("Use 'Sink.lazyFutureSink' in combination with 'Flow.prefixAndTail(1)' instead", "2.6.0")
def lazyInit[T, M](sinkFactory: T => Future[Sink[T, M]], fallback: () => M): Sink[T, Future[M]] =
Sink
.fromGraph(new LazySink[T, M](sinkFactory))
.mapMaterializedValue(_.map(_.getOrElse(fallback()))(ExecutionContexts.sameThreadExecutionContext))
.mapMaterializedValue(
_.recover { case _: NeverMaterializedException => fallback() }(ExecutionContexts.sameThreadExecutionContext))
/**
* Creates a real `Sink` upon receiving the first element. Internal `Sink` will not be created if there are no elements,
@ -601,7 +599,45 @@ object Sink {
* sink fails then the `Future` is completed with the exception.
* Otherwise the `Future` is completed with the materialized value of the internal sink.
*/
@deprecated("Use 'Sink.lazyFutureSink' instead", "2.6.0")
def lazyInitAsync[T, M](sinkFactory: () => Future[Sink[T, M]]): Sink[T, Future[Option[M]]] =
Sink.fromGraph(new LazySink[T, M](_ => sinkFactory()))
Sink.fromGraph(new LazySink[T, M](_ => sinkFactory())).mapMaterializedValue { m =>
implicit val ec = ExecutionContexts.sameThreadExecutionContext
m.map(Option.apply _).recover { case _: NeverMaterializedException => None }
}
/**
* Turn a `Future[Sink]` into a Sink that will consume the values of the source when the future completes successfully.
* If the `Future` is completed with a failure the stream is failed.
*
* The materialized future value is completed with the materialized value of the future sink or failed with a
* [[NeverMaterializedException]] if upstream fails or downstream cancels before the future has completed.
*/
def futureSink[T, M](future: Future[Sink[T, M]]): Sink[T, Future[M]] =
lazyFutureSink[T, M](() => future)
/**
* Defers invoking the `create` function to create a sink until there is a first element passed from upstream.
*
* The materialized future value is completed with the materialized value of the created sink when that has successfully
* been materialized.
*
* If the `create` function throws or returns or the stream fails to materialize, in this
* case the materialized future value is failed with a [[akka.stream.NeverMaterializedException]].
*/
def lazySink[T, M](create: () => Sink[T, M]): Sink[T, Future[M]] =
lazyFutureSink(() => Future.successful(create()))
/**
* Defers invoking the `create` function to create a future sink until there is a first element passed from upstream.
*
* The materialized future value is completed with the materialized value of the created sink when that has successfully
* been materialized.
*
* If the `create` function throws or returns a future that is failed, or the stream fails to materialize, in this
* case the materialized future value is failed with a [[akka.stream.NeverMaterializedException]].
*/
def lazyFutureSink[T, M](create: () => Future[Sink[T, M]]): Sink[T, Future[M]] =
Sink.fromGraph(new LazySink(_ => create()))
}

View file

@ -16,13 +16,14 @@ import akka.stream.{ Outlet, SourceShape, _ }
import akka.util.ConstantFun
import akka.{ Done, NotUsed }
import org.reactivestreams.{ Publisher, Subscriber }
import scala.annotation.tailrec
import scala.annotation.unchecked.uncheckedVariance
import scala.collection.immutable
import scala.concurrent.duration.FiniteDuration
import scala.concurrent.{ Future, Promise }
import akka.stream.stage.GraphStageWithMaterializedValue
import scala.compat.java8.FutureConverters._
/**
@ -341,6 +342,7 @@ object Source {
* may happen before or after materializing the `Flow`.
* The stream terminates with a failure if the `Future` is completed with a failure.
*/
@deprecated("Use 'Source.future' instead", "2.6.0")
def fromFuture[T](future: Future[T]): Source[T, NotUsed] =
fromGraph(new FutureSource(future))
@ -350,6 +352,7 @@ object Source {
* may happen before or after materializing the `Flow`.
* The stream terminates with a failure if the `Future` is completed with a failure.
*/
@deprecated("Use 'Source.completionStage' instead", "2.6.0")
def fromCompletionStage[T](future: CompletionStage[T]): Source[T, NotUsed] =
fromGraph(new FutureSource(future.toScala))
@ -358,6 +361,7 @@ object Source {
* If the [[Future]] fails the stream is failed with the exception from the future. If downstream cancels before the
* stream completes the materialized `Future` will be failed with a [[StreamDetachedException]]
*/
@deprecated("Use 'Source.futureSource' (potentially together with `Source.fromGraph`) instead", "2.6.0")
def fromFutureSource[T, M](future: Future[Graph[SourceShape[T], M]]): Source[T, Future[M]] =
fromGraph(new FutureFlattenSource(future))
@ -367,6 +371,7 @@ object Source {
* If downstream cancels before the stream completes the materialized `Future` will be failed
* with a [[StreamDetachedException]]
*/
@deprecated("Use scala-compat CompletionStage to future converter and 'Source.futureSource' instead", "2.6.0")
def fromSourceCompletionStage[T, M](
completion: CompletionStage[_ <: Graph[SourceShape[T], M]]): Source[T, CompletionStage[M]] =
fromFutureSource(completion.toScala).mapMaterializedValue(_.toJava)
@ -462,6 +467,7 @@ object Source {
* the materialized future is completed with its value, if downstream cancels or fails without any demand the
* create factory is never called and the materialized `Future` is failed.
*/
@deprecated("Use 'Source.lazySource' instead", "2.6.0")
def lazily[T, M](create: () => Source[T, M]): Source[T, Future[M]] =
Source.fromGraph(new LazySource[T, M](create))
@ -472,9 +478,98 @@ object Source {
*
* @see [[Source.lazily]]
*/
@deprecated("Use 'Source.lazyFuture' instead", "2.6.0")
def lazilyAsync[T](create: () => Future[T]): Source[T, Future[NotUsed]] =
lazily(() => fromFuture(create()))
/**
* Emits a single value when the given `Future` is successfully completed and then completes the stream.
* The stream fails if the `Future` is completed with a failure.
*/
def future[T](futureElement: Future[T]): Source[T, NotUsed] =
fromGraph(new FutureSource[T](futureElement))
/**
* Emits a single value when the given `CompletionStage` is successfully completed and then completes the stream.
* If the `CompletionStage` is completed with a failure the stream is failed.
*
* Here for Java interoperability, the normal use from Scala should be [[Source.future]]
*/
def completionStage[T](completionStage: CompletionStage[T]): Source[T, NotUsed] =
future(completionStage.toScala)
/**
* Turn a `Future[Source]` into a source that will emit the values of the source when the future completes successfully.
* If the `Future` is completed with a failure the stream is failed.
*/
def futureSource[T, M](futureSource: Future[Source[T, M]]): Source[T, Future[M]] =
fromGraph(new FutureFlattenSource(futureSource))
/**
* Defers invoking the `create` function to create a single element until there is downstream demand.
*
* If the `create` function fails when invoked the stream is failed.
*
* Note that asynchronous boundaries (and other operators) in the stream may do pre-fetching which counter acts
* the laziness and will trigger the factory immediately.
*/
def lazySingle[T](create: () => T): Source[T, NotUsed] =
lazySource(() => single(create())).mapMaterializedValue(_ => NotUsed)
/**
* Defers invoking the `create` function to create a future element until there is downstream demand.
*
* The returned future element will be emitted downstream when it completes, or fail the stream if the future
* is failed or the `create` function itself fails.
*
* Note that asynchronous boundaries (and other operators) in the stream may do pre-fetching which counter acts
* the laziness and will trigger the factory immediately.
*/
def lazyFuture[T](create: () => Future[T]): Source[T, NotUsed] =
lazySource { () =>
val f = create()
future(f)
}.mapMaterializedValue(_ => NotUsed)
/**
* Defers invoking the `create` function to create a future source until there is downstream demand.
*
* The returned source will emit downstream and behave just like it was the outer source. Downstream completes
* when the created source completes and fails when the created source fails.
*
* Note that asynchronous boundaries (and other operators) in the stream may do pre-fetching which counter acts
* the laziness and will trigger the factory immediately.
*
* The materialized future value is completed with the materialized value of the created source when
* it has been materialized. If the function throws or the source materialization fails the future materialized value
* is failed with the thrown exception.
*
* If downstream cancels or fails before the function is invoked the materialized value
* is failed with a [[akka.stream.NeverMaterializedException]]
*/
def lazySource[T, M](create: () => Source[T, M]): Source[T, Future[M]] =
fromGraph(new LazySource(create))
/**
* Defers invoking the `create` function to create a future source until there is downstream demand.
*
* The returned future source will emit downstream and behave just like it was the outer source when the future completes
* successfully. Downstream completes when the created source completes and fails when the created source fails.
* If the future or the `create` function fails the stream is failed.
*
* Note that asynchronous boundaries (and other operators) in the stream may do pre-fetching which counter acts
* the laziness and triggers the factory immediately.
*
* The materialized future value is completed with the materialized value of the created source when
* it has been materialized. If the function throws or the source materialization fails the future materialized value
* is failed with the thrown exception.
*
* If downstream cancels or fails before the function is invoked the materialized value
* is failed with a [[akka.stream.NeverMaterializedException]]
*/
def lazyFutureSource[T, M](create: () => Future[Source[T, M]]): Source[T, Future[M]] =
lazySource(() => futureSource(create())).mapMaterializedValue(_.flatten)
/**
* Creates a `Source` that is materialized as a [[org.reactivestreams.Subscriber]]
*/