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 . [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?