Skip to content

Commit 20e970c

Browse files
author
Trevör
authored
Merge pull request swift-server#4 from weissi/jw-test-delay-close
test delayed close
2 parents fa21e8b + 6c23cf5 commit 20e970c

File tree

1 file changed

+100
-0
lines changed

1 file changed

+100
-0
lines changed

Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift

+100
Original file line numberDiff line numberDiff line change
@@ -623,4 +623,104 @@ class HTTPClientInternalTests: XCTestCase {
623623
}
624624
XCTAssertNoThrow(try client.syncShutdown())
625625
}
626+
627+
func testRaceBetweenAsynchronousCloseAndChannelUsabilityDetection() {
628+
final class DelayChannelCloseUntilToldHandler: ChannelOutboundHandler {
629+
typealias OutboundIn = Any
630+
631+
enum State {
632+
case idling
633+
case delayedClose
634+
case closeDone
635+
}
636+
637+
var state: State = .idling
638+
let doTheCloseNowFuture: EventLoopFuture<Void>
639+
let sawTheClosePromise: EventLoopPromise<Void>
640+
641+
init(doTheCloseNowFuture: EventLoopFuture<Void>,
642+
sawTheClosePromise: EventLoopPromise<Void>) {
643+
self.doTheCloseNowFuture = doTheCloseNowFuture
644+
self.sawTheClosePromise = sawTheClosePromise
645+
}
646+
647+
func handlerRemoved(context: ChannelHandlerContext) {
648+
XCTAssertEqual(.closeDone, self.state)
649+
}
650+
651+
func close(context: ChannelHandlerContext, mode: CloseMode, promise: EventLoopPromise<Void>?) {
652+
XCTAssertEqual(.idling, self.state)
653+
self.state = .delayedClose
654+
self.sawTheClosePromise.succeed(())
655+
// let's hold the close until the future's complete
656+
self.doTheCloseNowFuture.whenSuccess {
657+
context.close(mode: mode).map {
658+
XCTAssertEqual(.delayedClose, self.state)
659+
self.state = .closeDone
660+
}.cascade(to: promise)
661+
}
662+
}
663+
}
664+
665+
let web = HTTPBin()
666+
defer {
667+
XCTAssertNoThrow(try web.shutdown())
668+
}
669+
670+
let client = HTTPClient(eventLoopGroupProvider: .createNew)
671+
defer {
672+
XCTAssertNoThrow(try client.syncShutdown())
673+
}
674+
675+
let req = try! HTTPClient.Request(url: "http://localhost:\(web.serverChannel.localAddress!.port!)/get",
676+
method: .GET,
677+
body: nil)
678+
679+
// Let's start by getting a connection so we can mess with the Channel :).
680+
var maybeConnection: ConnectionPool.Connection?
681+
XCTAssertNoThrow(try maybeConnection = client.pool.getConnection(for: req,
682+
preference: .indifferent,
683+
on: client.eventLoopGroup.next(),
684+
deadline: nil).wait())
685+
guard let connection = maybeConnection else {
686+
XCTFail("couldn't make connection")
687+
return
688+
}
689+
690+
let channel = connection.channel
691+
let doActualCloseNowPromise = channel.eventLoop.makePromise(of: Void.self)
692+
let sawTheClosePromise = channel.eventLoop.makePromise(of: Void.self)
693+
694+
XCTAssertNoThrow(try channel.pipeline.addHandler(DelayChannelCloseUntilToldHandler(doTheCloseNowFuture: doActualCloseNowPromise.futureResult,
695+
sawTheClosePromise: sawTheClosePromise),
696+
position: .first).wait())
697+
client.pool.release(connection)
698+
699+
XCTAssertNoThrow(try client.execute(request: req).wait())
700+
701+
// Now, let's pretend the timeout happened
702+
channel.pipeline.fireUserInboundEventTriggered(IdleStateHandler.IdleStateEvent.write)
703+
704+
// The Channel's closure should have already been initialised now but still, let's make sure the close
705+
// was initiated
706+
XCTAssertNoThrow(try sawTheClosePromise.futureResult.wait())
707+
// The Channel should still be active though because we delayed the close through our handler above.
708+
XCTAssertTrue(channel.isActive)
709+
710+
// When asking for a connection again, we should _not_ get the same one back because we did most of the close,
711+
// similar to what the SSLHandler would do.
712+
XCTAssertNoThrow(try maybeConnection = client.pool.getConnection(for: req,
713+
preference: .indifferent,
714+
on: client.eventLoopGroup.next(),
715+
deadline: nil).wait())
716+
doActualCloseNowPromise.succeed(())
717+
guard let connection2 = maybeConnection else {
718+
XCTFail("couldn't get second connection")
719+
return
720+
}
721+
722+
XCTAssert(connection !== connection2)
723+
client.pool.release(connection2)
724+
XCTAssertTrue(connection2.channel.isActive)
725+
}
626726
}

0 commit comments

Comments
 (0)