Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion Sources/NIOPosix/HappyEyeballs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,12 @@ internal final class HappyEyeballsConnector<ChannelBuilderResult> {
// notifications, and can also get late scheduled task callbacks. We want to just quietly
// ignore these, as our transition into the complete state should have already sent
// cleanup messages to all of these things.
case (.complete, .resolverACompleted),
//
// We can also get the resolutionDelayElapsed after allResolved, as it's possible that
// callback was already dequeued in the same tick as the cancellation. That's also fine:
// the resolution delay isn't interesting.
case (.allResolved, .resolutionDelayElapsed),
(.complete, .resolverACompleted),
(.complete, .resolverAAAACompleted),
(.complete, .connectSuccess),
(.complete, .connectFailed),
Expand Down
47 changes: 47 additions & 0 deletions Tests/NIOPosixTests/HappyEyeballsTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1308,4 +1308,51 @@ public final class HappyEyeballsTest: XCTestCase {

XCTAssertNoThrow(try client.close().wait())
}

func testResolutionTimeoutAndResolutionInSameTick() throws {
var channels: [Channel] = []
let (eyeballer, resolver, loop) = buildEyeballer(host: "example.com", port: 80) {
let channelFuture = defaultChannelBuilder(loop: $0, family: $1)
channelFuture.whenSuccess { channel in
try! channel.pipeline.addHandler(ConnectionDelayer(), name: CONNECT_DELAYER, position: .first).wait()
channels.append(channel)
}
return channelFuture
}
let targetFuture = eyeballer.resolveAndConnect().flatMapThrowing { (channel) -> String? in
let target = channel.connectTarget()
_ = try (channel as! EmbeddedChannel).finish()
return target
}
loop.run()

// Then, queue a task to resolve the v6 promise after 50ms.
// Why 50ms? This is the same time as the resolution delay.
let promise = resolver.v6Promise
loop.scheduleTask(in: .milliseconds(50)) {
promise.fail(DummyError())
}

// Kick off the IPv4 resolution. This triggers the timer for the resolution delay.
resolver.v4Promise.succeed(SINGLE_IPv4_RESULT)
loop.run()

// Advance time 50ms.
loop.advanceTime(by: .milliseconds(50))

// Then complete the connection future.
XCTAssertEqual(channels.count, 1)
channels.first!.succeedConnection()

// Should be done.
let target = try targetFuture.wait()
XCTAssertEqual(target!, "10.0.0.1")

// We should have had queries for AAAA and A.
let expectedQueries: [DummyResolver.Event] = [
.aaaa(host: "example.com", port: 80),
.a(host: "example.com", port: 80),
]
XCTAssertEqual(resolver.events, expectedQueries)
}
}
Loading