UdpConnectedIntegrationSpec dns fail fix (#28558)

* More generous timeout for dns resolution failure #28133

* Use async-dns as workaround for JDK/glibc/whatever resolution bug

* Handle Async DNS lookup failure in UDP and TCP connections
This commit is contained in:
Johan Andrén 2020-02-20 14:03:50 +01:00 committed by GitHub
parent 5bb9a7145a
commit 1df2c2d53a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 41 additions and 13 deletions

View file

@ -6,15 +6,24 @@ package akka.io
import java.net.InetSocketAddress import java.net.InetSocketAddress
import akka.testkit.{ AkkaSpec, ImplicitSender, TestProbe }
import akka.util.ByteString
import akka.actor.ActorRef import akka.actor.ActorRef
import akka.testkit.SocketUtil.temporaryServerAddresses import akka.testkit.SocketUtil.temporaryServerAddresses
import akka.testkit.WithLogCapturing import akka.testkit.WithLogCapturing
import akka.testkit.AkkaSpec
import akka.testkit.ImplicitSender
import akka.testkit.TestProbe
import akka.util.ByteString
import scala.concurrent.duration._ import scala.concurrent.duration._
class UdpConnectedIntegrationSpec extends AkkaSpec(""" class UdpConnectedIntegrationSpec extends AkkaSpec("""
akka.loglevel = DEBUG akka.loglevel = DEBUG
akka.actor.debug.lifecycle = on
akka.actor.debug.autoreceive = on
akka.io.udp-connected.trace-logging = on
# issues with dns resolution of non existent host hanging with the
# Java native host resolution
akka.io.dns.resolver = async-dns
akka.loggers = ["akka.testkit.SilenceAllTestEventListener"] akka.loggers = ["akka.testkit.SilenceAllTestEventListener"]
""") with ImplicitSender with WithLogCapturing { """) with ImplicitSender with WithLogCapturing {
@ -45,7 +54,7 @@ class UdpConnectedIntegrationSpec extends AkkaSpec("""
val handler = TestProbe() val handler = TestProbe()
val command = UdpConnected.Connect(handler.ref, InetSocketAddress.createUnresolved(serverAddress, 1234), None) val command = UdpConnected.Connect(handler.ref, InetSocketAddress.createUnresolved(serverAddress, 1234), None)
commander.send(IO(UdpConnected), command) commander.send(IO(UdpConnected), command)
commander.expectMsg(6.seconds, UdpConnected.CommandFailed(command)) commander.expectMsg(10.seconds, UdpConnected.CommandFailed(command))
} }
"report error if can not resolve (cached)" in { "report error if can not resolve (cached)" in {

View file

@ -7,6 +7,8 @@ package akka.io
import java.net.{ ConnectException, InetSocketAddress } import java.net.{ ConnectException, InetSocketAddress }
import java.nio.channels.{ SelectionKey, SocketChannel } import java.nio.channels.{ SelectionKey, SocketChannel }
import akka.actor.Status.Failure
import scala.util.control.{ NoStackTrace, NonFatal } import scala.util.control.{ NoStackTrace, NonFatal }
import scala.concurrent.duration._ import scala.concurrent.duration._
import akka.actor.{ ActorRef, ReceiveTimeout } import akka.actor.{ ActorRef, ReceiveTimeout }
@ -83,6 +85,11 @@ private[io] class TcpOutgoingConnection(
} }
case ReceiveTimeout => case ReceiveTimeout =>
connectionTimeout() connectionTimeout()
case Failure(ex) =>
// async-dns responds with a Failure on DNS server lookup failure
reportConnectFailure {
throw new RuntimeException(ex)
}
} }
def register(address: InetSocketAddress, registration: ChannelRegistration): Unit = { def register(address: InetSocketAddress, registration: ChannelRegistration): Unit = {

View file

@ -9,6 +9,8 @@ import java.nio.ByteBuffer
import java.nio.channels.DatagramChannel import java.nio.channels.DatagramChannel
import java.nio.channels.SelectionKey._ import java.nio.channels.SelectionKey._
import akka.actor.Status.Failure
import scala.annotation.tailrec import scala.annotation.tailrec
import scala.util.control.NonFatal import scala.util.control.NonFatal
import akka.actor.{ Actor, ActorLogging, ActorRef } import akka.actor.{ Actor, ActorLogging, ActorRef }
@ -50,7 +52,9 @@ private[io] class UdpConnection(
context.become(resolving()) context.become(resolving())
} }
} else { } else {
doConnect(remoteAddress) reportConnectFailure {
doConnect(remoteAddress)
}
} }
def resolving(): Receive = { def resolving(): Receive = {
@ -58,18 +62,22 @@ private[io] class UdpConnection(
reportConnectFailure { reportConnectFailure {
doConnect(new InetSocketAddress(r.address(), remoteAddress.getPort)) doConnect(new InetSocketAddress(r.address(), remoteAddress.getPort))
} }
case Failure(ex) =>
// async-dns responds with a Failure on DNS server lookup failure
reportConnectFailure {
throw new RuntimeException(ex)
}
} }
def doConnect(@unused address: InetSocketAddress): Unit = { def doConnect(@unused address: InetSocketAddress): Unit = {
reportConnectFailure { channel = DatagramChannel.open
channel = DatagramChannel.open channel.configureBlocking(false)
channel.configureBlocking(false) val socket = channel.socket
val socket = channel.socket options.foreach(_.beforeDatagramBind(socket))
options.foreach(_.beforeDatagramBind(socket)) localAddress.foreach(socket.bind)
localAddress.foreach(socket.bind) channel.connect(remoteAddress)
channel.connect(remoteAddress) channelRegistry.register(channel, OP_READ)
channelRegistry.register(channel, OP_READ)
}
log.debug("Successfully connected to [{}]", remoteAddress) log.debug("Successfully connected to [{}]", remoteAddress)
} }

View file

@ -47,6 +47,10 @@ object DnsProtocol {
*/ */
def srvRequestType(): RequestType = Srv def srvRequestType(): RequestType = Srv
/**
* Sending this to the [[AsyncDnsManager]] will either lead to a [[Resolved]] or a [[akka.actor.Status.Failure]] response.
* If request type are both, both resolutions must succeed or the response is a failure.
*/
final case class Resolve(name: String, requestType: RequestType) extends ConsistentHashable { final case class Resolve(name: String, requestType: RequestType) extends ConsistentHashable {
override def consistentHashKey: Any = name override def consistentHashKey: Any = name
} }