Strict concurrency for NIOExtrasPerformanceTester (#263)

This commit is contained in:
George Barnett 2025-04-02 13:26:48 +01:00 committed by GitHub
parent 1a16877a1b
commit cf3de22478
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 72 additions and 45 deletions

View File

@ -91,7 +91,8 @@ var targets: [PackageDescription.Target] = [
.product(name: "NIOPosix", package: "swift-nio"),
.product(name: "NIOEmbedded", package: "swift-nio"),
.product(name: "NIOHTTP1", package: "swift-nio"),
]
],
swiftSettings: strictConcurrencySettings
),
.target(
name: "NIOSOCKS",

View File

@ -13,56 +13,69 @@
//===----------------------------------------------------------------------===//
import Foundation
import NIOConcurrencyHelpers
import NIOCore
import NIOExtras
class HTTP1ThreadedPCapPerformanceTest: HTTP1ThreadedPerformanceTest {
private class SinkHolder {
var fileSink: NIOWritePCAPHandler.SynchronizedFileSink!
private final class SinkHolder: Sendable {
let fileSink: NIOLoopBound<NIOWritePCAPHandler.SynchronizedFileSink>
let eventLoop: any EventLoop
init(eventLoop: any EventLoop) {
self.eventLoop = eventLoop
func setUp() throws {
let outputFile = NSTemporaryDirectory() + "/" + UUID().uuidString
self.fileSink = try NIOWritePCAPHandler.SynchronizedFileSink.fileSinkWritingToFile(path: outputFile) {
let fileSink = try! NIOWritePCAPHandler.SynchronizedFileSink.fileSinkWritingToFile(path: outputFile) {
error in
print("ERROR: \(error)")
exit(1)
}
self.fileSink = NIOLoopBound(fileSink, eventLoop: eventLoop)
}
func tearDown() {
try! self.fileSink.syncClose()
func tearDown() -> EventLoopFuture<Void> {
self.eventLoop.submit {
try self.fileSink.value.syncClose()
}
}
}
init() {
let sinkHolder = SinkHolder()
func addPCap(channel: Channel) -> EventLoopFuture<Void> {
channel.eventLoop.submit {
let pcapHandler = NIOWritePCAPHandler(
mode: .client,
fileSink: sinkHolder.fileSink.write
)
return try channel.pipeline.syncOperations.addHandler(pcapHandler, position: .first)
}
}
self.sinkHolder = sinkHolder
let sinkHolders = NIOLockedValueBox<[SinkHolder]>([])
self.sinkHolders = sinkHolders
super.init(
numberOfRepeats: 50,
numberOfClients: System.coreCount,
requestsPerClient: 500,
extraInitialiser: { channel in addPCap(channel: channel) }
extraInitialiser: { channel in
channel.eventLoop.makeCompletedFuture {
let sinkHolder = SinkHolder(eventLoop: channel.eventLoop)
sinkHolders.withLockedValue { $0.append(sinkHolder) }
let pcapHandler = NIOWritePCAPHandler(
mode: .client,
fileSink: sinkHolder.fileSink.value.write(buffer:)
)
return try channel.pipeline.syncOperations.addHandler(pcapHandler, position: .first)
}
}
)
}
private let sinkHolder: SinkHolder
private let sinkHolders: NIOLockedValueBox<[SinkHolder]>
override func run() throws -> Int {
// Opening and closing the file included here as flushing data to disk is not known to complete until closed.
try sinkHolder.setUp()
defer {
sinkHolder.tearDown()
let result = Result {
try super.run()
}
return try super.run()
let holders = self.sinkHolders.withLockedValue { $0 }
for holder in holders {
try holder.tearDown().wait()
}
return try result.get()
}
}

View File

@ -12,6 +12,7 @@
//
//===----------------------------------------------------------------------===//
import NIOConcurrencyHelpers
import NIOCore
import NIOHTTP1
import NIOPosix
@ -107,7 +108,9 @@ final class RepeatedRequests: ChannelInboundHandler {
let reqPart = self.unwrapInboundIn(data)
if case .end(nil) = reqPart {
if self.remainingNumberOfRequests <= 0 {
context.channel.close().map { self.doneRequests }.cascade(to: self.isDonePromise)
context.channel.close().assumeIsolated().map { self.doneRequests }.nonisolated().cascade(
to: self.isDonePromise
)
} else {
self.doneRequests += 1
self.remainingNumberOfRequests -= 1
@ -124,7 +127,7 @@ class HTTP1ThreadedPerformanceTest: Benchmark {
let numberOfRepeats: Int
let numberOfClients: Int
let requestsPerClient: Int
let extraInitialiser: (Channel) -> EventLoopFuture<Void>
let extraInitialiser: @Sendable (Channel) -> EventLoopFuture<Void>
let head: HTTPRequestHead
@ -135,7 +138,7 @@ class HTTP1ThreadedPerformanceTest: Benchmark {
numberOfRepeats: Int,
numberOfClients: Int,
requestsPerClient: Int,
extraInitialiser: @escaping (Channel) -> EventLoopFuture<Void>
extraInitialiser: @escaping @Sendable (Channel) -> EventLoopFuture<Void>
) {
self.numberOfRepeats = numberOfRepeats
self.numberOfClients = numberOfClients
@ -152,8 +155,10 @@ class HTTP1ThreadedPerformanceTest: Benchmark {
self.serverChannel = try ServerBootstrap(group: self.group)
.serverChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
.childChannelInitializer { channel in
channel.pipeline.configureHTTPServerPipeline(withPipeliningAssistance: true).flatMap {
channel.pipeline.addHandler(SimpleHTTPServer())
channel.eventLoop.makeCompletedFuture {
let sync = channel.pipeline.syncOperations
try sync.configureHTTPServerPipeline(withPipeliningAssistance: true)
try sync.addHandler(SimpleHTTPServer())
}
}.bind(host: "127.0.0.1", port: 0).wait()
}
@ -167,23 +172,31 @@ class HTTP1ThreadedPerformanceTest: Benchmark {
var reqs: [Int] = []
reqs.reserveCapacity(self.numberOfRepeats)
for _ in 0..<self.numberOfRepeats {
var requestHandlers: [RepeatedRequests] = []
requestHandlers.reserveCapacity(self.numberOfClients)
let requestsCompletedFutures = NIOLockedValueBox<[EventLoopFuture<Int>]>([])
requestsCompletedFutures.withLockedValue({ $0.reserveCapacity(self.numberOfClients) })
var clientChannels: [Channel] = []
clientChannels.reserveCapacity(self.numberOfClients)
for _ in 0..<self.numberOfClients {
let clientChannel = try! ClientBootstrap(group: self.group)
.channelInitializer { channel in
channel.pipeline.addHTTPClientHandlers().flatMap {
.channelInitializer { [head, requestsPerClient, extraInitialiser] channel in
channel.eventLoop.makeCompletedFuture {
let sync = channel.pipeline.syncOperations
try sync.addHTTPClientHandlers()
let repeatedRequestsHandler = RepeatedRequests(
numberOfRequests: self.requestsPerClient,
numberOfRequests: requestsPerClient,
eventLoop: channel.eventLoop,
head: self.head
head: head
)
requestHandlers.append(repeatedRequestsHandler)
return channel.pipeline.addHandler(repeatedRequestsHandler)
requestsCompletedFutures.withLockedValue {
$0.append(repeatedRequestsHandler.completedFuture)
}
try sync.addHandler(repeatedRequestsHandler)
}.flatMap {
self.extraInitialiser(channel)
extraInitialiser(channel)
}
}
.connect(to: self.serverChannel.localAddress!)
@ -199,13 +212,12 @@ class HTTP1ThreadedPerformanceTest: Benchmark {
let allWrites = EventLoopFuture<Void>.andAllComplete(writeFutures, on: writeFutures.first!.eventLoop)
try! allWrites.wait()
let streamCompletedFutures = requestHandlers.map { rh in rh.completedFuture }
let futures = requestsCompletedFutures.withLockedValue { $0 }
let requestsServed = EventLoopFuture<Int>.reduce(
0,
streamCompletedFutures,
on: streamCompletedFutures.first!.eventLoop,
+
)
futures,
on: futures.first!.eventLoop
) { $0 + $1 }
reqs.append(try! requestsServed.wait())
}
return reqs.reduce(0, +) / self.numberOfRepeats

View File

@ -17,6 +17,7 @@ import NIOExtras
class HTTP1ThreadedRollingPCapPerformanceTest: HTTP1ThreadedPerformanceTest {
init() {
@Sendable
func addRollingPCap(channel: Channel) -> EventLoopFuture<Void> {
channel.eventLoop.submit {
let pcapRingBuffer = NIOPCAPRingBuffer(