I observed with printing out the thread name and using linux perf_events and a flamegraph.
The following example:
public func test() async -> Response {
print("Async: \(Thread.current.name!)")
return·Response(buf: nil) }
// ChannelInboundHandler.channelRead
print("Channel handler: \(Thread.current.name!)")
let promise = eventLoop.makePromise(of: Response.self)
promise.completeWithTask {
return await test()
}
Prints the following:
Channel handler: NIO-ELT-0-#5
Channel handler: NIO-ELT-0-#0
Channel handler: NIO-ELT-0-#2
Async: NIO-ELT-0-#2
Async: NIO-ELT-0-#2
Async: NIO-ELT-0-#2
Async: NIO-ELT-0-#2
Async: NIO-ELT-0-#2
Channel handler: NIO-ELT-0-#2
Channel handler: NIO-ELT-0-#6
Async: NIO-ELT-0-#0
Channel handler: NIO-ELT-0-#7
Channel handler: NIO-ELT-0-#1
Channel handler: NIO-ELT-0-#2
Async: NIO-ELT-0-#2
Async: NIO-ELT-0-#2
Async: NIO-ELT-0-#2
Channel handler: NIO-ELT-0-#7
Channel handler: NIO-ELT-0-#2
Channel handler: NIO-ELT-0-#7
Async: NIO-ELT-0-#2
Async: NIO-ELT-0-#2
Async: NIO-ELT-0-#2
Async: NIO-ELT-0-#2
Edit:
And flamegraph:
There are some minimal _dispatch_dispose calls on other threads, but one thread takes the main load.
Thinking about it. I have an async function which has no await.
I updated the example to have an await call in my test() function.
public func getResponse() async -> Response {
let t = Task.detached {
print("Async2: \(Thread.current.name!)")
try! await Task.sleep(for: .seconds(0.1))
let response = Response(buf: nil)
response.status = .ok
return response
}
return await t.value
}
public func test() async -> Response {
print("Async: \(Thread.current.name!)")
return await getResponse()
}
Channel handler: NIO-ELT-0-#1
Async2: NIO-ELT-0-#4
Async: MyApp
Async2: NIO-ELT-0-#4
Async2: MyApp
Async2: NIO-ELT-0-#4
Async: MyApp
Async: MyApp
Async2: NIO-ELT-0-#4
Async2: NIO-ELT-0-#4
Channel handler: NIO-ELT-0-#0
Channel handler: NIO-ELT-0-#7
Async2: MyApp
Async2: NIO-ELT-0-#4
Async: NIO-ELT-0-#4
Async2: NIO-ELT-0-#4
Async2: NIO-ELT-0-#4
Async2: NIO-ELT-0-#4
Async2: MyApp
Async2: NIO-ELT-0-#4
Async2: NIO-ELT-0-#4
Async2: MyApp
Async2: NIO-ELT-0-#4
Async2: NIO-ELT-0-#4
Only with Task.detach i see one additional thread (NIO #4). As before, there are some sporadic calls on other threads, but they are so few. Some threads (#4 and MyApp) take the main load again.
I use a benchmark script to test the server, so the load is high and i would expect it to distribute across threads because of the Task.sleep. But instead the MyApp thread is just getting slower.
I have the feeling i'm missing something fundamentally.
Edit: Found that async functions are called on other threads, but one or two threads take the main load. Still way slower than just using NIOs EventLoopFuture