2014-04-08 10:28:27 +02:00
|
|
|
/**
|
|
|
|
|
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
|
|
|
|
*/
|
|
|
|
|
package akka.stream.io
|
|
|
|
|
|
|
|
|
|
import akka.io.{ IO, Tcp }
|
|
|
|
|
import scala.util.control.NoStackTrace
|
2014-04-14 15:35:15 +02:00
|
|
|
import akka.actor.{ ActorRefFactory, Actor, Props, ActorRef, Status }
|
2014-04-08 10:28:27 +02:00
|
|
|
import akka.stream.impl._
|
|
|
|
|
import akka.util.ByteString
|
|
|
|
|
import akka.io.Tcp._
|
|
|
|
|
import akka.stream.MaterializerSettings
|
|
|
|
|
import org.reactivestreams.api.Processor
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* INTERNAL API
|
|
|
|
|
*/
|
|
|
|
|
private[akka] object TcpStreamActor {
|
|
|
|
|
case object WriteAck extends Tcp.Event
|
|
|
|
|
class TcpStreamException(msg: String) extends RuntimeException(msg) with NoStackTrace
|
|
|
|
|
|
|
|
|
|
def outboundProps(connectCmd: Connect, requester: ActorRef, settings: MaterializerSettings): Props =
|
2014-05-13 17:17:33 +02:00
|
|
|
Props(new OutboundTcpStreamActor(connectCmd, requester, settings)).withDispatcher(settings.dispatcher)
|
2014-04-08 10:28:27 +02:00
|
|
|
def inboundProps(connection: ActorRef, settings: MaterializerSettings): Props =
|
2014-05-13 17:17:33 +02:00
|
|
|
Props(new InboundTcpStreamActor(connection, settings)).withDispatcher(settings.dispatcher)
|
2014-04-08 10:28:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* INTERNAL API
|
|
|
|
|
*/
|
2014-05-12 16:45:30 +02:00
|
|
|
private[akka] abstract class TcpStreamActor(val settings: MaterializerSettings) extends Actor {
|
2014-04-08 10:28:27 +02:00
|
|
|
|
|
|
|
|
import TcpStreamActor._
|
|
|
|
|
|
2014-05-12 16:45:30 +02:00
|
|
|
val primaryInputs: Inputs = new BatchingInputBuffer(settings.initialInputBufferSize, writePump) {
|
|
|
|
|
override def inputOnError(e: Throwable): Unit = fail(e)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val primaryOutputs: Outputs =
|
|
|
|
|
new FanoutOutputs(settings.maxFanOutBufferSize, settings.initialFanOutBufferSize, self, readPump) {
|
|
|
|
|
override def afterShutdown(): Unit = {
|
|
|
|
|
tcpInputs.cancel()
|
|
|
|
|
TcpStreamActor.this.tryShutdown()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
object tcpInputs extends DefaultInputTransferStates {
|
2014-04-08 10:28:27 +02:00
|
|
|
private var closed: Boolean = false
|
|
|
|
|
private var pendingElement: ByteString = null
|
2014-05-12 16:45:30 +02:00
|
|
|
private var connection: ActorRef = _
|
|
|
|
|
|
|
|
|
|
val subreceive = new SubReceive(Actor.emptyBehavior)
|
|
|
|
|
|
|
|
|
|
def setConnection(c: ActorRef): Unit = {
|
|
|
|
|
connection = c
|
|
|
|
|
// Prefetch
|
|
|
|
|
c ! ResumeReading
|
|
|
|
|
subreceive.become(handleRead)
|
|
|
|
|
readPump.pump()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def handleRead: Receive = {
|
|
|
|
|
case Received(data) ⇒
|
|
|
|
|
pendingElement = data
|
|
|
|
|
readPump.pump()
|
|
|
|
|
case Closed ⇒
|
|
|
|
|
closed = true
|
|
|
|
|
tcpOutputs.complete()
|
|
|
|
|
writePump.pump()
|
|
|
|
|
readPump.pump()
|
|
|
|
|
case ConfirmedClosed ⇒
|
|
|
|
|
closed = true
|
|
|
|
|
readPump.pump()
|
|
|
|
|
case PeerClosed ⇒
|
|
|
|
|
closed = true
|
|
|
|
|
readPump.pump()
|
|
|
|
|
case ErrorClosed(cause) ⇒ fail(new TcpStreamException(s"The connection closed with error $cause"))
|
|
|
|
|
case CommandFailed(cmd) ⇒ fail(new TcpStreamException(s"Tcp command [$cmd] failed"))
|
|
|
|
|
case Aborted ⇒ fail(new TcpStreamException("The connection has been aborted"))
|
|
|
|
|
}
|
2014-04-08 10:28:27 +02:00
|
|
|
|
|
|
|
|
override def inputsAvailable: Boolean = pendingElement ne null
|
|
|
|
|
override def inputsDepleted: Boolean = closed && !inputsAvailable
|
|
|
|
|
override def isClosed: Boolean = closed
|
2014-05-12 16:45:30 +02:00
|
|
|
|
2014-04-08 10:28:27 +02:00
|
|
|
override def cancel(): Unit = {
|
|
|
|
|
closed = true
|
|
|
|
|
pendingElement = null
|
|
|
|
|
}
|
2014-05-12 16:45:30 +02:00
|
|
|
|
2014-04-08 10:28:27 +02:00
|
|
|
override def dequeueInputElement(): Any = {
|
|
|
|
|
val elem = pendingElement
|
|
|
|
|
pendingElement = null
|
|
|
|
|
connection ! ResumeReading
|
|
|
|
|
elem
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2014-04-29 16:51:46 +02:00
|
|
|
object tcpOutputs extends DefaultOutputTransferStates {
|
2014-04-08 10:28:27 +02:00
|
|
|
private var closed: Boolean = false
|
|
|
|
|
private var pendingDemand = true
|
2014-05-12 16:45:30 +02:00
|
|
|
private var connection: ActorRef = _
|
|
|
|
|
|
2014-06-25 11:03:18 +02:00
|
|
|
private def initialized: Boolean = connection ne null
|
|
|
|
|
|
2014-05-12 16:45:30 +02:00
|
|
|
def setConnection(c: ActorRef): Unit = {
|
|
|
|
|
connection = c
|
|
|
|
|
writePump.pump()
|
2014-05-16 14:21:15 +02:00
|
|
|
subreceive.become(handleWrite)
|
2014-05-12 16:45:30 +02:00
|
|
|
}
|
|
|
|
|
|
2014-05-16 14:21:15 +02:00
|
|
|
val subreceive = new SubReceive(Actor.emptyBehavior)
|
2014-05-12 16:45:30 +02:00
|
|
|
|
|
|
|
|
def handleWrite: Receive = {
|
|
|
|
|
case WriteAck ⇒
|
|
|
|
|
pendingDemand = true
|
|
|
|
|
writePump.pump()
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2014-04-08 10:28:27 +02:00
|
|
|
override def isClosed: Boolean = closed
|
|
|
|
|
override def cancel(e: Throwable): Unit = {
|
2014-06-25 11:03:18 +02:00
|
|
|
if (!closed && initialized) connection ! Abort
|
2014-04-08 10:28:27 +02:00
|
|
|
closed = true
|
|
|
|
|
}
|
|
|
|
|
override def complete(): Unit = {
|
2014-06-25 11:03:18 +02:00
|
|
|
if (!closed && initialized) connection ! ConfirmedClose
|
2014-04-08 10:28:27 +02:00
|
|
|
closed = true
|
|
|
|
|
}
|
|
|
|
|
override def enqueueOutputElement(elem: Any): Unit = {
|
|
|
|
|
connection ! Write(elem.asInstanceOf[ByteString], WriteAck)
|
|
|
|
|
pendingDemand = false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override def demandAvailable: Boolean = pendingDemand
|
|
|
|
|
}
|
|
|
|
|
|
2014-04-29 16:51:46 +02:00
|
|
|
object writePump extends Pump {
|
2014-05-12 16:45:30 +02:00
|
|
|
|
|
|
|
|
def running = TransferPhase(primaryInputs.NeedsInput && tcpOutputs.NeedsDemand) { () ⇒
|
2014-04-08 10:28:27 +02:00
|
|
|
var batch = ByteString.empty
|
|
|
|
|
while (primaryInputs.inputsAvailable) batch ++= primaryInputs.dequeueInputElement().asInstanceOf[ByteString]
|
2014-04-14 15:35:15 +02:00
|
|
|
tcpOutputs.enqueueOutputElement(batch)
|
2014-04-08 10:28:27 +02:00
|
|
|
}
|
2014-05-12 16:45:30 +02:00
|
|
|
|
|
|
|
|
override protected def pumpFinished(): Unit = {
|
|
|
|
|
tcpOutputs.complete()
|
|
|
|
|
tryShutdown()
|
|
|
|
|
}
|
2014-04-08 10:28:27 +02:00
|
|
|
override protected def pumpFailed(e: Throwable): Unit = fail(e)
|
|
|
|
|
override protected def pumpContext: ActorRefFactory = context
|
|
|
|
|
}
|
|
|
|
|
|
2014-04-29 16:51:46 +02:00
|
|
|
object readPump extends Pump {
|
2014-05-12 16:45:30 +02:00
|
|
|
|
|
|
|
|
def running = TransferPhase(tcpInputs.NeedsInput && primaryOutputs.NeedsDemand) { () ⇒
|
2014-04-14 15:35:15 +02:00
|
|
|
primaryOutputs.enqueueOutputElement(tcpInputs.dequeueInputElement())
|
2014-04-08 10:28:27 +02:00
|
|
|
}
|
2014-05-12 16:45:30 +02:00
|
|
|
|
|
|
|
|
override protected def pumpFinished(): Unit = {
|
|
|
|
|
primaryOutputs.complete()
|
|
|
|
|
tryShutdown()
|
|
|
|
|
}
|
2014-04-08 10:28:27 +02:00
|
|
|
override protected def pumpFailed(e: Throwable): Unit = fail(e)
|
|
|
|
|
override protected def pumpContext: ActorRefFactory = context
|
|
|
|
|
}
|
|
|
|
|
|
2014-05-12 16:45:30 +02:00
|
|
|
override def receive =
|
2014-05-16 14:21:15 +02:00
|
|
|
primaryInputs.subreceive orElse primaryOutputs.subreceive orElse tcpInputs.subreceive orElse tcpOutputs.subreceive
|
2014-04-08 10:28:27 +02:00
|
|
|
|
2014-05-12 16:45:30 +02:00
|
|
|
readPump.nextPhase(readPump.running)
|
|
|
|
|
writePump.nextPhase(writePump.running)
|
2014-04-08 10:28:27 +02:00
|
|
|
|
|
|
|
|
def fail(e: Throwable): Unit = {
|
2014-04-14 15:35:15 +02:00
|
|
|
tcpInputs.cancel()
|
|
|
|
|
tcpOutputs.cancel(e)
|
2014-05-12 16:45:30 +02:00
|
|
|
primaryInputs.cancel()
|
2014-04-14 15:35:15 +02:00
|
|
|
primaryOutputs.cancel(e)
|
2014-04-08 10:28:27 +02:00
|
|
|
}
|
|
|
|
|
|
2014-05-12 16:45:30 +02:00
|
|
|
def tryShutdown(): Unit = if (primaryInputs.isClosed && tcpInputs.isClosed && tcpOutputs.isClosed) context.stop(self)
|
2014-04-08 10:28:27 +02:00
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* INTERNAL API
|
|
|
|
|
*/
|
|
|
|
|
private[akka] class InboundTcpStreamActor(
|
|
|
|
|
val connection: ActorRef, _settings: MaterializerSettings)
|
|
|
|
|
extends TcpStreamActor(_settings) {
|
|
|
|
|
|
2014-05-12 16:45:30 +02:00
|
|
|
connection ! Register(self, keepOpenOnPeerClosed = true, useResumeWriting = false)
|
|
|
|
|
tcpInputs.setConnection(connection)
|
|
|
|
|
tcpOutputs.setConnection(connection)
|
2014-04-08 10:28:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* INTERNAL API
|
|
|
|
|
*/
|
|
|
|
|
private[akka] class OutboundTcpStreamActor(val connectCmd: Connect, val requester: ActorRef, _settings: MaterializerSettings)
|
|
|
|
|
extends TcpStreamActor(_settings) {
|
|
|
|
|
import TcpStreamActor._
|
|
|
|
|
import context.system
|
|
|
|
|
|
2014-05-12 16:45:30 +02:00
|
|
|
val initSteps = new SubReceive(waitingExposedProcessor)
|
|
|
|
|
|
|
|
|
|
override def receive = initSteps orElse super.receive
|
2014-04-08 10:28:27 +02:00
|
|
|
|
2014-05-12 16:45:30 +02:00
|
|
|
def waitingExposedProcessor: Receive = {
|
2014-04-08 10:28:27 +02:00
|
|
|
case StreamTcpManager.ExposedProcessor(processor) ⇒
|
|
|
|
|
IO(Tcp) ! connectCmd
|
2014-05-12 16:45:30 +02:00
|
|
|
initSteps.become(waitConnection(processor))
|
2014-04-08 10:28:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def waitConnection(exposedProcessor: Processor[ByteString, ByteString]): Receive = {
|
|
|
|
|
case Connected(remoteAddress, localAddress) ⇒
|
2014-05-12 16:45:30 +02:00
|
|
|
val connection = sender()
|
|
|
|
|
connection ! Register(self, keepOpenOnPeerClosed = true, useResumeWriting = false)
|
|
|
|
|
tcpOutputs.setConnection(connection)
|
|
|
|
|
tcpInputs.setConnection(connection)
|
2014-04-08 10:28:27 +02:00
|
|
|
requester ! StreamTcp.OutgoingTcpConnection(remoteAddress, localAddress, exposedProcessor)
|
2014-05-12 16:45:30 +02:00
|
|
|
initSteps.become(Actor.emptyBehavior)
|
2014-04-08 10:28:27 +02:00
|
|
|
case f: CommandFailed ⇒
|
2014-04-14 15:35:15 +02:00
|
|
|
val ex = new TcpStreamException("Connection failed.")
|
|
|
|
|
requester ! Status.Failure(ex)
|
|
|
|
|
fail(ex)
|
2014-04-08 10:28:27 +02:00
|
|
|
}
|
|
|
|
|
}
|