2014-04-01 15:19:42 +02:00
/* *
* Copyright ( C ) 2014 Typesafe Inc . < http : //www.typesafe.com>
*/
package akka.stream
2014-11-03 15:29:02 +01:00
import java.util.Locale
import java.util.concurrent.TimeUnit
2015-04-16 02:24:01 +02:00
2015-01-28 14:19:50 +01:00
import akka.actor. { ActorContext , ActorRef , ActorRefFactory , ActorSystem , ExtendedActorSystem , Props }
2014-11-09 21:09:50 +01:00
import akka.stream.impl._
2014-08-26 09:03:48 +02:00
import com.typesafe.config.Config
2015-04-16 02:24:01 +02:00
2014-11-03 15:29:02 +01:00
import scala.concurrent.duration._
2015-04-09 12:21:12 +02:00
import akka.japi.function
2014-11-03 15:29:02 +01:00
2015-05-12 15:44:18 +02:00
import scala.util.control.NoStackTrace
2015-01-27 18:29:20 +01:00
object ActorFlowMaterializer {
2014-05-08 19:34:58 +02:00
2014-04-01 19:35:56 +02:00
/* *
2015-01-27 18:29:20 +01:00
* Scala API : Creates a ActorFlowMaterializer which will execute every step of a transformation
2014-04-01 19:35:56 +02:00
* pipeline within its own [ [ akka . actor . Actor ] ] . The required [ [ akka . actor . ActorRefFactory ] ]
2014-04-02 09:03:59 +02:00
* ( which can be either an [ [ akka . actor . ActorSystem ] ] or an [ [ akka . actor . ActorContext ] ] )
2014-09-01 13:12:18 +02:00
* will be used to create one actor that in turn creates actors for the transformation steps .
2014-05-08 19:34:58 +02:00
*
2015-01-27 18:29:20 +01:00
* The materializer 's [ [ akka . stream . ActorFlowMaterializerSettings ] ] will be obtained from the
2014-08-26 09:03:48 +02:00
* configuration of the `context` 's underlying [ [ akka . actor . ActorSystem ] ] .
*
2014-05-08 19:34:58 +02:00
* The `namePrefix` is used as the first part of the names of the actors running
* the processing steps . The default `namePrefix` is `"flow"` . The actor names are built up of
* `namePrefix-flowNumber-flowStepNumber-stepName` .
2014-04-01 19:35:56 +02:00
*/
2015-01-28 14:19:50 +01:00
def apply ( materializerSettings : Option [ ActorFlowMaterializerSettings ] = None , namePrefix : Option [ String ] = None , optimizations : Optimizations = Optimizations . none ) ( implicit context : ActorRefFactory ) : ActorFlowMaterializer = {
2014-08-26 09:03:48 +02:00
val system = actorSystemOf ( context )
2015-01-27 18:29:20 +01:00
val settings = materializerSettings getOrElse ActorFlowMaterializerSettings ( system )
2015-01-28 14:19:50 +01:00
apply ( settings , namePrefix . getOrElse ( "flow" ) , optimizations ) ( context )
2014-08-26 09:03:48 +02:00
}
/* *
2015-01-27 18:29:20 +01:00
* Scala API : Creates a ActorFlowMaterializer which will execute every step of a transformation
2014-08-26 09:03:48 +02:00
* pipeline within its own [ [ akka . actor . Actor ] ] . The required [ [ akka . actor . ActorRefFactory ] ]
* ( which can be either an [ [ akka . actor . ActorSystem ] ] or an [ [ akka . actor . ActorContext ] ] )
* will be used to create these actors , therefore it is * forbidden * to pass this object
* to another actor if the factory is an ActorContext .
*
* The `namePrefix` is used as the first part of the names of the actors running
* the processing steps . The default `namePrefix` is `"flow"` . The actor names are built up of
* `namePrefix-flowNumber-flowStepNumber-stepName` .
*/
2015-01-28 14:19:50 +01:00
def apply ( materializerSettings : ActorFlowMaterializerSettings , namePrefix : String , optimizations : Optimizations ) ( implicit context : ActorRefFactory ) : ActorFlowMaterializer = {
2014-08-26 09:03:48 +02:00
val system = actorSystemOf ( context )
2014-08-21 12:35:38 +02:00
2015-01-27 18:29:20 +01:00
new ActorFlowMaterializerImpl (
2015-04-16 02:24:01 +02:00
system ,
2014-08-26 09:03:48 +02:00
materializerSettings ,
2014-11-17 22:50:15 +01:00
system . dispatchers ,
2014-08-26 09:03:48 +02:00
context . actorOf ( StreamSupervisor . props ( materializerSettings ) . withDispatcher ( materializerSettings . dispatcher ) ) ,
2014-08-21 12:35:38 +02:00
FlowNameCounter ( system ) . counter ,
2015-01-28 14:19:50 +01:00
namePrefix ,
optimizations )
2014-08-21 12:35:38 +02:00
}
2014-05-08 19:34:58 +02:00
2014-08-26 09:03:48 +02:00
/* *
2015-01-27 18:29:20 +01:00
* Scala API : Creates a ActorFlowMaterializer which will execute every step of a transformation
2014-08-26 09:03:48 +02:00
* pipeline within its own [ [ akka . actor . Actor ] ] . The required [ [ akka . actor . ActorRefFactory ] ]
* ( which can be either an [ [ akka . actor . ActorSystem ] ] or an [ [ akka . actor . ActorContext ] ] )
* will be used to create these actors , therefore it is * forbidden * to pass this object
* to another actor if the factory is an ActorContext .
*
* The `namePrefix` is used as the first part of the names of the actors running
* the processing steps . The default `namePrefix` is `"flow"` . The actor names are built up of
* `namePrefix-flowNumber-flowStepNumber-stepName` .
*/
2015-01-27 18:29:20 +01:00
def apply ( materializerSettings : ActorFlowMaterializerSettings ) ( implicit context : ActorRefFactory ) : ActorFlowMaterializer =
2014-08-26 09:03:48 +02:00
apply ( Some ( materializerSettings ) , None )
/* *
2015-01-27 18:29:20 +01:00
* Java API : Creates a ActorFlowMaterializer which will execute every step of a transformation
2014-08-26 09:03:48 +02:00
* pipeline within its own [ [ akka . actor . Actor ] ] . The required [ [ akka . actor . ActorRefFactory ] ]
* ( which can be either an [ [ akka . actor . ActorSystem ] ] or an [ [ akka . actor . ActorContext ] ] )
* will be used to create these actors , therefore it is * forbidden * to pass this object
* to another actor if the factory is an ActorContext .
*
* Defaults the actor name prefix used to name actors running the processing steps to `"flow"` .
* The actor names are built up of `namePrefix-flowNumber-flowStepNumber-stepName` .
*/
2015-01-27 18:29:20 +01:00
def create ( context : ActorRefFactory ) : ActorFlowMaterializer =
2014-08-26 09:03:48 +02:00
apply ( ) ( context )
2014-04-23 10:05:09 +02:00
/* *
2015-01-27 18:29:20 +01:00
* Java API : Creates a ActorFlowMaterializer which will execute every step of a transformation
2014-04-23 10:05:09 +02:00
* pipeline within its own [ [ akka . actor . Actor ] ] . The required [ [ akka . actor . ActorRefFactory ] ]
* ( which can be either an [ [ akka . actor . ActorSystem ] ] or an [ [ akka . actor . ActorContext ] ] )
2014-09-01 13:12:18 +02:00
* will be used to create one actor that in turn creates actors for the transformation steps .
2014-04-23 10:05:09 +02:00
*/
2015-01-27 18:29:20 +01:00
def create ( settings : ActorFlowMaterializerSettings , context : ActorRefFactory ) : ActorFlowMaterializer =
2014-08-26 09:03:48 +02:00
apply ( Option ( settings ) , None ) ( context )
/* *
2015-01-27 18:29:20 +01:00
* Java API : Creates a ActorFlowMaterializer which will execute every step of a transformation
2014-08-26 09:03:48 +02:00
* pipeline within its own [ [ akka . actor . Actor ] ] . The required [ [ akka . actor . ActorRefFactory ] ]
* ( which can be either an [ [ akka . actor . ActorSystem ] ] or an [ [ akka . actor . ActorContext ] ] )
* will be used to create these actors , therefore it is * forbidden * to pass this object
* to another actor if the factory is an ActorContext .
*
* The `namePrefix` is used as the first part of the names of the actors running
* the processing steps . The default `namePrefix` is `"flow"` . The actor names are built up of
* `namePrefix-flowNumber-flowStepNumber-stepName` .
*/
2015-01-27 18:29:20 +01:00
def create ( settings : ActorFlowMaterializerSettings , context : ActorRefFactory , namePrefix : String ) : ActorFlowMaterializer =
2014-08-26 09:03:48 +02:00
apply ( Option ( settings ) , Option ( namePrefix ) ) ( context )
private def actorSystemOf ( context : ActorRefFactory ) : ActorSystem = {
val system = context match {
case s : ExtendedActorSystem ⇒ s
case c : ActorContext ⇒ c . system
case null ⇒ throw new IllegalArgumentException ( "ActorRefFactory context must be defined" )
case _ ⇒
throw new IllegalArgumentException ( s" ActorRefFactory context must be a ActorSystem or ActorContext, got [ ${ context . getClass . getName } ] " )
}
system
}
2015-04-10 14:39:48 +02:00
/* *
* INTERNAL API
*/
private [ akka ] def downcast ( materializer : FlowMaterializer ) : ActorFlowMaterializer =
materializer match {
case m : ActorFlowMaterializer ⇒ m
case _ ⇒ throw new IllegalArgumentException ( s" required [ ${ classOf [ ActorFlowMaterializer ] . getName } ] " +
s" but got [ ${ materializer . getClass . getName } ] " )
}
2014-04-01 15:19:42 +02:00
}
2014-04-01 19:35:56 +02:00
/* *
2015-01-27 18:29:20 +01:00
* A ActorFlowMaterializer takes the list of transformations comprising a
2014-04-01 19:35:56 +02:00
* [ [ akka . stream . scaladsl . Flow ] ] and materializes them in the form of
2014-07-22 12:21:53 +02:00
* [ [ org . reactivestreams . Processor ] ] instances . How transformation
2014-04-01 19:35:56 +02:00
* steps are split up into asynchronous regions is implementation
* dependent .
*/
2015-01-27 18:29:20 +01:00
abstract class ActorFlowMaterializer extends FlowMaterializer {
def settings : ActorFlowMaterializerSettings
2015-04-10 14:39:48 +02:00
def effectiveSettings ( opAttr : OperationAttributes ) : ActorFlowMaterializerSettings
2015-01-27 18:29:20 +01:00
/* *
2015-04-10 14:39:48 +02:00
* INTERNAL API : this might become public later
2015-01-27 18:29:20 +01:00
*/
2015-04-10 14:39:48 +02:00
private [ akka ] def actorOf ( context : MaterializationContext , props : Props ) : ActorRef
2015-01-27 18:29:20 +01:00
2015-04-09 12:21:12 +02:00
/* *
* INTERNAL API
*/
private [ akka ] def system : ActorSystem
2015-01-27 18:29:20 +01:00
}
/* *
* This exception or subtypes thereof should be used to signal materialization
* failures .
*/
class MaterializationException ( msg : String , cause : Throwable = null ) extends RuntimeException ( msg , cause )
2015-05-12 15:44:18 +02:00
/* *
* This exception signals that an actor implementing a Reactive Streams Subscriber , Publisher or Processor
* has been terminated without being notified by an onError , onComplete or cancel signal . This usually happens
* when an ActorSystem is shut down while stream processing actors are still running .
*/
final case class AbruptTerminationException ( actor : ActorRef )
extends RuntimeException ( s" Processor actor [ $actor ] terminated abruptly " ) with NoStackTrace
2015-01-27 18:29:20 +01:00
object ActorFlowMaterializerSettings {
2015-05-05 12:32:49 +02:00
def apply (
initialInputBufferSize : Int ,
maxInputBufferSize : Int ,
dispatcher : String ,
supervisionDecider : Supervision . Decider ,
subscriptionTimeoutSettings : StreamSubscriptionTimeoutSettings ,
debugLogging : Boolean ,
outputBurstLimit : Int ,
optimizations : Optimizations ) =
new ActorFlowMaterializerSettings (
initialInputBufferSize , maxInputBufferSize , dispatcher , supervisionDecider , subscriptionTimeoutSettings , debugLogging ,
outputBurstLimit , optimizations )
2014-04-23 10:05:09 +02:00
/* *
2015-01-27 18:29:20 +01:00
* Create [ [ ActorFlowMaterializerSettings ] ] .
2014-08-26 09:03:48 +02:00
*
2015-01-27 18:29:20 +01:00
* You can refine the configuration based settings using [ [ ActorFlowMaterializerSettings # withInputBuffer ] ] ,
* [ [ ActorFlowMaterializerSettings # withDispatcher ] ]
2014-04-23 10:05:09 +02:00
*/
2015-01-27 18:29:20 +01:00
def apply ( system : ActorSystem ) : ActorFlowMaterializerSettings =
2014-08-26 09:03:48 +02:00
apply ( system . settings . config . getConfig ( "akka.stream.materializer" ) )
/* *
2015-01-27 18:29:20 +01:00
* Create [ [ ActorFlowMaterializerSettings ] ] .
2014-08-26 09:03:48 +02:00
*
2015-01-27 18:29:20 +01:00
* You can refine the configuration based settings using [ [ ActorFlowMaterializerSettings # withInputBuffer ] ] ,
* [ [ ActorFlowMaterializerSettings # withDispatcher ] ]
2014-08-26 09:03:48 +02:00
*/
2015-01-27 18:29:20 +01:00
def apply ( config : Config ) : ActorFlowMaterializerSettings =
ActorFlowMaterializerSettings (
initialInputBufferSize = config . getInt ( "initial-input-buffer-size" ) ,
maxInputBufferSize = config . getInt ( "max-input-buffer-size" ) ,
dispatcher = config . getString ( "dispatcher" ) ,
2015-02-04 09:26:32 +01:00
supervisionDecider = Supervision . stoppingDecider ,
2015-01-27 18:29:20 +01:00
subscriptionTimeoutSettings = StreamSubscriptionTimeoutSettings ( config ) ,
debugLogging = config . getBoolean ( "debug-logging" ) ,
2015-03-03 21:05:53 +01:00
outputBurstLimit = config . getInt ( "output-burst-limit" ) ,
2015-01-27 18:29:20 +01:00
optimizations = Optimizations . none )
2014-08-26 09:03:48 +02:00
/* *
* Java API
*
2015-01-27 18:29:20 +01:00
* You can refine the configuration based settings using [ [ ActorFlowMaterializerSettings # withInputBuffer ] ] ,
* [ [ ActorFlowMaterializerSettings # withDispatcher ] ]
2014-08-26 09:03:48 +02:00
*/
2015-01-27 18:29:20 +01:00
def create ( system : ActorSystem ) : ActorFlowMaterializerSettings =
2014-08-26 09:03:48 +02:00
apply ( system )
/* *
* Java API
*
2015-01-27 18:29:20 +01:00
* You can refine the configuration based settings using [ [ ActorFlowMaterializerSettings # withInputBuffer ] ] ,
* [ [ ActorFlowMaterializerSettings # withDispatcher ] ]
2014-08-26 09:03:48 +02:00
*/
2015-01-27 18:29:20 +01:00
def create ( config : Config ) : ActorFlowMaterializerSettings =
2014-08-26 09:03:48 +02:00
apply ( config )
2014-04-23 10:05:09 +02:00
}
2014-04-01 19:35:56 +02:00
/* *
* The buffers employed by the generated Processors can be configured by
* creating an appropriate instance of this class .
2014-04-02 09:03:59 +02:00
*
2014-04-01 19:35:56 +02:00
* This will likely be replaced in the future by auto - tuning these values at runtime .
*/
2015-05-05 12:32:49 +02:00
final class ActorFlowMaterializerSettings (
val initialInputBufferSize : Int ,
val maxInputBufferSize : Int ,
val dispatcher : String ,
val supervisionDecider : Supervision . Decider ,
val subscriptionTimeoutSettings : StreamSubscriptionTimeoutSettings ,
val debugLogging : Boolean ,
val outputBurstLimit : Int ,
val optimizations : Optimizations ) {
2014-08-26 09:03:48 +02:00
require ( initialInputBufferSize > 0 , "initialInputBufferSize must be > 0" )
2015-01-28 14:19:50 +01:00
requirePowerOfTwo ( maxInputBufferSize , "maxInputBufferSize" )
2014-08-26 09:03:48 +02:00
require ( initialInputBufferSize <= maxInputBufferSize , s" initialInputBufferSize( $initialInputBufferSize ) must be <= maxInputBufferSize( $maxInputBufferSize ) " )
2014-04-01 15:19:42 +02:00
2015-05-05 12:32:49 +02:00
private def copy (
initialInputBufferSize : Int = this . initialInputBufferSize ,
maxInputBufferSize : Int = this . maxInputBufferSize ,
dispatcher : String = this . dispatcher ,
supervisionDecider : Supervision . Decider = this . supervisionDecider ,
subscriptionTimeoutSettings : StreamSubscriptionTimeoutSettings = this . subscriptionTimeoutSettings ,
debugLogging : Boolean = this . debugLogging ,
outputBurstLimit : Int = this . outputBurstLimit ,
optimizations : Optimizations = this . optimizations ) =
new ActorFlowMaterializerSettings (
initialInputBufferSize , maxInputBufferSize , dispatcher , supervisionDecider , subscriptionTimeoutSettings , debugLogging ,
outputBurstLimit , optimizations )
2015-01-27 18:29:20 +01:00
def withInputBuffer ( initialSize : Int , maxSize : Int ) : ActorFlowMaterializerSettings =
2014-08-26 09:03:48 +02:00
copy ( initialInputBufferSize = initialSize , maxInputBufferSize = maxSize )
2014-04-23 10:05:09 +02:00
2015-01-27 18:29:20 +01:00
def withDispatcher ( dispatcher : String ) : ActorFlowMaterializerSettings =
2014-08-26 09:03:48 +02:00
copy ( dispatcher = dispatcher )
2014-05-13 17:17:33 +02:00
2015-02-04 09:26:32 +01:00
/* *
* Scala API : Decides how exceptions from application code are to be handled , unless
2015-04-09 15:16:59 +02:00
* overridden for specific flows of the stream operations with
2015-04-14 08:59:37 +02:00
* [ [ akka . stream . OperationAttributes # supervisionStrategy ] ] .
2015-02-04 09:26:32 +01:00
*/
def withSupervisionStrategy ( decider : Supervision . Decider ) : ActorFlowMaterializerSettings =
copy ( supervisionDecider = decider )
/* *
* Java API : Decides how exceptions from application code are to be handled , unless
2015-04-09 15:16:59 +02:00
* overridden for specific flows of the stream operations with
2015-04-14 08:59:37 +02:00
* [ [ akka . stream . OperationAttributes # supervisionStrategy ] ] .
2015-02-04 09:26:32 +01:00
*/
2015-04-09 12:21:12 +02:00
def withSupervisionStrategy ( decider : function.Function [ Throwable , Supervision . Directive ] ) : ActorFlowMaterializerSettings = {
2015-02-23 11:54:02 +01:00
import Supervision._
copy ( supervisionDecider = decider match {
2015-02-26 22:42:34 +01:00
case `resumingDecider` ⇒ resumingDecider
case `restartingDecider` ⇒ restartingDecider
case `stoppingDecider` ⇒ stoppingDecider
case other ⇒ other . apply _
2015-02-23 11:54:02 +01:00
} )
}
2015-02-04 09:26:32 +01:00
2015-01-27 18:29:20 +01:00
def withDebugLogging ( enable : Boolean ) : ActorFlowMaterializerSettings =
2015-01-27 13:36:13 +01:00
copy ( debugLogging = enable )
2015-01-27 18:29:20 +01:00
def withOptimizations ( optimizations : Optimizations ) : ActorFlowMaterializerSettings =
copy ( optimizations = optimizations )
2015-01-28 14:19:50 +01:00
private def requirePowerOfTwo ( n : Integer , name : String ) : Unit = {
require ( n > 0 , s" $name must be > 0 " )
require ( ( n & ( n - 1 ) ) == 0 , s" $name must be a power of two " )
}
2014-04-01 15:19:42 +02:00
}
2014-11-03 15:29:02 +01:00
object StreamSubscriptionTimeoutSettings {
2015-01-28 14:19:50 +01:00
import akka.stream.StreamSubscriptionTimeoutTerminationMode._
2014-11-03 15:29:02 +01:00
2015-05-05 12:32:49 +02:00
def apply ( mode : StreamSubscriptionTimeoutTerminationMode , timeout : FiniteDuration ) : StreamSubscriptionTimeoutSettings =
new StreamSubscriptionTimeoutSettings ( mode , timeout )
2014-11-03 15:29:02 +01:00
/* * Java API */
def create ( config : Config ) : StreamSubscriptionTimeoutSettings =
apply ( config )
def apply ( config : Config ) : StreamSubscriptionTimeoutSettings = {
val c = config . getConfig ( "subscription-timeout" )
StreamSubscriptionTimeoutSettings (
mode = c . getString ( "mode" ) . toLowerCase ( Locale . ROOT ) match {
case "no" | "off" | "false" | "noop" ⇒ NoopTermination
case "warn" ⇒ WarnTermination
case "cancel" ⇒ CancelTermination
} ,
2014-11-12 13:05:57 +01:00
timeout = c . getDuration ( "timeout" , TimeUnit . MILLISECONDS ) . millis )
2014-11-03 15:29:02 +01:00
}
}
2015-05-05 12:32:49 +02:00
final class StreamSubscriptionTimeoutSettings ( val mode : StreamSubscriptionTimeoutTerminationMode , val timeout : FiniteDuration )
2014-11-03 15:29:02 +01:00
sealed abstract class StreamSubscriptionTimeoutTerminationMode
2014-11-12 13:05:57 +01:00
2014-11-03 15:29:02 +01:00
object StreamSubscriptionTimeoutTerminationMode {
2014-11-12 13:05:57 +01:00
case object NoopTermination extends StreamSubscriptionTimeoutTerminationMode
case object WarnTermination extends StreamSubscriptionTimeoutTerminationMode
case object CancelTermination extends StreamSubscriptionTimeoutTerminationMode
2014-11-03 15:29:02 +01:00
/* * Java API */
def noop = NoopTermination
/* * Java API */
def warn = WarnTermination
/* * Java API */
def cancel = CancelTermination
2014-11-12 13:05:57 +01:00
2014-11-03 15:29:02 +01:00
}
2015-01-27 18:29:20 +01:00
final object Optimizations {
val none : Optimizations = Optimizations ( collapsing = false , elision = false , simplification = false , fusion = false )
val all : Optimizations = Optimizations ( collapsing = true , elision = true , simplification = true , fusion = true )
}
final case class Optimizations ( collapsing : Boolean , elision : Boolean , simplification : Boolean , fusion : Boolean ) {
def isEnabled : Boolean = collapsing || elision || simplification || fusion
}