
Interesting. I have indeed seen enough cases of "Blocked indefinitely on
MVar" to suggest they might be difficult to handle correctly!
On Wed, Dec 19, 2018, 23:02 Станислав Черничкин Recently I had an interesting discussion on MVars with cats-effect library
designers. Cats-effect brings MVar synchronization primitive along with
other IO stuff to the Scala programming language. I tried to persuade them
to include some Control.Concurrent.MVar’s functions to the library but has
failed completely. Moreover, now I think that MVar is a poor choice for
basic synchronization primitive. Your may find discussion here
https://github.com/typelevel/cats-effect/issues/451 and event try to
advocate, tl;dr. Anyway, what is so wrong with MVar? 1. It’s complex. Each MVar has 2 state transitions, each may block. 2. It does not play well in presence of asynchronous exceptions.
More specifically, `take` and `put` operations should be balanced (each
`take` must be followed by `put`) this force programmer to mask
asynchronous exceptions during the MVar acquisition and since `take`
function may block, this will delay task cancelation. Well, you may argue
what the `takeMVar` function is actually interruptible, but I’m going to
show an easier approach which renders interpretability magic unnecessary. What could be the sensible alternative? Guy from the cats-effect suggested
me IVar + atomic reference (IORef). This pattern separates concern of
blocking (synchronization) from the atomic mutation. So everything can be
represented as atomic reference with IVar inside. Just look at this
beautiful mutex implementation
https://github.com/ovotech/fs2-kafka/blob/master/src/main/scala/fs2/kafka/in...
(By ”beautiful” I mean approach itself of course, but not the Scala’s
syntax. Scala is one of most ugliest girls after C++ I was forced to date
with by my employer for money. Thankfully he didn’t force me to do the same
things with her grandma Java). For people who don’t read Scala, the approach is fairly simple. Each
thread which want to touch mutex, will create IVar, atomically swap it in
the IORef masked (note, that IORef’s operations non-blocking), unmask and
wait for previous become available IVar *unmasked*. Then it will either
perform it’s operations or fail due to the interruption or exception and
trigger newly installed IVar anyway. It just works. Without any
«interruptible» magic. So, which benefits can we get? 1. Simpler implementation of basic primitives. Obliviously IORef is
fairly simple. IVar is also mind be simpler than MVar, and possibly faster
(just “possibly”, I don’t know how it’s implemented, but I guess lesser
state transitions implies less logic). 2. Simplified deadlock analysis. Really, we have only IVar with
only one state transition and only one potentially blocking operation. 3. Magicless support of interruptions. We don’t need to separate
mask/uninterruptibleMask anymore, because all updates are non-blocking, and
all waits are unmasked.
_______________________________________________
Haskell-Cafe mailing list
To (un)subscribe, modify options or view archives go to:
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Only members subscribed via the mailman list are allowed to post.