[GHC] #4154: Deadlock in Chan module

GHC ghc-devs at haskell.org
Tue Apr 11 09:55:01 UTC 2017


#4154: Deadlock in Chan module
-------------------------------------+-------------------------------------
        Reporter:  NeilMitchell      |                Owner:  (none)
            Type:  bug               |               Status:  closed
        Priority:  high              |            Milestone:  8.4.1
       Component:  libraries/base    |              Version:  6.12.3
      Resolution:  fixed             |             Keywords:
Operating System:  Unknown/Multiple  |         Architecture:
 Type of failure:  Incorrect result  |  Unknown/Multiple
  at runtime                         |            Test Case:
      Blocked By:                    |             Blocking:
 Related Tickets:                    |  Differential Rev(s):
       Wiki Page:                    |
-------------------------------------+-------------------------------------

Comment (by osa1):

 > Is it possible to define instead of isEmptyChan some non-blocking
 version of
 > readChan with tryTakeMVar and tryPutMVar?

 I think this can't work. A `readChan` is actually two `takeMVar`s.
 Implementation of `tryReadChan` has to use `tryTakeMVar`s, otherwise you
 can't
 avoid being blocked in some cases.

 So these two MVars that need to be taken to be able to read something off
 a
 chan need to be taken with `tryReadChan`.

 First `tryTakeMVar` would only succeed if the queue is empty. The trouble
 is in
 the case where the chan has enough stuff but currently some other thread
 is
 busy actually reading the contents (and updating the read-end) you'd get a
 `Nothing`, even though if you actually do `readChan` you'd get blocked for
 a
 very short amount of time because chan has enough contents. So this
 implementation would return `Nothing` in some cases when there is enough
 contents in the chan.


 Now suppose that you use `tryReadMVar` to read the read-end. Suppose that
 right
 after you read the read-end another thread does `readChan`, and takes the
 read-end MVar. Now there's a race condition between your thread and the
 other
 thread. The loser needs to re-take the read-end. Furthermore, if your
 thread
 was the only thread you can't update the read-end, because you didn't take
 it
 (remember that in this scenario we use `tryReadMVar` instead of
 `tryTakeMVar`).

 So neither `tryTakeMVar` nor `tryReadMVar` gives you a race-free
 implementation
 of `tryReadChan`. I hope this makes sense.

 (Another problem with both implementations is that you have no guarantees
 that
 you'll be able to read the contents. For example if chan has enough
 contents
 but a million threads are running `readChan` in parallel you may not be
 able to
 read anything with `tryReadChan`)

 `MVar`-based implementation is cute but has this limitation. (it also
 doesn't
 scale as number of writers and readers increase)

--
Ticket URL: <http://ghc.haskell.org/trac/ghc/ticket/4154#comment:12>
GHC <http://www.haskell.org/ghc/>
The Glasgow Haskell Compiler


More information about the ghc-tickets mailing list