diff --git a/src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt b/src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt index 58979a4589c..a717c1e2fbc 100644 --- a/src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt +++ b/src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt @@ -3294,10 +3294,13 @@ namespace Akka.IO } public class Resolved : Akka.IO.Dns.Command { + public Resolved(string name, System.Exception ex) { } public Resolved(string name, System.Collections.Generic.IEnumerable ipv4, System.Collections.Generic.IEnumerable ipv6) { } public System.Net.IPAddress Addr { get; } + public System.Exception Exception { get; } public System.Collections.Generic.IEnumerable Ipv4 { get; } public System.Collections.Generic.IEnumerable Ipv6 { get; } + public bool IsSuccess { get; } public string Name { get; } public static Akka.IO.Dns.Resolved Create(string name, System.Collections.Generic.IEnumerable addresses) { } } diff --git a/src/core/Akka.Tests/IO/TcpIntegrationSpec.cs b/src/core/Akka.Tests/IO/TcpIntegrationSpec.cs index 0b56cb5c43a..acaa67a06ba 100644 --- a/src/core/Akka.Tests/IO/TcpIntegrationSpec.cs +++ b/src/core/Akka.Tests/IO/TcpIntegrationSpec.cs @@ -22,6 +22,8 @@ using Xunit.Abstractions; using FluentAssertions; using System.Runtime.InteropServices; +using System.Threading; +using Akka.Event; namespace Akka.Tests.IO { @@ -470,6 +472,18 @@ public void The_TCP_transport_implementation_dont_report_Connected_when_endpoint replies.Count.ShouldBe(0); } + [Fact] + public void Should_report_Error_only_once_when_connecting_to_unreachable_DnsEndpoint() + { + var probe = CreateTestProbe(); + var endpoint = new DnsEndPoint("fake", 1000); + Sys.Tcp().Tell(new Tcp.Connect(endpoint), probe.Ref); + + // expecting CommandFailed or no reply (within timeout) + var replies = probe.ReceiveWhile(TimeSpan.FromSeconds(5), x => x as Tcp.CommandFailed); + replies.Count.ShouldBe(1); + } + [Fact] public void The_TCP_transport_implementation_handle_tcp_connection_actor_death_properly() { diff --git a/src/core/Akka/IO/Dns.cs b/src/core/Akka/IO/Dns.cs index b510f51331f..7d31041321b 100644 --- a/src/core/Akka/IO/Dns.cs +++ b/src/core/Akka/IO/Dns.cs @@ -7,9 +7,11 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Net; using System.Net.Sockets; +using System.Runtime.ExceptionServices; using Akka.Actor; using Akka.Configuration; using Akka.Routing; @@ -94,6 +96,11 @@ public class Resolved : Command { private readonly IPAddress _addr; + public Resolved(string name, Exception ex) : this(name, null, null) + { + Exception = ex; + } + /// /// TBD /// @@ -103,24 +110,28 @@ public class Resolved : Command public Resolved(string name, IEnumerable ipv4, IEnumerable ipv6) { Name = name; - Ipv4 = ipv4; - Ipv6 = ipv6; + Ipv4 = ipv4?.ToImmutableList() ?? ImmutableList.Empty; + Ipv6 = ipv6?.ToImmutableList() ?? ImmutableList.Empty; - _addr = ipv4.FirstOrDefault() ?? ipv6.FirstOrDefault(); + _addr = Ipv4.FirstOrDefault() ?? Ipv6.FirstOrDefault(); } + public bool IsSuccess => Exception == null; + + public Exception Exception { get; } + /// /// TBD /// - public string Name { get; private set; } + public string Name { get; } /// /// TBD /// - public IEnumerable Ipv4 { get; private set; } + public IEnumerable Ipv4 { get; } /// /// TBD /// - public IEnumerable Ipv6 { get; private set; } + public IEnumerable Ipv6 { get; } /// /// TBD @@ -129,8 +140,11 @@ public IPAddress Addr { get { - //TODO: Throw better exception - if (_addr == null) throw new Exception("Unknown host"); + if(Exception != null) + ExceptionDispatchInfo.Capture(Exception).Throw(); + else + if (_addr == null) throw new Exception("Unknown host"); + return _addr; } } diff --git a/src/core/Akka/IO/InetAddressDnsResolver.cs b/src/core/Akka/IO/InetAddressDnsResolver.cs index 91d4ed68b4e..b57aec6b5ad 100644 --- a/src/core/Akka/IO/InetAddressDnsResolver.cs +++ b/src/core/Akka/IO/InetAddressDnsResolver.cs @@ -8,6 +8,8 @@ using System.Linq; using System.Net; using System.Net.Sockets; +using System.Runtime.ExceptionServices; +using System.Threading.Tasks; using Akka.Actor; using Akka.Configuration; @@ -43,28 +45,33 @@ public InetAddressDnsResolver(SimpleDnsCache cache, Config config) /// TBD protected override bool Receive(object message) { - var resolve = message as Dns.Resolve; - if (resolve != null) + if (message is Dns.Resolve resolve) { + var replyTo = Sender; var answer = _cache.Cached(resolve.Name); - if (answer == null) + if (answer != null) { - try - { - //TODO: IP6 - answer = Dns.Resolved.Create(resolve.Name, System.Net.Dns.GetHostEntryAsync(resolve.Name).Result.AddressList.Where(x => - x.AddressFamily == AddressFamily.InterNetwork - || _useIpv6 && x.AddressFamily == AddressFamily.InterNetworkV6)); - _cache.Put(answer, _positiveTtl); - } - catch (SocketException ex) + replyTo.Tell(answer); + return true; + } + + System.Net.Dns.GetHostEntryAsync(resolve.Name).ContinueWith(t => + { + if (t.IsFaulted) { - if (ex.SocketErrorCode != SocketError.HostNotFound) throw; - answer = new Dns.Resolved(resolve.Name, Enumerable.Empty(), Enumerable.Empty()); - _cache.Put(answer, _negativeTtl); + var flattened = t.Exception.Flatten().InnerExceptions; + return flattened.Count == 1 + ? new Dns.Resolved(resolve.Name, flattened[0]) + : new Dns.Resolved(resolve.Name, t.Exception); } - } - Sender.Tell(answer); + + answer = Dns.Resolved.Create(resolve.Name, t.Result.AddressList.Where(x => + x.AddressFamily == AddressFamily.InterNetwork + || _useIpv6 && x.AddressFamily == AddressFamily.InterNetworkV6)); + _cache.Put(answer, _positiveTtl); + return answer; + + }, TaskContinuationOptions.ExecuteSynchronously).PipeTo(replyTo); return true; } return false;