Use the pre-succeeded void future when possible for async pipeline operations (#1766)
Motivation: We added synchronous pipeline operations to allow the caller to save allocations when they know they are already on the correct event loop. However, we missed a trick! In some cases the caller cannot guarantee they are on the correct event loop and must use an asynchronous method instead. If that method returns a void future and is called on the event loop, then we can perform the operation synchronously and return a cached void future. Modifications: - Add API to `EventLoop` for creating a 'completed' future with a `Result` (similar to `EventLoopPromise.completeWith`) - Add an equivalent for making completed void futures - Use these when asynchronously adding handlers and the caller is already on the right event loop. Result: - Fewer allocations on the happiest of happy paths when adding handlers asynchronously to a pipeline.
This commit is contained in:
parent
9b230f89a4
commit
178636d999
|
@ -170,16 +170,17 @@ public final class ChannelPipeline: ChannelInvoker {
|
|||
public func addHandler(_ handler: ChannelHandler,
|
||||
name: String? = nil,
|
||||
position: ChannelPipeline.Position = .last) -> EventLoopFuture<Void> {
|
||||
let promise = self.eventLoop.makePromise(of: Void.self)
|
||||
let future: EventLoopFuture<Void>
|
||||
|
||||
if self.eventLoop.inEventLoop {
|
||||
promise.completeWith(self.addHandlerSync(handler, name: name, position: position))
|
||||
future = self.eventLoop.makeCompletedFuture(self.addHandlerSync(handler, name: name, position: position))
|
||||
} else {
|
||||
self.eventLoop.execute {
|
||||
promise.completeWith(self.addHandlerSync(handler, name: name, position: position))
|
||||
future = self.eventLoop.submit {
|
||||
try self.addHandlerSync(handler, name: name, position: position).get()
|
||||
}
|
||||
}
|
||||
|
||||
return promise.futureResult
|
||||
return future
|
||||
}
|
||||
|
||||
/// Synchronously add a `ChannelHandler` to the `ChannelPipeline`.
|
||||
|
@ -934,17 +935,17 @@ extension ChannelPipeline {
|
|||
/// - returns: A future that will be completed when all of the supplied `ChannelHandler`s were added.
|
||||
public func addHandlers(_ handlers: [ChannelHandler],
|
||||
position: ChannelPipeline.Position = .last) -> EventLoopFuture<Void> {
|
||||
let promise = self.eventLoop.makePromise(of: Void.self)
|
||||
let future: EventLoopFuture<Void>
|
||||
|
||||
if self.eventLoop.inEventLoop {
|
||||
promise.completeWith(self.addHandlersSync(handlers, position: position))
|
||||
future = self.eventLoop.makeCompletedFuture(self.addHandlersSync(handlers, position: position))
|
||||
} else {
|
||||
self.eventLoop.execute {
|
||||
promise.completeWith(self.addHandlersSync(handlers, position: position))
|
||||
future = self.eventLoop.submit {
|
||||
try self.addHandlersSync(handlers, position: position).get()
|
||||
}
|
||||
}
|
||||
|
||||
return promise.futureResult
|
||||
return future
|
||||
}
|
||||
|
||||
/// Adds the provided channel handlers to the pipeline in the order given, taking account
|
||||
|
|
|
@ -608,6 +608,21 @@ extension EventLoop {
|
|||
}
|
||||
}
|
||||
|
||||
/// Creates and returns a new `EventLoopFuture` that is marked as succeeded or failed with the value held by `result`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - result: The value that is used by the `EventLoopFuture`
|
||||
/// - Returns: A completed `EventLoopFuture`.
|
||||
@inlinable
|
||||
public func makeCompletedFuture<Success>(_ result: Result<Success, Error>) -> EventLoopFuture<Success> {
|
||||
switch result {
|
||||
case .success(let value):
|
||||
return self.makeSucceededFuture(value)
|
||||
case .failure(let error):
|
||||
return self.makeFailedFuture(error)
|
||||
}
|
||||
}
|
||||
|
||||
/// An `EventLoop` forms a singular `EventLoopGroup`, returning itself as the 'next' `EventLoop`.
|
||||
///
|
||||
/// - returns: Itself, because an `EventLoop` forms a singular `EventLoopGroup`.
|
||||
|
|
|
@ -82,6 +82,8 @@ extension EventLoopTest {
|
|||
("testEventLoopsWithPreSucceededFuturesCacheThem", testEventLoopsWithPreSucceededFuturesCacheThem),
|
||||
("testEventLoopsWithoutPreSucceededFuturesDoNotCacheThem", testEventLoopsWithoutPreSucceededFuturesDoNotCacheThem),
|
||||
("testSelectableEventLoopHasPreSucceededFuturesOnlyOnTheEventLoop", testSelectableEventLoopHasPreSucceededFuturesOnlyOnTheEventLoop),
|
||||
("testMakeCompletedFuture", testMakeCompletedFuture),
|
||||
("testMakeCompletedVoidFuture", testMakeCompletedVoidFuture),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1366,6 +1366,34 @@ public final class EventLoopTest : XCTestCase {
|
|||
XCTAssert(futureInside1 === futureInside2)
|
||||
}.wait())
|
||||
}
|
||||
|
||||
func testMakeCompletedFuture() {
|
||||
let eventLoop = EmbeddedEventLoop()
|
||||
defer {
|
||||
XCTAssertNoThrow(try eventLoop.syncShutdownGracefully())
|
||||
}
|
||||
|
||||
XCTAssertEqual(try eventLoop.makeCompletedFuture(.success("foo")).wait(), "foo")
|
||||
|
||||
struct DummyError: Error {}
|
||||
let future = eventLoop.makeCompletedFuture(Result<String, Error>.failure(DummyError()))
|
||||
XCTAssertThrowsError(try future.wait()) { error in
|
||||
XCTAssertTrue(error is DummyError)
|
||||
}
|
||||
}
|
||||
|
||||
func testMakeCompletedVoidFuture() {
|
||||
let eventLoop = EventLoopWithPreSucceededFuture()
|
||||
defer {
|
||||
XCTAssertNoThrow(try eventLoop.syncShutdownGracefully())
|
||||
}
|
||||
|
||||
let future1 = eventLoop.makeCompletedFuture(.success(()))
|
||||
let future2 = eventLoop.makeSucceededVoidFuture()
|
||||
let future3 = eventLoop.makeSucceededFuture(())
|
||||
XCTAssert(future1 === future2)
|
||||
XCTAssert(future2 === future3)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate class EventLoopWithPreSucceededFuture: EventLoop {
|
||||
|
|
|
@ -26,11 +26,11 @@ services:
|
|||
- MAX_ALLOCS_ALLOWED_1000_getHandlers_sync=50
|
||||
- MAX_ALLOCS_ALLOWED_1000_reqs_1_conn=30540
|
||||
- MAX_ALLOCS_ALLOWED_1000_tcpbootstraps=4100
|
||||
- MAX_ALLOCS_ALLOWED_1000_tcpconnections=180010
|
||||
- MAX_ALLOCS_ALLOWED_1000_tcpconnections=178010
|
||||
- MAX_ALLOCS_ALLOWED_1000_udp_reqs=16050
|
||||
- MAX_ALLOCS_ALLOWED_1000_udpbootstraps=2000
|
||||
- MAX_ALLOCS_ALLOWED_1000_udpconnections=102050
|
||||
- MAX_ALLOCS_ALLOWED_1_reqs_1000_conn=476050
|
||||
- MAX_ALLOCS_ALLOWED_1_reqs_1000_conn=471050
|
||||
- MAX_ALLOCS_ALLOWED_bytebuffer_lots_of_rw=2100
|
||||
- MAX_ALLOCS_ALLOWED_creating_10000_headers=100 # 5.2 improvement 10000
|
||||
- MAX_ALLOCS_ALLOWED_decode_1000_ws_frames=2000
|
||||
|
@ -46,7 +46,7 @@ services:
|
|||
- MAX_ALLOCS_ALLOWED_schedule_10000_tasks=90050
|
||||
- MAX_ALLOCS_ALLOWED_scheduling_10000_executions=20150
|
||||
- MAX_ALLOCS_ALLOWED_udp_1000_reqs_1_conn=16250
|
||||
- MAX_ALLOCS_ALLOWED_udp_1_reqs_1000_conn=202050
|
||||
- MAX_ALLOCS_ALLOWED_udp_1_reqs_1000_conn=200050
|
||||
- SANITIZER_ARG=--sanitize=thread
|
||||
- INTEGRATION_TESTS_ARG=-f tests_0[013-9]
|
||||
|
||||
|
|
|
@ -26,11 +26,11 @@ services:
|
|||
- MAX_ALLOCS_ALLOWED_1000_getHandlers_sync=50
|
||||
- MAX_ALLOCS_ALLOWED_1000_reqs_1_conn=30540
|
||||
- MAX_ALLOCS_ALLOWED_1000_tcpbootstraps=4100
|
||||
- MAX_ALLOCS_ALLOWED_1000_tcpconnections=179010
|
||||
- MAX_ALLOCS_ALLOWED_1000_tcpconnections=177010
|
||||
- MAX_ALLOCS_ALLOWED_1000_udp_reqs=16050
|
||||
- MAX_ALLOCS_ALLOWED_1000_udpbootstraps=2000
|
||||
- MAX_ALLOCS_ALLOWED_1000_udpconnections=101050
|
||||
- MAX_ALLOCS_ALLOWED_1_reqs_1000_conn=471050
|
||||
- MAX_ALLOCS_ALLOWED_1_reqs_1000_conn=466050
|
||||
- MAX_ALLOCS_ALLOWED_bytebuffer_lots_of_rw=2100
|
||||
- MAX_ALLOCS_ALLOWED_creating_10000_headers=100
|
||||
- MAX_ALLOCS_ALLOWED_decode_1000_ws_frames=2000
|
||||
|
@ -46,7 +46,7 @@ services:
|
|||
- MAX_ALLOCS_ALLOWED_schedule_10000_tasks=90050
|
||||
- MAX_ALLOCS_ALLOWED_scheduling_10000_executions=20150
|
||||
- MAX_ALLOCS_ALLOWED_udp_1000_reqs_1_conn=16250
|
||||
- MAX_ALLOCS_ALLOWED_udp_1_reqs_1000_conn=202050
|
||||
- MAX_ALLOCS_ALLOWED_udp_1_reqs_1000_conn=200050
|
||||
- SANITIZER_ARG=--sanitize=thread
|
||||
|
||||
performance-test:
|
||||
|
|
|
@ -26,11 +26,11 @@ services:
|
|||
- MAX_ALLOCS_ALLOWED_1000_getHandlers_sync=50
|
||||
- MAX_ALLOCS_ALLOWED_1000_reqs_1_conn=31990
|
||||
- MAX_ALLOCS_ALLOWED_1000_tcpbootstraps=3100
|
||||
- MAX_ALLOCS_ALLOWED_1000_tcpconnections=188050
|
||||
- MAX_ALLOCS_ALLOWED_1000_tcpconnections=186050
|
||||
- MAX_ALLOCS_ALLOWED_1000_udp_reqs=18050
|
||||
- MAX_ALLOCS_ALLOWED_1000_udpbootstraps=2000
|
||||
- MAX_ALLOCS_ALLOWED_1000_udpconnections=107050
|
||||
- MAX_ALLOCS_ALLOWED_1_reqs_1000_conn=948050
|
||||
- MAX_ALLOCS_ALLOWED_1_reqs_1000_conn=943050
|
||||
- MAX_ALLOCS_ALLOWED_bytebuffer_lots_of_rw=2100
|
||||
- MAX_ALLOCS_ALLOWED_creating_10000_headers=10100
|
||||
- MAX_ALLOCS_ALLOWED_decode_1000_ws_frames=2000
|
||||
|
@ -46,7 +46,7 @@ services:
|
|||
- MAX_ALLOCS_ALLOWED_schedule_10000_tasks=90050
|
||||
- MAX_ALLOCS_ALLOWED_scheduling_10000_executions=20150
|
||||
- MAX_ALLOCS_ALLOWED_udp_1000_reqs_1_conn=18250
|
||||
- MAX_ALLOCS_ALLOWED_udp_1_reqs_1000_conn=213050
|
||||
- MAX_ALLOCS_ALLOWED_udp_1_reqs_1000_conn=211050
|
||||
|
||||
performance-test:
|
||||
image: swift-nio:18.04-5.0
|
||||
|
|
|
@ -26,11 +26,11 @@ services:
|
|||
- MAX_ALLOCS_ALLOWED_1000_getHandlers_sync=50
|
||||
- MAX_ALLOCS_ALLOWED_1000_reqs_1_conn=30540
|
||||
- MAX_ALLOCS_ALLOWED_1000_tcpbootstraps=3100
|
||||
- MAX_ALLOCS_ALLOWED_1000_tcpconnections=180050
|
||||
- MAX_ALLOCS_ALLOWED_1000_tcpconnections=178050
|
||||
- MAX_ALLOCS_ALLOWED_1000_udp_reqs=16050
|
||||
- MAX_ALLOCS_ALLOWED_1000_udpbootstraps=2000
|
||||
- MAX_ALLOCS_ALLOWED_1000_udpconnections=102050
|
||||
- MAX_ALLOCS_ALLOWED_1_reqs_1000_conn=473050
|
||||
- MAX_ALLOCS_ALLOWED_1_reqs_1000_conn=468050
|
||||
- MAX_ALLOCS_ALLOWED_bytebuffer_lots_of_rw=2100
|
||||
- MAX_ALLOCS_ALLOWED_creating_10000_headers=10100
|
||||
- MAX_ALLOCS_ALLOWED_decode_1000_ws_frames=2000
|
||||
|
@ -46,7 +46,7 @@ services:
|
|||
- MAX_ALLOCS_ALLOWED_schedule_10000_tasks=90050
|
||||
- MAX_ALLOCS_ALLOWED_scheduling_10000_executions=20150
|
||||
- MAX_ALLOCS_ALLOWED_udp_1000_reqs_1_conn=16250
|
||||
- MAX_ALLOCS_ALLOWED_udp_1_reqs_1000_conn=202050
|
||||
- MAX_ALLOCS_ALLOWED_udp_1_reqs_1000_conn=200050
|
||||
|
||||
performance-test:
|
||||
image: swift-nio:18.04-5.1
|
||||
|
|
|
@ -26,11 +26,11 @@ services:
|
|||
- MAX_ALLOCS_ALLOWED_1000_getHandlers_sync=50
|
||||
- MAX_ALLOCS_ALLOWED_1000_reqs_1_conn=30540
|
||||
- MAX_ALLOCS_ALLOWED_1000_tcpbootstraps=4100
|
||||
- MAX_ALLOCS_ALLOWED_1000_tcpconnections=181050 # regression from 5.3 which was 179010
|
||||
- MAX_ALLOCS_ALLOWED_1000_tcpconnections=179050 # regression from 5.3 which was 177010
|
||||
- MAX_ALLOCS_ALLOWED_1000_udpbootstraps=2000
|
||||
- MAX_ALLOCS_ALLOWED_1000_udpconnections=102050 # regression from 5.3 which was 101050
|
||||
- MAX_ALLOCS_ALLOWED_1000_udp_reqs=16050
|
||||
- MAX_ALLOCS_ALLOWED_1_reqs_1000_conn=473050 # regression from 5.3 which was 471050
|
||||
- MAX_ALLOCS_ALLOWED_1_reqs_1000_conn=468050 # regression from 5.3 which was 466050
|
||||
- MAX_ALLOCS_ALLOWED_bytebuffer_lots_of_rw=2100
|
||||
- MAX_ALLOCS_ALLOWED_creating_10000_headers=100
|
||||
- MAX_ALLOCS_ALLOWED_decode_1000_ws_frames=2000
|
||||
|
@ -46,7 +46,7 @@ services:
|
|||
- MAX_ALLOCS_ALLOWED_schedule_10000_tasks=90050
|
||||
- MAX_ALLOCS_ALLOWED_scheduling_10000_executions=20150
|
||||
- MAX_ALLOCS_ALLOWED_udp_1000_reqs_1_conn=16250
|
||||
- MAX_ALLOCS_ALLOWED_udp_1_reqs_1000_conn=204050 # regression from 5.3 which was 202050
|
||||
- MAX_ALLOCS_ALLOWED_udp_1_reqs_1000_conn=202050 # regression from 5.3 which was 200050
|
||||
# - SANITIZER_ARG=--sanitize=thread # TSan broken still
|
||||
|
||||
performance-test:
|
||||
|
|
Loading…
Reference in New Issue