Support custom executors in Swift concurrency

Thank you for putting this together. This will be a major game-changer for systems that do non-blocking, evented I/O directly with the OS interfaces (such as kqueue/epoll/io_uring). Without custom executors, the I/O eventing threads (which may block in kevent/epoll_wait/io_uring_enter) cannot be the "default executor" threads (because of course you don't want to block them). Therefore without custom executors such systems would have necessarily incurred a pervasive amount of thread switching to get from the I/O threads onto the executors for async/await and back.

As a real-world example, SwiftNIO does its own evented I/O handling (mostly using kqueue/epoll at the moment) so needless to say I'm super happy and very supportive about this proposal because it will mean a major speed improvement when using async/await with SwiftNIO :slight_smile:. [later addition through EDIT]: I wrote this up in a little more detail.


@John_McCall I've got one question regarding the immediate execution of asynchronous work in an executor: Let's imagine we're in a (custom) executor and we want to call user code in the form of a let userCode: () async -> Void. One way we can of course make this work is by using

// self is a serial, custom executor
self.run(userCode)

which would then soon call the executor's func enqueue(_ job: UnownedJobRef) with the userCode as an UnownedJobRef. The executor would then probably enqueue that in its own task queue and soon after run

userCodeAsUnownedRefFromTaskQueue.execute(self.asUnownedRef())

This works but will always go through the task queue. But what if the custom executor knows that it can run a task straight away (without going through the queue), how could we (without using enqueue go from userCode: () async -> Void to an UnownedJobRef that we can then execute immediately?

5 Likes