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
import akka.stream.impl.ActorBasedFlowMaterializer
import akka.stream.impl.Ast
import akka.stream.impl.FlowNameCounter
import akka.stream.impl.StreamSupervisor
2014-10-27 14:35:41 +01:00
import scala.collection.immutable
2014-11-03 15:29:02 +01:00
import akka.actor.ActorContext
import akka.actor.ActorRefFactory
import akka.actor.ActorSystem
import akka.actor.ExtendedActorSystem
2014-08-26 09:03:48 +02:00
import com.typesafe.config.Config
2014-10-27 14:35:41 +01:00
import org.reactivestreams.Publisher
import org.reactivestreams.Subscriber
2014-04-01 15:19:42 +02:00
2014-11-03 15:29:02 +01:00
import scala.concurrent.duration._
2014-04-08 13:37:55 +02:00
object FlowMaterializer {
2014-05-08 19:34:58 +02:00
2014-04-01 19:35:56 +02:00
/* *
2014-04-23 10:05:09 +02:00
* Scala API : Creates a FlowMaterializer 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
*
2014-08-26 09:03:48 +02:00
* The materializer 's [ [ akka . stream . MaterializerSettings ] ] will be obtained from the
* 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
*/
2014-08-26 09:03:48 +02:00
def apply ( materializerSettings : Option [ MaterializerSettings ] = None , namePrefix : Option [ String ] = None ) ( implicit context : ActorRefFactory ) : FlowMaterializer = {
val system = actorSystemOf ( context )
val settings = materializerSettings getOrElse MaterializerSettings ( system )
apply ( settings , namePrefix . getOrElse ( "flow" ) ) ( context )
}
/* *
* Scala API : Creates a FlowMaterializer which will execute every step of a transformation
* 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` .
*/
def apply ( materializerSettings : MaterializerSettings , namePrefix : String ) ( implicit context : ActorRefFactory ) : FlowMaterializer = {
val system = actorSystemOf ( context )
2014-08-21 12:35:38 +02:00
new ActorBasedFlowMaterializer (
2014-08-26 09:03:48 +02:00
materializerSettings ,
context . actorOf ( StreamSupervisor . props ( materializerSettings ) . withDispatcher ( materializerSettings . dispatcher ) ) ,
2014-08-21 12:35:38 +02:00
FlowNameCounter ( system ) . counter ,
2014-08-26 09:03:48 +02:00
namePrefix )
2014-08-21 12:35:38 +02:00
}
2014-05-08 19:34:58 +02:00
2014-08-26 09:03:48 +02:00
/* *
* Scala API : Creates a FlowMaterializer which will execute every step of a transformation
* 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` .
*/
def apply ( materializerSettings : MaterializerSettings ) ( implicit context : ActorRefFactory ) : FlowMaterializer =
apply ( Some ( materializerSettings ) , None )
/* *
* Java API : Creates a FlowMaterializer which will execute every step of a transformation
* 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` .
*/
def create ( context : ActorRefFactory ) : FlowMaterializer =
apply ( ) ( context )
2014-04-23 10:05:09 +02:00
/* *
* Java API : Creates a FlowMaterializer which will execute every step of a transformation
* 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
*/
def create ( settings : MaterializerSettings , context : ActorRefFactory ) : FlowMaterializer =
2014-08-26 09:03:48 +02:00
apply ( Option ( settings ) , None ) ( context )
/* *
* Java API : Creates a FlowMaterializer which will execute every step of a transformation
* 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` .
*/
def create ( settings : MaterializerSettings , context : ActorRefFactory , namePrefix : String ) : FlowMaterializer =
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
}
2014-04-01 15:19:42 +02:00
}
2014-04-01 19:35:56 +02:00
/* *
2014-04-08 13:37:55 +02:00
* A FlowMaterializer 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 .
*/
2014-05-22 08:40:41 +02:00
abstract class FlowMaterializer ( val settings : MaterializerSettings ) {
2014-05-08 19:34:58 +02:00
/* *
2014-10-27 14:35:41 +01:00
* The `namePrefix` shall be used for deriving the names of processing
* entities that are created during materialization . This is meant to aid
* logging and error reporting both during materialization and while the
* stream is running .
2014-05-08 19:34:58 +02:00
*/
def withNamePrefix ( name : String ) : FlowMaterializer
2014-10-27 14:35:41 +01:00
// FIXME this is scaladsl specific
2014-04-01 15:19:42 +02:00
/* *
2014-10-27 14:35:41 +01:00
* This method interprets the given Flow description and creates the running
* stream . The result can be highly implementation specific , ranging from
* local actor chains to remote - deployed processing networks .
2014-05-07 15:56:02 +02:00
*/
2014-10-27 14:35:41 +01:00
def materialize [ In , Out ] ( source : scaladsl.Source [ In ] , sink : scaladsl.Sink [ Out ] , ops : List [ Ast . AstNode ] ) : scaladsl.MaterializedMap
2014-05-07 15:56:02 +02:00
/* *
2014-10-27 14:35:41 +01:00
* Create publishers and subscribers for fan - in and fan - out operations .
2014-05-07 15:56:02 +02:00
*/
2014-10-27 14:35:41 +01:00
def materializeJunction [ In , Out ] ( op : Ast . JunctionAstNode , inputCount : Int , outputCount : Int ) : ( immutable.Seq [ Subscriber [ In ] ] , immutable . Seq [ Publisher [ Out ] ] )
2014-05-07 15:56:02 +02:00
2014-04-01 15:19:42 +02:00
}
2014-04-23 10:05:09 +02:00
object MaterializerSettings {
/* *
2014-08-26 09:03:48 +02:00
* Create [ [ MaterializerSettings ] ] .
*
2014-11-06 14:03:01 +01:00
* You can refine the configuration based settings using [ [ MaterializerSettings # withInputBuffer ] ] ,
* [ [ MaterializerSettings # withFanOutBuffer ] ] , [ [ MaterializerSettings # withDispatcher ] ]
2014-04-23 10:05:09 +02:00
*/
2014-08-26 09:03:48 +02:00
def apply ( system : ActorSystem ) : MaterializerSettings =
apply ( system . settings . config . getConfig ( "akka.stream.materializer" ) )
/* *
* Create [ [ MaterializerSettings ] ] .
*
2014-11-06 14:03:01 +01:00
* You can refine the configuration based settings using [ [ MaterializerSettings # withInputBuffer ] ] ,
* [ [ MaterializerSettings # withFanOutBuffer ] ] , [ [ MaterializerSettings # withDispatcher ] ]
2014-08-26 09:03:48 +02:00
*/
def apply ( config : Config ) : MaterializerSettings =
MaterializerSettings (
config . getInt ( "initial-input-buffer-size" ) ,
config . getInt ( "max-input-buffer-size" ) ,
config . getInt ( "initial-fan-out-buffer-size" ) ,
config . getInt ( "max-fan-out-buffer-size" ) ,
2014-11-03 15:29:02 +01:00
config . getString ( "dispatcher" ) ,
StreamSubscriptionTimeoutSettings ( config ) )
2014-08-26 09:03:48 +02:00
/* *
* Java API
*
2014-11-06 14:03:01 +01:00
* You can refine the configuration based settings using [ [ MaterializerSettings # withInputBuffer ] ] ,
* [ [ MaterializerSettings # withFanOutBuffer ] ] , [ [ MaterializerSettings # withDispatcher ] ]
2014-08-26 09:03:48 +02:00
*/
def create ( system : ActorSystem ) : MaterializerSettings =
apply ( system )
/* *
* Java API
*
2014-11-06 14:03:01 +01:00
* You can refine the configuration based settings using [ [ MaterializerSettings # withInputBuffer ] ] ,
* [ [ MaterializerSettings # withFanOutBuffer ] ] , [ [ MaterializerSettings # withDispatcher ] ]
2014-08-26 09:03:48 +02:00
*/
def create ( config : Config ) : MaterializerSettings =
apply ( config )
2014-04-23 10:05:09 +02:00
}
2014-10-27 14:35:41 +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 )
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 .
*/
2014-08-26 09:03:48 +02:00
final case class MaterializerSettings (
initialInputBufferSize : Int ,
maxInputBufferSize : Int ,
initialFanOutBufferSize : Int ,
maxFanOutBufferSize : Int ,
2014-11-03 15:29:02 +01:00
dispatcher : String ,
subscriptionTimeoutSettings : StreamSubscriptionTimeoutSettings ) {
2014-08-26 09:03:48 +02:00
require ( initialInputBufferSize > 0 , "initialInputBufferSize must be > 0" )
require ( maxInputBufferSize > 0 , "maxInputBufferSize must be > 0" )
require ( isPowerOfTwo ( maxInputBufferSize ) , "maxInputBufferSize must be a power of two" )
require ( initialInputBufferSize <= maxInputBufferSize , s" initialInputBufferSize( $initialInputBufferSize ) must be <= maxInputBufferSize( $maxInputBufferSize ) " )
2014-04-01 15:19:42 +02:00
require ( initialFanOutBufferSize > 0 , "initialFanOutBufferSize must be > 0" )
2014-08-26 09:03:48 +02:00
require ( maxFanOutBufferSize > 0 , "maxFanOutBufferSize must be > 0" )
require ( isPowerOfTwo ( maxFanOutBufferSize ) , "maxFanOutBufferSize must be a power of two" )
require ( initialFanOutBufferSize <= maxFanOutBufferSize , s" initialFanOutBufferSize( $initialFanOutBufferSize ) must be <= maxFanOutBufferSize( $maxFanOutBufferSize ) " )
2014-04-23 10:05:09 +02:00
2014-08-26 09:03:48 +02:00
def withInputBuffer ( initialSize : Int , maxSize : Int ) : MaterializerSettings =
copy ( initialInputBufferSize = initialSize , maxInputBufferSize = maxSize )
2014-04-23 10:05:09 +02:00
2014-08-26 09:03:48 +02:00
def withFanOutBuffer ( initialSize : Int , maxSize : Int ) : MaterializerSettings =
copy ( initialFanOutBufferSize = initialSize , maxFanOutBufferSize = maxSize )
2014-04-23 10:05:09 +02:00
2014-08-26 09:03:48 +02:00
def withDispatcher ( dispatcher : String ) : MaterializerSettings =
copy ( dispatcher = dispatcher )
2014-05-13 17:17:33 +02:00
2014-08-26 09:03:48 +02:00
private def isPowerOfTwo ( n : Integer ) : Boolean = ( n & ( n - 1 ) ) == 0
2014-04-01 15:19:42 +02:00
}
2014-11-03 15:29:02 +01:00
object StreamSubscriptionTimeoutSettings {
/* * 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
} ,
timeout = c . getDuration ( "timeout" , TimeUnit . MILLISECONDS ) . millis ,
dispatcher = c . getString ( "dispatcher" ) )
}
}
final case class StreamSubscriptionTimeoutSettings ( mode : StreamSubscriptionTimeoutTerminationMode , timeout : FiniteDuration , dispatcher : String )
sealed abstract class StreamSubscriptionTimeoutTerminationMode
object StreamSubscriptionTimeoutTerminationMode {
/* * Java API */
def noop = NoopTermination
/* * Java API */
def warn = WarnTermination
/* * Java API */
def cancel = CancelTermination
}
case object NoopTermination extends StreamSubscriptionTimeoutTerminationMode
case object WarnTermination extends StreamSubscriptionTimeoutTerminationMode
case object CancelTermination extends StreamSubscriptionTimeoutTerminationMode