Skip to content

Commit 27c839f

Browse files
authored
GetaddrinfoResolver succeeds futures on eventLoop (#3042)
### Motivation: `testClientBindWorksOnSocketsBoundToEitherIPv4OrIPv6Only` would fail sometimes leaking the IPv4 promise in `GetaddrinfoResolver` `HappyEyeballsConnector` returns the connection when it resolves either IPv4 of IPv6. It uses the `GetaddrinfoResolver` which holds a promise for each of the IPv4 and IPv6 resolution; when one is completed the connection will be returned and it is possible to start tearing down the test and shutting down the event loop before the other is completed and we leak the promise. ### Modifications: Complete both futures on the event loop rather than the dispatch queue. ### Result: The futures are completed in the same event loop tick meaning that we cannot continue execution and leak one.
1 parent 4292153 commit 27c839f

8 files changed

+23
-14
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"mallocCountTotal" : 555
2+
"mallocCountTotal" : 107
33
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"mallocCountTotal" : 556
2+
"mallocCountTotal" : 109
33
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"mallocCountTotal" : 548
2+
"mallocCountTotal" : 107
33
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"mallocCountTotal" : 548
2+
"mallocCountTotal" : 107
33
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"mallocCountTotal" : 548
2+
"mallocCountTotal" : 107
33
}

Sources/NIOPosix/Bootstrap.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -956,8 +956,6 @@ public final class ClientBootstrap: NIOClientTCPBootstrapProtocol {
956956
///
957957
/// Using `bind` is not necessary unless you need the local address to be bound to a specific address.
958958
///
959-
/// - Note: Using `bind` will disable Happy Eyeballs on this `Channel`.
960-
///
961959
/// - Parameters:
962960
/// - address: The `SocketAddress` to bind on.
963961
public func bind(to address: SocketAddress) -> ClientBootstrap {
@@ -978,6 +976,8 @@ public final class ClientBootstrap: NIOClientTCPBootstrapProtocol {
978976

979977
/// Specify the `host` and `port` to connect to for the TCP `Channel` that will be established.
980978
///
979+
/// - Note: Makes use of Happy Eyeballs.
980+
///
981981
/// - Parameters:
982982
/// - host: The host to connect to.
983983
/// - port: The port to connect to.

Sources/NIOPosix/GetaddrinfoResolver.swift

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import struct WinSDK.SOCKADDR_IN6
5151
let offloadQueueTSV = ThreadSpecificVariable<DispatchQueue>()
5252

5353
internal class GetaddrinfoResolver: Resolver {
54+
private let loop: EventLoop
5455
private let v4Future: EventLoopPromise<[SocketAddress]>
5556
private let v6Future: EventLoopPromise<[SocketAddress]>
5657
private let aiSocktype: NIOBSDSocket.SocketType
@@ -67,6 +68,7 @@ internal class GetaddrinfoResolver: Resolver {
6768
aiSocktype: NIOBSDSocket.SocketType,
6869
aiProtocol: NIOBSDSocket.OptionLevel
6970
) {
71+
self.loop = loop
7072
self.v4Future = loop.makePromise()
7173
self.v6Future = loop.makePromise()
7274
self.aiSocktype = aiSocktype
@@ -90,7 +92,6 @@ internal class GetaddrinfoResolver: Resolver {
9092
/// Initiate a DNS AAAA query for a given host.
9193
///
9294
/// Due to the nature of `getaddrinfo`, we only actually call the function once, in this function.
93-
/// That means this function call actually blocks: sorry!
9495
///
9596
/// - Parameters:
9697
/// - host: The hostname to do an AAAA lookup on.
@@ -214,16 +215,24 @@ internal class GetaddrinfoResolver: Resolver {
214215
info = nextInfo
215216
}
216217

217-
v6Future.succeed(v6Results)
218-
v4Future.succeed(v4Results)
218+
// Ensure that both futures are succeeded in the same tick
219+
// to avoid racing and potentially leaking a promise
220+
self.loop.execute {
221+
self.v6Future.succeed(v6Results)
222+
self.v4Future.succeed(v4Results)
223+
}
219224
}
220225

221226
/// Record an error and fail the lookup process.
222227
///
223228
/// - Parameters:
224229
/// - error: The error encountered during lookup.
225230
private func fail(_ error: Error) {
226-
self.v6Future.fail(error)
227-
self.v4Future.fail(error)
231+
// Ensure that both futures are succeeded in the same tick
232+
// to avoid racing and potentially leaking a promise
233+
self.loop.execute {
234+
self.v6Future.fail(error)
235+
self.v4Future.fail(error)
236+
}
228237
}
229238
}

Tests/NIOPosixTests/BootstrapTest.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -734,7 +734,7 @@ class BootstrapTest: XCTestCase {
734734
// Some platforms don't define "localhost" for IPv6, so check that
735735
// and use "ip6-localhost" instead.
736736
if !isIPv4 {
737-
let hostResolver = GetaddrinfoResolver(loop: group.next(), aiSocktype: .stream, aiProtocol: .tcp)
737+
let hostResolver = GetaddrinfoResolver(loop: self.group.next(), aiSocktype: .stream, aiProtocol: .tcp)
738738
let hostv6 = try! hostResolver.initiateAAAAQuery(host: "localhost", port: 8088).wait()
739739
if hostv6.isEmpty {
740740
localhost = "ip6-localhost"
@@ -752,7 +752,7 @@ class BootstrapTest: XCTestCase {
752752
XCTFail("can't connect channel 1")
753753
return
754754
}
755-
XCTAssertEqual(localIP, maybeChannel1?.localAddress?.ipAddress)
755+
XCTAssertEqual(localIP, myChannel1Address.ipAddress)
756756
// Try 3: Bind the client to the same address/port as in try 2 but to server 2.
757757
XCTAssertNoThrow(
758758
try ClientBootstrap(group: self.group)

0 commit comments

Comments
 (0)