Fix stack overflow in stream converters (#27305)
This commit is contained in:
parent
0037998bfb
commit
6e69bc8713
6 changed files with 426 additions and 273 deletions
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
package akka.stream.impl
|
||||
|
||||
import java.util.function.BinaryOperator
|
||||
|
||||
import scala.annotation.unchecked.uncheckedVariance
|
||||
import scala.collection.immutable
|
||||
import scala.collection.mutable
|
||||
|
|
@ -13,7 +15,6 @@ import scala.util.Failure
|
|||
import scala.util.Success
|
||||
import scala.util.Try
|
||||
import scala.util.control.NonFatal
|
||||
|
||||
import akka.NotUsed
|
||||
import akka.actor.ActorRef
|
||||
import akka.actor.Props
|
||||
|
|
@ -428,21 +429,81 @@ import org.reactivestreams.Subscriber
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
*
|
||||
* Helper class to be able to express collection as a fold using mutable data without
|
||||
* accidentally sharing state between materializations
|
||||
*/
|
||||
@InternalApi private[akka] trait CollectorState[T, R] {
|
||||
def accumulated(): Any
|
||||
def update(elem: T): CollectorState[T, R]
|
||||
def finish(): R
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
*
|
||||
* Helper class to be able to express collection as a fold using mutable data
|
||||
*/
|
||||
@InternalApi private[akka] final class CollectorState[T, R](val collector: java.util.stream.Collector[T, Any, R]) {
|
||||
lazy val accumulated = collector.supplier().get()
|
||||
private lazy val accumulator = collector.accumulator()
|
||||
@InternalApi private[akka] final class FirstCollectorState[T, R](
|
||||
collectorFactory: () => java.util.stream.Collector[T, Any, R])
|
||||
extends CollectorState[T, R] {
|
||||
|
||||
def update(elem: T): CollectorState[T, R] = {
|
||||
override def update(elem: T): CollectorState[T, R] = {
|
||||
// on first update, return a new mutable collector to ensure not
|
||||
// sharing collector between streams
|
||||
val collector = collectorFactory()
|
||||
val accumulator = collector.accumulator()
|
||||
val accumulated = collector.supplier().get()
|
||||
accumulator.accept(accumulated, elem)
|
||||
new MutableCollectorState(collector, accumulator, accumulated)
|
||||
}
|
||||
|
||||
override def accumulated(): Any = {
|
||||
// only called if it is asked about accumulated before accepting a first element
|
||||
val collector = collectorFactory()
|
||||
collector.supplier().get()
|
||||
}
|
||||
|
||||
override def finish(): R = {
|
||||
// only called if completed without elements
|
||||
val collector = collectorFactory()
|
||||
collector.finisher().apply(collector.supplier().get())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
*
|
||||
* Helper class to be able to express collection as a fold using mutable data
|
||||
*/
|
||||
@InternalApi private[akka] final class MutableCollectorState[T, R](
|
||||
collector: java.util.stream.Collector[T, Any, R],
|
||||
accumulator: java.util.function.BiConsumer[Any, T],
|
||||
val accumulated: Any)
|
||||
extends CollectorState[T, R] {
|
||||
|
||||
override def update(elem: T): CollectorState[T, R] = {
|
||||
accumulator.accept(accumulated, elem)
|
||||
this
|
||||
}
|
||||
|
||||
def finish(): R = collector.finisher().apply(accumulated)
|
||||
override def finish(): R = {
|
||||
// only called if completed without elements
|
||||
collector.finisher().apply(accumulated)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
*
|
||||
* Helper class to be able to express reduce as a fold for parallel collector without
|
||||
* accidentally sharing state between materializations
|
||||
*/
|
||||
@InternalApi private[akka] trait ReducerState[T, R] {
|
||||
def update(batch: Any): ReducerState[T, R]
|
||||
def finish(): R
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -450,13 +511,38 @@ import org.reactivestreams.Subscriber
|
|||
*
|
||||
* Helper class to be able to express reduce as a fold for parallel collector
|
||||
*/
|
||||
@InternalApi private[akka] final class ReducerState[T, R](val collector: java.util.stream.Collector[T, Any, R]) {
|
||||
private var reduced: Any = null.asInstanceOf[Any]
|
||||
private lazy val combiner = collector.combiner()
|
||||
@InternalApi private[akka] final class FirstReducerState[T, R](
|
||||
collectorFactory: () => java.util.stream.Collector[T, Any, R])
|
||||
extends ReducerState[T, R] {
|
||||
|
||||
def update(batch: Any): ReducerState[T, R] = {
|
||||
if (reduced == null) reduced = batch
|
||||
else reduced = combiner(reduced, batch)
|
||||
// on first update, return a new mutable collector to ensure not
|
||||
// sharing collector between streams
|
||||
val collector = collectorFactory()
|
||||
val combiner = collector.combiner()
|
||||
new MutableReducerState(collector, combiner, batch)
|
||||
}
|
||||
|
||||
def finish(): R = {
|
||||
// only called if completed without elements
|
||||
val collector = collectorFactory()
|
||||
collector.finisher().apply(null)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
*
|
||||
* Helper class to be able to express reduce as a fold for parallel collector
|
||||
*/
|
||||
@InternalApi private[akka] final class MutableReducerState[T, R](
|
||||
val collector: java.util.stream.Collector[T, Any, R],
|
||||
val combiner: BinaryOperator[Any],
|
||||
var reduced: Any)
|
||||
extends ReducerState[T, R] {
|
||||
|
||||
def update(batch: Any): ReducerState[T, R] = {
|
||||
reduced = combiner(reduced, batch)
|
||||
this
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue