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 akka.testkit.{ AkkaSpec, ImplicitSender, TestProbe }
import akka.util.ByteString
import akka.actor.ActorRef
import akka.testkit.SocketUtil.temporaryServerAddresses
import akka.testkit.WithLogCapturing
import akka.testkit.AkkaSpec
import akka.testkit.ImplicitSender
import akka.testkit.TestProbe
import akka.util.ByteString
import scala.concurrent.duration._
class UdpConnectedIntegrationSpec extends AkkaSpec("""
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"]
""") with ImplicitSender with WithLogCapturing {
@ -45,7 +54,7 @@ class UdpConnectedIntegrationSpec extends AkkaSpec("""
val handler = TestProbe()
val command = UdpConnected.Connect(handler.ref, InetSocketAddress.createUnresolved(serverAddress, 1234), None)
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 {

View file

@ -7,6 +7,8 @@ package akka.io
import java.net.{ ConnectException, InetSocketAddress }
import java.nio.channels.{ SelectionKey, SocketChannel }
import akka.actor.Status.Failure
import scala.util.control.{ NoStackTrace, NonFatal }
import scala.concurrent.duration._
import akka.actor.{ ActorRef, ReceiveTimeout }
@ -83,6 +85,11 @@ private[io] class TcpOutgoingConnection(
}
case ReceiveTimeout =>
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 = {

View file

@ -9,6 +9,8 @@ import java.nio.ByteBuffer
import java.nio.channels.DatagramChannel
import java.nio.channels.SelectionKey._
import akka.actor.Status.Failure
import scala.annotation.tailrec
import scala.util.control.NonFatal
import akka.actor.{ Actor, ActorLogging, ActorRef }
@ -50,7 +52,9 @@ private[io] class UdpConnection(
context.become(resolving())
}
} else {
doConnect(remoteAddress)
reportConnectFailure {
doConnect(remoteAddress)
}
}
def resolving(): Receive = {
@ -58,18 +62,22 @@ private[io] class UdpConnection(
reportConnectFailure {
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 = {
reportConnectFailure {
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)
}
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)
log.debug("Successfully connected to [{}]", remoteAddress)
}

View file

@ -47,6 +47,10 @@ object DnsProtocol {
*/
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 {
override def consistentHashKey: Any = name
}