BoundedSourceQueue API #29574
A new queue implementation that drops new elements immediately when the buffer is full. Does not use async callbacks like Source.queue with OverflowStrategy.dropNew, which can still result in OOM errors (#25798).
This commit is contained in:
commit
2b8d0b2285
11 changed files with 587 additions and 16 deletions
|
|
@ -1,15 +1,44 @@
|
|||
# Source.queue
|
||||
|
||||
Materialize a `SourceQueue` onto which elements can be pushed for emitting from the source.
|
||||
Materialize a `BoundedSourceQueue` or `SourceQueue` onto which elements can be pushed for emitting from the source.
|
||||
|
||||
@ref[Source operators](../index.md#source-operators)
|
||||
|
||||
## Signature
|
||||
## Signature (`BoundedSourceQueue`)
|
||||
|
||||
@apidoc[Source.queue](Source$) { scala="#queue[T](bufferSize:Int):akka.stream.scaladsl.Source[T,akka.stream.scaladsl.BoundedSourceQueue[T]]" java="#queue(int)" }
|
||||
|
||||
## Description (`BoundedSourceQueue`)
|
||||
|
||||
The `BoundedSourceQueue` is an optimized variant of the `SourceQueue` with `OverflowStrategy.dropNew`.
|
||||
The `BoundedSourceQueue` will give immediate, synchronous feedback whether an element was accepted or not and is therefore recommended for situations where overload and dropping elements is expected and needs to be handled quickly.
|
||||
|
||||
In contrast, the `SourceQueue` offers more variety of `OverflowStrategies` but feedback is only asynchronously provided through a @scala[`Future`]@java[`CompletionStage`] value.
|
||||
In cases where elements need to be discarded quickly at times of overload to avoid out-of-memory situations, delivering feedback asynchronously can itself become a problem.
|
||||
This happens if elements come in faster than the feedback can be delivered in which case the feedback mechanism itself is part of the reason that an out-of-memory situation arises.
|
||||
|
||||
In summary, prefer `BoundedSourceQueue` over `SourceQueue` with `OverflowStrategy.dropNew` especially in high-load scenarios.
|
||||
Use `SourceQueue` if you need one of the other `OverflowStrategies`.
|
||||
|
||||
The `BoundedSourceQueue` contains a buffer that can be used by many producers on different threads.
|
||||
When the buffer is full, the `BoundedSourceQueue` will not accept more elements.
|
||||
The return value of `BoundedSourceQueue.offer()` immediately returns a `QueueOfferResult` (as opposed to an asynchronous value returned by `SourceQueue`).
|
||||
A synchronous result is important in order to avoid situations where offer acknowledgements are handled slower than the rate of which elements are offered, which will eventually lead to an Out Of Memory error.
|
||||
|
||||
## Example (`BoundedSourceQueue`)
|
||||
|
||||
Scala
|
||||
: @@snip [IntegrationDocSpec.scala](/akka-docs/src/test/scala/docs/stream/IntegrationDocSpec.scala) { #source-queue-synchronous }
|
||||
|
||||
Java
|
||||
: @@snip [IntegrationDocTest.java](/akka-docs/src/test/java/jdocs/stream/IntegrationDocTest.java) { #source-queue-synchronous }
|
||||
|
||||
## Signature (`SourceQueue`)
|
||||
|
||||
@apidoc[Source.queue](Source$) { scala="#queue[T](bufferSize:Int,overflowStrategy:akka.stream.OverflowStrategy):akka.stream.scaladsl.Source[T,akka.stream.scaladsl.SourceQueueWithComplete[T]]" java="#queue(int,akka.stream.OverflowStrategy)" }
|
||||
@apidoc[Source.queue](Source$) { scala="#queue[T](bufferSize:Int,overflowStrategy:akka.stream.OverflowStrategy,maxConcurrentOffers:Int):akka.stream.scaladsl.Source[T,akka.stream.scaladsl.SourceQueueWithComplete[T]]" java="#queue(int,akka.stream.OverflowStrategy,int)" }
|
||||
|
||||
|
||||
## Description
|
||||
## Description (`SourceQueue`)
|
||||
|
||||
Materialize a `SourceQueue` onto which elements can be pushed for emitting from the source. The queue contains
|
||||
a buffer, if elements are pushed onto the queue faster than the source is consumed the overflow will be handled with
|
||||
|
|
@ -22,7 +51,7 @@ will be discarded if downstream is terminated.
|
|||
|
||||
In combination with the queue, the @ref[`throttle`](./../Source-or-Flow/throttle.md) operator can be used to control the processing to a given limit, e.g. `5 elements` per `3 seconds`.
|
||||
|
||||
## Example
|
||||
## Example (`SourceQueue`)
|
||||
|
||||
Scala
|
||||
: @@snip [IntegrationDocSpec.scala](/akka-docs/src/test/scala/docs/stream/IntegrationDocSpec.scala) { #source-queue }
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ These built-in sources are available from @scala[`akka.stream.scaladsl.Source`]
|
|||
|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="never"></a>@ref[never](Source/never.md)|Never emit any elements, never complete and never fail.|
|
||||
|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="queue"></a>@ref[queue](Source/queue.md)|Materialize a `BoundedSourceQueue` or `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.|
|
||||
|Source|<a name="repeat"></a>@ref[repeat](Source/repeat.md)|Stream a single object repeatedly.|
|
||||
|Source|<a name="single"></a>@ref[single](Source/single.md)|Stream a single object once.|
|
||||
|
|
|
|||
|
|
@ -765,6 +765,44 @@ public class IntegrationDocTest extends AbstractJavaTest {
|
|||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void illustrateSynchronousSourceQueue() throws Exception {
|
||||
new TestKit(system) {
|
||||
{
|
||||
// #source-queue-synchronous
|
||||
int bufferSize = 10;
|
||||
int elementsToProcess = 5;
|
||||
|
||||
BoundedSourceQueue<Integer> sourceQueue =
|
||||
Source.<Integer>queue(bufferSize)
|
||||
.throttle(elementsToProcess, Duration.ofSeconds(3))
|
||||
.map(x -> x * x)
|
||||
.to(Sink.foreach(x -> System.out.println("got: " + x)))
|
||||
.run(system);
|
||||
|
||||
List<Integer> fastElements = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
|
||||
|
||||
fastElements.stream()
|
||||
.forEach(
|
||||
x -> {
|
||||
QueueOfferResult result = sourceQueue.offer(x);
|
||||
if (result == QueueOfferResult.enqueued()) {
|
||||
System.out.println("enqueued " + x);
|
||||
} else if (result == QueueOfferResult.dropped()) {
|
||||
System.out.println("dropped " + x);
|
||||
} else if (result instanceof QueueOfferResult.Failure) {
|
||||
QueueOfferResult.Failure failure = (QueueOfferResult.Failure) result;
|
||||
System.out.println("Offer failed " + failure.cause().getMessage());
|
||||
} else if (result instanceof QueueOfferResult.QueueClosed$) {
|
||||
System.out.println("Bounded Source Queue closed");
|
||||
}
|
||||
});
|
||||
|
||||
// #source-queue-synchronous
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void illustrateSourceActorRef() throws Exception {
|
||||
new TestKit(system) {
|
||||
|
|
|
|||
|
|
@ -491,6 +491,36 @@ class IntegrationDocSpec extends AkkaSpec(IntegrationDocSpec.config) {
|
|||
//#source-queue
|
||||
}
|
||||
|
||||
"illustrate use of synchronous source queue" in {
|
||||
//#source-queue-synchronous
|
||||
val bufferSize = 1000
|
||||
|
||||
//#source-queue-synchronous
|
||||
// format: OFF
|
||||
//#source-queue-synchronous
|
||||
val queue = Source
|
||||
.queue[Int](bufferSize)
|
||||
.map(x => x * x)
|
||||
.toMat(Sink.foreach(x => println(s"completed $x")))(Keep.left)
|
||||
.run()
|
||||
//#source-queue-synchronous
|
||||
// format: OFF
|
||||
//#source-queue-synchronous
|
||||
|
||||
val fastElements = 1 to 10
|
||||
|
||||
implicit val ec = system.dispatcher
|
||||
fastElements.foreach { x =>
|
||||
queue.offer(x) match {
|
||||
case QueueOfferResult.Enqueued => println(s"enqueued $x")
|
||||
case QueueOfferResult.Dropped => println(s"dropped $x")
|
||||
case QueueOfferResult.Failure(ex) => println(s"Offer failed ${ex.getMessage}")
|
||||
case QueueOfferResult.QueueClosed => println("Source Queue closed")
|
||||
}
|
||||
}
|
||||
//#source-queue-synchronous
|
||||
}
|
||||
|
||||
"illustrate use of source actor ref" in {
|
||||
//#source-actorRef
|
||||
val bufferSize = 10
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue