[Haskell-cafe] MVar considered harmful

Станислав Черничкин schernichkin at gmail.com
Wed Dec 19 21:02:07 UTC 2018


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/internal/Synchronized.scala
(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.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.haskell.org/pipermail/haskell-cafe/attachments/20181220/033dc762/attachment.html>


More information about the Haskell-Cafe mailing list