-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Description
Currently there is a race condition when using multithreaded coroutines. Consider the following scenario:
- Thread 1 does
epoll_wait/kevent/GetQueuedCompletionStatus, blocking until some work can be done. The thread wakes up, and it is about to executeresume coroutine_handle. - Thread 2 is executing the coroutine, which reaches a suspend point, and then reads its atomic state and learns that it is scheduled to be canceled (destroyed). It runs the defers and errdefers, which dutifully remove the coroutine from the epoll set. However because of (1) it isn't even in the epoll set/kqueue/iocp. (1) is about to
resumethe coroutine and there's nothing (2) can do to stop it. (2) proceeds to destroy the coroutine frame. The memory is gone. - Thread 1 does
resume coroutine_handlewhich now points to invalid memory. Boom.
In order to fix this, we need to introduce new syntax and semantics. Current semantics are:
asynccreates apromisewhich must be consumed withcancelorawait.suspendmust be consumed with aresume.
The problem here described above - resume and cancel racing with each other. When a suspended coroutine is canceled, the memory must not be destroyed until the suspend is canceled or resumed. Proposal for new semantics:
asynccreates apromisewhich must be consumed withcancelasyncorawait.suspendmust be consumed with acancelsuspendorresume.
With these semantics, each coroutine essentially has 0, 1, or 2 references, and when the reference count reaches 0 it is destroyed. There is the "async reference", which is the main one, and the "suspend reference", which might never exist.
Therefore it is crucial that when a coroutine uses suspend - which is a low level feature intended mainly for low level library implementations of concurrency primitives - that it ensures it will be consumed with either resume or cancelsuspend.
defer and errdefer will both run when a coroutine's cancellation process begins. This means that the first cancelasync or cancelsuspend will run the defers of the coroutine. When the other reference drops, the memory will be destroyed.