This is one of those cases where software transactional memory really shines.
You can often take the naive solution and it will be the correct one. Your code will looks like your intent.
TFA's first attempt:
async def drain_requests():
while state != "closing":
await asyncio.sleep(0.1)
print("draining pending requests")
Got it. Let's port it to STM: let drain_requests = do
atomically (
do s <- readTVar state
when (s /= "closing")
retry )
print("draining pending requests")
Thread-safe and no busy-waiting. No mention of 'notify', 'sleep'. No attempt to evade the concurrency issues, as in the articles "The fix: per-consumer queues - Each consumer drains its own queue and checks each transition individually."
In most STM models this is a busy-wait implemented with STM? Only Haskell blocks on `retry`