Skip to content

Commit 4b7854d

Browse files
authored
Strict concurrency for NIOPerformanceTester and NIOCrashTester (#3167)
1 parent 202f987 commit 4b7854d

12 files changed

+154
-97
lines changed

Package.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,8 @@ let package = Package(
424424
"NIOHTTP1",
425425
"NIOFoundationCompat",
426426
"NIOWebSocket",
427-
]
427+
],
428+
swiftSettings: strictConcurrencySettings
428429
),
429430
.executableTarget(
430431
name: "NIOCrashTester",
@@ -435,7 +436,8 @@ let package = Package(
435436
"NIOHTTP1",
436437
"NIOWebSocket",
437438
"NIOFoundationCompat",
438-
]
439+
],
440+
swiftSettings: strictConcurrencySettings
439441
),
440442
.testTarget(
441443
name: "NIOCoreTests",

Sources/NIOCrashTester/CrashTestSuites.swift

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@
1313
//===----------------------------------------------------------------------===//
1414

1515
#if !canImport(Darwin) || os(macOS)
16-
let crashTestSuites: [String: Any] = [
17-
"EventLoopCrashTests": EventLoopCrashTests(),
18-
"ByteBufferCrashTests": ByteBufferCrashTests(),
19-
"SystemCrashTests": SystemCrashTests(),
20-
"HTTPCrashTests": HTTPCrashTests(),
21-
"StrictCrashTests": StrictCrashTests(),
22-
"LoopBoundTests": LoopBoundTests(),
23-
]
16+
func makeCrashTestSuites() -> [String: Any] {
17+
[
18+
"EventLoopCrashTests": EventLoopCrashTests(),
19+
"ByteBufferCrashTests": ByteBufferCrashTests(),
20+
"SystemCrashTests": SystemCrashTests(),
21+
"HTTPCrashTests": HTTPCrashTests(),
22+
"StrictCrashTests": StrictCrashTests(),
23+
"LoopBoundTests": LoopBoundTests(),
24+
]
25+
}
2426
#endif

Sources/NIOCrashTester/CrashTests+EventLoop.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,9 @@ struct EventLoopCrashTests {
8282
exit(2)
8383
}
8484
func f() {
85-
el.scheduleTask(in: .nanoseconds(0)) { [f] in
85+
el.assumeIsolated().scheduleTask(in: .nanoseconds(0)) { [f] in
8686
f()
87-
}.futureResult.whenFailure { [f] error in
87+
}.futureResult.assumeIsolated().whenFailure { [f] error in
8888
guard case .some(.shutdown) = error as? EventLoopError else {
8989
exit(3)
9090
}

Sources/NIOCrashTester/main.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@ func main() throws {
7676
}
7777

7878
func allTestsForSuite(_ testSuite: String) -> [(String, CrashTest)] {
79-
crashTestSuites[testSuite].map { testSuiteObject in
79+
let crashTestSuites = makeCrashTestSuites()
80+
return crashTestSuites[testSuite].map { testSuiteObject in
8081
Mirror(reflecting: testSuiteObject)
8182
.children
8283
.filter { $0.label?.starts(with: "test") ?? false }
@@ -199,6 +200,7 @@ func main() throws {
199200

200201
switch CommandLine.arguments.dropFirst().first {
201202
case .some("run-all"):
203+
let crashTestSuites = makeCrashTestSuites()
202204
for testSuite in crashTestSuites {
203205
for test in allTestsForSuite(testSuite.key) {
204206
try runAndEval(test.0, suite: testSuite.key)

Sources/NIOPerformanceTester/ChannelPipelineBenchmark.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ import NIOCore
1515
import NIOEmbedded
1616

1717
final class ChannelPipelineBenchmark: Benchmark {
18-
private final class NoOpHandler: ChannelInboundHandler, RemovableChannelHandler {
18+
private final class NoOpHandler: ChannelInboundHandler, RemovableChannelHandler, Sendable {
1919
typealias InboundIn = Any
2020
}
21-
private final class ConsumingHandler: ChannelInboundHandler, RemovableChannelHandler {
21+
private final class ConsumingHandler: ChannelInboundHandler, RemovableChannelHandler, Sendable {
2222
typealias InboundIn = Any
2323

2424
func channelReadComplete(context: ChannelHandlerContext) {
@@ -28,7 +28,7 @@ final class ChannelPipelineBenchmark: Benchmark {
2828
private let channel: EmbeddedChannel
2929
private let runCount: Int
3030
private let extraHandlers = 4
31-
private var handlers: [RemovableChannelHandler] = []
31+
private var handlers: [RemovableChannelHandler & Sendable] = []
3232

3333
init(runCount: Int) {
3434
self.channel = EmbeddedChannel()

Sources/NIOPerformanceTester/ExecuteBenchmark.swift

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ final class ExecuteBenchmark: Benchmark {
2020
private var group: MultiThreadedEventLoopGroup!
2121
private var loop: EventLoop!
2222
private var dg: DispatchGroup!
23-
private var counter = 0
2423
private let numTasks: Int
2524

2625
init(numTasks: Int) {
@@ -34,10 +33,10 @@ final class ExecuteBenchmark: Benchmark {
3433

3534
// We are preheating the EL to avoid growing the `ScheduledTask` `PriorityQueue`
3635
// during the actual test
37-
try! self.loop.submit {
36+
try! self.loop.submit { [loop, numTasks] in
3837
var counter: Int = 0
39-
for _ in 0..<self.numTasks {
40-
self.loop.scheduleTask(in: .nanoseconds(0)) {
38+
for _ in 0..<numTasks {
39+
loop!.assumeIsolatedUnsafeUnchecked().scheduleTask(in: .nanoseconds(0)) {
4140
counter &+= 1
4241
}
4342
}
@@ -47,19 +46,21 @@ final class ExecuteBenchmark: Benchmark {
4746
func tearDown() {}
4847

4948
func run() -> Int {
50-
try! self.loop.submit {
51-
for _ in 0..<self.numTasks {
52-
self.dg.enter()
49+
let counter = try! self.loop.submit { [dg, loop, numTasks] in
50+
var counter = 0
51+
for _ in 0..<numTasks {
52+
dg!.enter()
5353

54-
self.loop.execute {
55-
self.counter &+= 1
56-
self.dg.leave()
54+
loop!.assumeIsolatedUnsafeUnchecked().execute {
55+
counter &+= 1
56+
dg!.leave()
5757
}
5858
}
59+
return counter
5960
}.wait()
6061
self.dg.wait()
6162

62-
return self.counter
63+
return counter
6364
}
6465

6566
}

Sources/NIOPerformanceTester/LockBenchmark.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ import NIOConcurrencyHelpers
1717
import NIOCore
1818
import NIOPosix
1919

20-
final class NIOLockBenchmark: Benchmark {
20+
final class NIOLockBenchmark: Benchmark, @unchecked Sendable {
21+
// mutable state is protected by the lock
22+
2123
private let numberOfThreads: Int
2224
private let lockOperationsPerThread: Int
2325
private let threadPool: NIOThreadPool

Sources/NIOPerformanceTester/SchedulingAndRunningBenchmark.swift

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ final class SchedulingAndRunningBenchmark: Benchmark {
2020
private var group: MultiThreadedEventLoopGroup!
2121
private var loop: EventLoop!
2222
private var dg: DispatchGroup!
23-
private var counter = 0
2423
private let numTasks: Int
2524

2625
init(numTasks: Int) {
@@ -34,10 +33,10 @@ final class SchedulingAndRunningBenchmark: Benchmark {
3433

3534
// We are preheating the EL to avoid growing the `ScheduledTask` `PriorityQueue`
3635
// during the actual test
37-
try! self.loop.submit {
36+
try! self.loop.submit { [loop, numTasks] in
3837
var counter: Int = 0
39-
for _ in 0..<self.numTasks {
40-
self.loop.scheduleTask(in: .nanoseconds(0)) {
38+
for _ in 0..<numTasks {
39+
loop!.assumeIsolatedUnsafeUnchecked().scheduleTask(in: .nanoseconds(0)) {
4140
counter &+= 1
4241
}
4342
}
@@ -47,15 +46,17 @@ final class SchedulingAndRunningBenchmark: Benchmark {
4746
func tearDown() {}
4847

4948
func run() -> Int {
50-
try! self.loop.submit {
51-
for _ in 0..<self.numTasks {
52-
self.dg.enter()
49+
let counter = try! self.loop.submit { [dg, loop, numTasks] in
50+
var counter: Int = 0
51+
for _ in 0..<numTasks {
52+
dg!.enter()
5353

54-
self.loop.scheduleTask(in: .nanoseconds(0)) {
55-
self.counter &+= 1
56-
self.dg.leave()
54+
loop!.assumeIsolatedUnsafeUnchecked().scheduleTask(in: .nanoseconds(0)) {
55+
counter &+= 1
56+
dg!.leave()
5757
}
5858
}
59+
return counter
5960
}.wait()
6061
self.dg.wait()
6162

Sources/NIOPerformanceTester/TCPThroughputBenchmark.swift

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ final class TCPThroughputBenchmark: Benchmark {
2828

2929
private var group: EventLoopGroup!
3030
private var serverChannel: Channel!
31-
private var serverHandler: ServerHandler!
31+
private var serverHandler: NIOLoopBound<ServerHandler>!
3232
private var clientChannel: Channel!
3333

3434
private var message: ByteBuffer!
@@ -39,10 +39,12 @@ final class TCPThroughputBenchmark: Benchmark {
3939
public typealias OutboundOut = ByteBuffer
4040

4141
private let connectionEstablishedPromise: EventLoopPromise<EventLoop>
42+
private let eventLoop: EventLoop
4243
private var context: ChannelHandlerContext!
4344

44-
init(_ connectionEstablishedPromise: EventLoopPromise<EventLoop>) {
45+
init(_ connectionEstablishedPromise: EventLoopPromise<EventLoop>, eventLoop: EventLoop) {
4546
self.connectionEstablishedPromise = connectionEstablishedPromise
47+
self.eventLoop = eventLoop
4648
}
4749

4850
public func channelActive(context: ChannelHandlerContext) {
@@ -114,14 +116,20 @@ final class TCPThroughputBenchmark: Benchmark {
114116

115117
let connectionEstablishedPromise: EventLoopPromise<EventLoop> = self.group.next().makePromise()
116118

119+
let promise = self.group.next().makePromise(of: NIOLoopBound<ServerHandler>.self)
117120
self.serverChannel = try ServerBootstrap(group: self.group)
118121
.childChannelInitializer { channel in
119-
self.serverHandler = ServerHandler(connectionEstablishedPromise)
120-
return channel.pipeline.addHandler(self.serverHandler)
122+
channel.eventLoop.makeCompletedFuture {
123+
let serverHandler = ServerHandler(connectionEstablishedPromise, eventLoop: channel.eventLoop)
124+
promise.succeed(NIOLoopBound(serverHandler, eventLoop: channel.eventLoop))
125+
try channel.pipeline.syncOperations.addHandler(serverHandler)
126+
}
121127
}
122128
.bind(host: "127.0.0.1", port: 0)
123129
.wait()
124130

131+
self.serverHandler = try promise.futureResult.wait()
132+
125133
self.clientChannel = try ClientBootstrap(group: group)
126134
.channelInitializer { channel in
127135
channel.eventLoop.makeCompletedFuture {
@@ -165,7 +173,7 @@ final class TCPThroughputBenchmark: Benchmark {
165173
let messages = self.messages
166174

167175
self.serverEventLoop.execute {
168-
serverHandler.send(message, times: messages)
176+
serverHandler.value.send(message, times: messages)
169177
}
170178
try isDonePromise.futureResult.wait()
171179
return 0

Sources/NIOPerformanceTester/UDPBenchmark.swift

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ final class UDPBenchmark {
2828
private var group: EventLoopGroup!
2929
private var server: Channel!
3030
private var client: Channel!
31-
private var clientHandler: EchoHandlerClient!
31+
private var clientHandler: EchoHandlerClient.SendableView!
3232

3333
init(data: ByteBuffer, numberOfRequests: Int, vectorReads: Int, vectorWrites: Int) {
3434
self.data = data
@@ -57,22 +57,25 @@ extension UDPBenchmark: Benchmark {
5757
self.client = try DatagramBootstrap(group: group)
5858
// zero is the same as not applying the option.
5959
.channelOption(.datagramVectorReadMessageCount, value: self.vectorReads)
60-
.channelInitializer { channel in
61-
let handler = EchoHandlerClient(
62-
eventLoop: channel.eventLoop,
63-
config: .init(
64-
remoteAddress: remoteAddress,
65-
request: self.data,
66-
requests: self.numberOfRequests,
67-
writesPerFlush: self.vectorWrites
60+
.channelInitializer { [data, numberOfRequests, vectorWrites] channel in
61+
channel.eventLoop.makeCompletedFuture {
62+
let handler = EchoHandlerClient(
63+
eventLoop: channel.eventLoop,
64+
config: .init(
65+
remoteAddress: remoteAddress,
66+
request: data,
67+
requests: numberOfRequests,
68+
writesPerFlush: vectorWrites
69+
)
6870
)
69-
)
70-
return channel.pipeline.addHandler(handler)
71+
try channel.pipeline.syncOperations.addHandler(handler)
72+
}
7173
}
7274
.bind(to: address)
7375
.wait()
7476

75-
self.clientHandler = try self.client.pipeline.handler(type: EchoHandlerClient.self).wait()
77+
self.clientHandler = try self.client.pipeline.handler(type: EchoHandlerClient.self).map { $0.sendableView }
78+
.wait()
7679
}
7780

7881
func tearDown() {
@@ -87,7 +90,7 @@ extension UDPBenchmark: Benchmark {
8790
}
8891

8992
extension UDPBenchmark {
90-
final class EchoHandler: ChannelInboundHandler {
93+
final class EchoHandler: ChannelInboundHandler, Sendable {
9194
typealias InboundIn = AddressedEnvelope<ByteBuffer>
9295
typealias OutboundOut = AddressedEnvelope<ByteBuffer>
9396

@@ -232,12 +235,26 @@ extension UDPBenchmark {
232235
self.context = nil
233236
}
234237

235-
func run() -> EventLoopFuture<Void> {
236-
let p = self.eventLoop.makePromise(of: Void.self)
237-
self.eventLoop.execute {
238-
self._run(promise: p)
238+
var sendableView: SendableView {
239+
SendableView(handler: self, eventLoop: self.eventLoop)
240+
}
241+
242+
struct SendableView: Sendable {
243+
private let handler: NIOLoopBound<EchoHandlerClient>
244+
private let eventLoop: EventLoop
245+
246+
init(handler: EchoHandlerClient, eventLoop: EventLoop) {
247+
self.handler = NIOLoopBound(handler, eventLoop: eventLoop)
248+
self.eventLoop = eventLoop
249+
}
250+
251+
func run() -> EventLoopFuture<Void> {
252+
let p = self.eventLoop.makePromise(of: Void.self)
253+
self.eventLoop.execute {
254+
self.handler.value._run(promise: p)
255+
}
256+
return p.futureResult
239257
}
240-
return p.futureResult
241258
}
242259

243260
private func _run(promise: EventLoopPromise<Void>) {

Sources/NIOPerformanceTester/WebSocketFrameEncoderBenchmark.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,9 @@ extension WebSocketFrameEncoderBenchmark {
6767
extension WebSocketFrameEncoderBenchmark: Benchmark {
6868
func setUp() throws {
6969
// We want the pipeline walk to have some cost.
70-
try! self.channel.pipeline.addHandler(WriteConsumingHandler()).wait()
70+
try! self.channel.pipeline.syncOperations.addHandler(WriteConsumingHandler())
7171
for _ in 0..<3 {
72-
try! self.channel.pipeline.addHandler(NoOpOutboundHandler()).wait()
72+
try! self.channel.pipeline.syncOperations.addHandler(NoOpOutboundHandler())
7373
}
7474
try! self.channel.pipeline.syncOperations.addHandler(WebSocketFrameEncoder())
7575
self.frame = WebSocketFrame(opcode: .binary, maskKey: self.maskingKey, data: self.data, extensionData: nil)

0 commit comments

Comments
 (0)