Well, his example code demonstrating the "bug" is definitely broken. But not because of any flaw in coroutines, rather in his understanding of them.
session = sessions.get_session(cookie);
if(!session)
session = sessions.create_session(user_id);
Concurrent access to a global sessions object is obviously unsafe, even in a coroutine environment where "You know [the above methods] don’t yield to the scheduler." I'm not sure that anyone advocating for coroutines over async APIs is arguing that coroutines somehow magically absolve the programmer from considering race conditions. Only that coroutines, used idiomatically, remove classes of bugs _like this one_. And that snippet is not idiomatic.One "coroutine way" of handling concurrent access to shared data is by piping requests to it through a synchronization point. For example, in pseudo-Go, it may look like
// public, synchronous API method
func (s *Sessions) GetSession(cookie string) Session {
// return s.dataStructure[cookie] // bad, obviously
responseChannel := make(chan Session)
s.requests <- getSessionRequest{cookie, responseChannel}
return <-responseChannel
}
func (s *Sessions) loop() {
for {
select {
. . .
case req := <-s.requests:
req.responseChannel <- s.dataStructure[req.cookie]
. . .
}
}
}
(Another way is explicit locking, of course.)