2018-10-29 17:19:37 +08:00
|
|
|
/*
|
2020-01-02 07:24:59 -05:00
|
|
|
* Copyright (C) 2009-2020 Lightbend Inc. <https://www.lightbend.com>
|
2013-02-05 15:48:29 +01:00
|
|
|
*/
|
2018-03-13 23:45:55 +09:00
|
|
|
|
2013-02-05 15:48:29 +01:00
|
|
|
package akka.io
|
|
|
|
|
|
2019-05-14 15:20:41 +02:00
|
|
|
import java.net.{ InetSocketAddress, PortUnreachableException }
|
2013-02-07 17:54:42 +01:00
|
|
|
import java.nio.ByteBuffer
|
2013-02-05 15:48:29 +01:00
|
|
|
import java.nio.channels.DatagramChannel
|
|
|
|
|
import java.nio.channels.SelectionKey._
|
2018-12-14 17:48:08 -05:00
|
|
|
|
2020-02-20 14:03:50 +01:00
|
|
|
import akka.actor.Status.Failure
|
|
|
|
|
|
2013-02-06 11:38:42 +01:00
|
|
|
import scala.annotation.tailrec
|
2013-02-07 17:54:42 +01:00
|
|
|
import scala.util.control.NonFatal
|
2013-05-06 17:01:01 +02:00
|
|
|
import akka.actor.{ Actor, ActorLogging, ActorRef }
|
2018-12-14 17:48:08 -05:00
|
|
|
import akka.dispatch.{ RequiresMessageQueue, UnboundedMessageQueueSemantics }
|
2019-03-11 10:38:24 +01:00
|
|
|
import akka.util.{ unused, ByteString }
|
2013-05-06 17:01:01 +02:00
|
|
|
import akka.io.SelectionHandler._
|
|
|
|
|
import akka.io.UdpConnected._
|
2019-10-16 09:22:01 +01:00
|
|
|
import akka.io.dns.DnsProtocol
|
2013-02-05 15:48:29 +01:00
|
|
|
|
2013-02-15 11:59:01 +01:00
|
|
|
/**
|
|
|
|
|
* INTERNAL API
|
|
|
|
|
*/
|
2019-03-13 10:56:20 +01:00
|
|
|
private[io] class UdpConnection(
|
|
|
|
|
udpConn: UdpConnectedExt,
|
|
|
|
|
channelRegistry: ChannelRegistry,
|
|
|
|
|
commander: ActorRef,
|
|
|
|
|
connect: Connect)
|
2019-03-11 10:38:24 +01:00
|
|
|
extends Actor
|
|
|
|
|
with ActorLogging
|
|
|
|
|
with RequiresMessageQueue[UnboundedMessageQueueSemantics] {
|
2013-02-05 15:48:29 +01:00
|
|
|
|
2013-02-06 12:17:52 +01:00
|
|
|
import connect._
|
2013-02-07 17:54:42 +01:00
|
|
|
import udpConn._
|
2013-02-05 15:48:29 +01:00
|
|
|
import udpConn.settings._
|
|
|
|
|
|
|
|
|
|
var pendingSend: (Send, ActorRef) = null
|
|
|
|
|
def writePending = pendingSend ne null
|
|
|
|
|
|
|
|
|
|
context.watch(handler) // sign death pact
|
2014-06-21 16:20:20 +04:00
|
|
|
var channel: DatagramChannel = null
|
|
|
|
|
|
|
|
|
|
if (remoteAddress.isUnresolved) {
|
2019-10-16 09:22:01 +01:00
|
|
|
Dns.resolve(DnsProtocol.Resolve(remoteAddress.getHostName), context.system, self) match {
|
2019-02-09 15:25:39 +01:00
|
|
|
case Some(r) =>
|
2020-01-07 08:19:14 +01:00
|
|
|
reportConnectFailure {
|
|
|
|
|
doConnect(new InetSocketAddress(r.address(), remoteAddress.getPort))
|
|
|
|
|
}
|
2019-02-09 15:25:39 +01:00
|
|
|
case None =>
|
2020-01-07 08:19:14 +01:00
|
|
|
context.become(resolving())
|
2014-06-21 16:20:20 +04:00
|
|
|
}
|
|
|
|
|
} else {
|
2020-02-20 14:03:50 +01:00
|
|
|
reportConnectFailure {
|
|
|
|
|
doConnect(remoteAddress)
|
|
|
|
|
}
|
2014-06-21 16:20:20 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def resolving(): Receive = {
|
2019-10-16 09:22:01 +01:00
|
|
|
case r: DnsProtocol.Resolved =>
|
2014-06-21 16:20:20 +04:00
|
|
|
reportConnectFailure {
|
2019-10-16 09:22:01 +01:00
|
|
|
doConnect(new InetSocketAddress(r.address(), remoteAddress.getPort))
|
2014-06-21 16:20:20 +04:00
|
|
|
}
|
2020-02-20 14:03:50 +01:00
|
|
|
case Failure(ex) =>
|
|
|
|
|
// async-dns responds with a Failure on DNS server lookup failure
|
|
|
|
|
reportConnectFailure {
|
|
|
|
|
throw new RuntimeException(ex)
|
|
|
|
|
}
|
2014-06-21 16:20:20 +04:00
|
|
|
}
|
|
|
|
|
|
2018-12-14 17:48:08 -05:00
|
|
|
def doConnect(@unused address: InetSocketAddress): Unit = {
|
2020-02-20 14:03:50 +01:00
|
|
|
channel = DatagramChannel.open
|
|
|
|
|
channel.configureBlocking(false)
|
|
|
|
|
val socket = channel.socket
|
|
|
|
|
options.foreach(_.beforeDatagramBind(socket))
|
|
|
|
|
localAddress.foreach(socket.bind)
|
|
|
|
|
channel.connect(remoteAddress)
|
|
|
|
|
channelRegistry.register(channel, OP_READ)
|
|
|
|
|
|
2014-06-21 16:20:20 +04:00
|
|
|
log.debug("Successfully connected to [{}]", remoteAddress)
|
2013-02-05 15:48:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def receive = {
|
2019-02-09 15:25:39 +01:00
|
|
|
case registration: ChannelRegistration =>
|
2015-04-30 09:23:18 +02:00
|
|
|
options.foreach {
|
2019-02-09 15:25:39 +01:00
|
|
|
case v2: Inet.SocketOptionV2 => v2.afterConnect(channel.socket)
|
|
|
|
|
case _ =>
|
2015-04-30 09:23:18 +02:00
|
|
|
}
|
2013-02-06 12:17:52 +01:00
|
|
|
commander ! Connected
|
2013-05-06 17:01:01 +02:00
|
|
|
context.become(connected(registration), discardOld = true)
|
2013-02-05 15:48:29 +01:00
|
|
|
}
|
|
|
|
|
|
2013-05-06 17:01:01 +02:00
|
|
|
def connected(registration: ChannelRegistration): Receive = {
|
2019-02-09 15:25:39 +01:00
|
|
|
case SuspendReading => registration.disableInterest(OP_READ)
|
|
|
|
|
case ResumeReading => registration.enableInterest(OP_READ)
|
|
|
|
|
case ChannelReadable => doRead(registration, handler)
|
2013-02-05 15:48:29 +01:00
|
|
|
|
2019-02-09 15:25:39 +01:00
|
|
|
case Disconnect =>
|
2013-02-10 13:52:52 +01:00
|
|
|
log.debug("Closing UDP connection to [{}]", remoteAddress)
|
2013-02-05 15:48:29 +01:00
|
|
|
channel.close()
|
2014-01-16 15:16:35 +01:00
|
|
|
sender() ! Disconnected
|
2013-02-10 13:52:52 +01:00
|
|
|
log.debug("Connection closed to [{}], stopping listener", remoteAddress)
|
2013-02-05 15:48:29 +01:00
|
|
|
context.stop(self)
|
|
|
|
|
|
2019-02-09 15:25:39 +01:00
|
|
|
case send: Send if writePending =>
|
2013-02-05 15:48:29 +01:00
|
|
|
if (TraceLogging) log.debug("Dropping write because queue is full")
|
2014-01-16 15:16:35 +01:00
|
|
|
sender() ! CommandFailed(send)
|
2013-02-05 15:48:29 +01:00
|
|
|
|
2019-02-09 15:25:39 +01:00
|
|
|
case send: Send if send.payload.isEmpty =>
|
2013-02-05 15:48:29 +01:00
|
|
|
if (send.wantsAck)
|
2014-01-16 15:16:35 +01:00
|
|
|
sender() ! send.ack
|
2013-02-05 15:48:29 +01:00
|
|
|
|
2019-02-09 15:25:39 +01:00
|
|
|
case send: Send =>
|
2014-01-16 15:16:35 +01:00
|
|
|
pendingSend = (send, sender())
|
2013-05-06 17:01:01 +02:00
|
|
|
registration.enableInterest(OP_WRITE)
|
2013-02-05 15:48:29 +01:00
|
|
|
|
2019-02-09 15:25:39 +01:00
|
|
|
case ChannelWritable => doWrite()
|
2013-02-05 15:48:29 +01:00
|
|
|
}
|
|
|
|
|
|
2013-05-06 17:01:01 +02:00
|
|
|
def doRead(registration: ChannelRegistration, handler: ActorRef): Unit = {
|
2013-02-06 11:38:42 +01:00
|
|
|
@tailrec def innerRead(readsLeft: Int, buffer: ByteBuffer): Unit = {
|
2013-02-05 15:48:29 +01:00
|
|
|
buffer.clear()
|
|
|
|
|
buffer.limit(DirectBufferSize)
|
|
|
|
|
|
2013-02-06 11:38:42 +01:00
|
|
|
if (channel.read(buffer) > 0) {
|
2013-02-07 17:54:42 +01:00
|
|
|
buffer.flip()
|
2013-02-06 11:38:42 +01:00
|
|
|
handler ! Received(ByteString(buffer))
|
|
|
|
|
innerRead(readsLeft - 1, buffer)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
val buffer = bufferPool.acquire()
|
2019-03-11 10:38:24 +01:00
|
|
|
try innerRead(BatchReceiveLimit, buffer)
|
2019-05-14 15:20:41 +02:00
|
|
|
catch {
|
|
|
|
|
case _: PortUnreachableException =>
|
|
|
|
|
if (TraceLogging) log.debug("Ignoring PortUnreachableException in doRead")
|
|
|
|
|
} finally {
|
2013-05-06 17:01:01 +02:00
|
|
|
registration.enableInterest(OP_READ)
|
2013-02-05 15:48:29 +01:00
|
|
|
bufferPool.release(buffer)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final def doWrite(): Unit = {
|
|
|
|
|
val buffer = udpConn.bufferPool.acquire()
|
|
|
|
|
try {
|
|
|
|
|
val (send, commander) = pendingSend
|
|
|
|
|
buffer.clear()
|
|
|
|
|
send.payload.copyToBuffer(buffer)
|
|
|
|
|
buffer.flip()
|
|
|
|
|
val writtenBytes = channel.write(buffer)
|
2013-02-10 13:52:52 +01:00
|
|
|
if (TraceLogging) log.debug("Wrote [{}] bytes to channel", writtenBytes)
|
2013-02-05 15:48:29 +01:00
|
|
|
|
|
|
|
|
// Datagram channel either sends the whole message, or nothing
|
|
|
|
|
if (writtenBytes == 0) commander ! CommandFailed(send)
|
|
|
|
|
else if (send.wantsAck) commander ! send.ack
|
|
|
|
|
} finally {
|
|
|
|
|
udpConn.bufferPool.release(buffer)
|
|
|
|
|
pendingSend = null
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-05-06 17:01:01 +02:00
|
|
|
override def postStop(): Unit =
|
2019-10-16 09:22:01 +01:00
|
|
|
if (channel != null && channel.isOpen) {
|
2013-02-05 15:48:29 +01:00
|
|
|
log.debug("Closing DatagramChannel after being stopped")
|
|
|
|
|
try channel.close()
|
|
|
|
|
catch {
|
2019-02-09 15:25:39 +01:00
|
|
|
case NonFatal(e) => log.debug("Error closing DatagramChannel: {}", e)
|
2013-02-05 15:48:29 +01:00
|
|
|
}
|
|
|
|
|
}
|
2014-06-21 16:20:20 +04:00
|
|
|
|
2019-02-09 15:25:39 +01:00
|
|
|
private def reportConnectFailure(thunk: => Unit): Unit = {
|
2014-06-21 16:20:20 +04:00
|
|
|
try {
|
|
|
|
|
thunk
|
|
|
|
|
} catch {
|
2019-02-09 15:25:39 +01:00
|
|
|
case NonFatal(e) =>
|
2019-03-13 10:56:20 +01:00
|
|
|
log.debug(
|
|
|
|
|
"Failure while connecting UDP channel to remote address [{}] local address [{}]: {}",
|
|
|
|
|
remoteAddress,
|
|
|
|
|
localAddress.getOrElse("undefined"),
|
|
|
|
|
e)
|
2014-06-21 16:20:20 +04:00
|
|
|
commander ! CommandFailed(connect)
|
|
|
|
|
context.stop(self)
|
|
|
|
|
}
|
|
|
|
|
}
|
2013-02-05 15:48:29 +01:00
|
|
|
}
|