IORingSwift has been around for a couple of years now ;) We're using it in our embedded audio product for UART, SPI and I2C as well as socket and file I/O.
Public API provides structured concurrency wrappers around common operations such as reading and writing. Multishot APIs, such as accept(2)
, which can return multiple completions over time return an AnyAsyncSequence
. Internally, wrappers allocate a concrete instance of Submission<T>
, representing an initialized Submission Queue Entry (SQE), which is then submitted to the io_uring
. Completion handlers are handled by having libdispatch
monitor an eventfd(2)
representing available completions. The user_data
in each queue entry is a block, which executes the onCompletion(cqe:)
method of the Submission<T>
instance in the ring's isolated context.
let clients: AnyAsyncSequence<Socket> = try await socket.accept()
for try await client in clients {
Task {
repeat {
let data = try await client.receive(count: bufferSize)
try await client.send(data)
} while true
}
}
Plenty of other examples here. Original discussion here.
Things I would do differently? The use of classes does cause a lot of allocations (although this hasn't been a bottleneck for us). And, SE-0461 will make it easier to avoid the global actor.