4 ms·
Well, his example code demonstrating the "bug" is definitely broken. But not because of any flaw in coroutines, rather in his understanding of them. sessio
by redbad 14y ago
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.)
- StavrosK 14y agoExactly. If you're relying on coroutines not yielding for your synchronization, you're going to get bitten hard. Use per-coroutine data structures and lock when accessing shared structures. I tend to think about coroutines as threads, never relying on their determinism, as that's not something you can count on, and it's not advertised as one of their features.