Fix stack overflow in stream converters (#27305)

This commit is contained in:
Johan Andrén 2019-07-09 13:58:26 +02:00 committed by GitHub
parent 0037998bfb
commit 6e69bc8713
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 426 additions and 273 deletions

View file

@ -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
}