Re: Can we make QMP commands in Rust always be coroutine safe?
Paolo Bonzini <[email protected]> Tue, 5 May 2026 12:51:00 +0200
| Newsgroups | org.nongnu.qemu-rust,org.nongnu.qemu-devel |
|---|---|
| Message-ID | <[email protected]> |
On 5/5/26 10:44, Markus Armbruster wrote: > Coroutine safety can be hard to prove, similar to thread safety. Common > pitfalls are: > > - The BQL isn't held across ``qemu_coroutine_yield()``, so > operations that used to assume that they execute atomically may have > to be more careful to protect against changes in the global state. > > - Nested event loops (``AIO_WAIT_WHILE()`` etc.) are problematic in > coroutine context and can easily lead to deadlocks. They should be > replaced by yielding and reentering the coroutine when the condition > becomes false. > > Since the command handler may assume coroutine context, any callers > other than the QMP dispatcher must also call it in coroutine context. > In particular, HMP commands calling such a QMP command handler must be > marked ``.coroutine = true`` in hmp-commands.hx. > > It is an error to specify both ``'coroutine': true`` and ``'allow-oob': true`` > for a command. We don't currently have a use case for both together and > without a use case, it's not entirely clear what the semantics should > be. > > Can we make commands written in Rust always coroutine safe? We won't *ever* have mixed coroutine/non-coroutine functions in Rust. Kevin's prototype used async functions (stackless coroutines) for Rust yielding functions, rather than qemu_coroutine_yield()[1]. Within sucg fubctuibs, yielding is much more explicit than in C (you have to write ".await" explicitly at all levels of calling a yielding function). But we have the BQL, and "coroutine: true" commands if I understand correctly are run outside it (they run in iothread context). So any command that uses BQL-protected data cannot be coroutine safe, and that means it's likely that Rust would also have coroutine: true/false. However, there are safeguards: 1) it will be impossible to write yielding code in a "coroutine: false" Rust command; it won't secretly start a nested event loop. 2) releasing the BQL while keeping a reference to a BqlRefCell is an instant panic; This leaves nested event loops in "coroutine: true" commands as a potential pitfall. Paolo [1] suspension and resumption is represented respectively by futures (a discriminated union of a suspension state and a result after completion) and wakers. To convert suspended async fns to qemu_coroutine_yield() calls, Kevin wrapped the async fns with a loop that continues until the future as a result, yielding across calls to the async fn; and to resume the suspended async fn, he wrote a waker that invokes qemu_coroutine_resume().