4

TL;DR: I have a haskell project where everything is in a single thread, without any concurrency, but crashes like this:

program1: thread blocked indefinitely in an MVar operation

Longer description:

I am trying to locate the bug when working on https://github.com/carldong/timeless-tutorials/blob/master/src/Tutorial1.hs, which depends on another library, timeless. You would notice that all concurrency code are commented out, and doing a grep on the timeless repo will show that no concurrency code is involved. Then I am totally confused about this crash, and I don't know how to get more detailed information, like stack trace. I tried running with some RTS parameters:

$ stack exec -- Tutorial1 +RTS -p -M4m -xc
*** Exception (reporting due to +RTS -xc): (THUNK_STATIC), stack trace: 
  FRP.Timeless.Internal.Signal.loop.\,
  called from FRP.Timeless.Internal.Signal.loop,
  called from FRP.Timeless.state,
  called from FRP.Timeless.Internal.Signal.first.\,
  called from FRP.Timeless.Internal.Signal.first,
  called from FRP.Timeless.Internal.Signal...\,
  called from FRP.Timeless.Internal.Signal..,
  called from Tutorial1.test0,
  called from FRP.Timeless.Internal.Signal.stepSignal.step,
  called from FRP.Timeless.Internal.Signal.stepSignal,
  called from FRP.Timeless.Run.runBox,
  called from Tutorial1.main
*** Exception (reporting due to +RTS -xc): (THUNK_STATIC), stack trace: 
  FRP.Timeless.Internal.Signal.loop.\,
  called from FRP.Timeless.Internal.Signal.loop,
  called from FRP.Timeless.state,
  called from FRP.Timeless.Internal.Signal.first.\,
  called from FRP.Timeless.Internal.Signal.first,
  called from FRP.Timeless.Internal.Signal...\,
  called from FRP.Timeless.Internal.Signal..,
  called from Tutorial1.test0,
  called from FRP.Timeless.Internal.Signal.stepSignal.step,
  called from FRP.Timeless.Internal.Signal.stepSignal,
  called from FRP.Timeless.Run.runBox,
  called from Tutorial1.main
Tutorial1: thread blocked indefinitely in an MVar operation

And I cannot find what THUNK_STATIC is, because nothing on Google says about it. From recursive update a "Behaviour" in Sodium yields 'thread blocked ...' I might guess that the bug might have some relationship with the magical ArrowLoop which originally comes from Netwire, but I can't really make sense out of it.

I stripped down the entire Timeless and my test case into the smallest sample, which is self-contained and contains the bug. I cannot strip down the Signals any more because these are originally forked from Netwire, and I don't fully understand how ArrowLoop stuff really work under the hood.

Update I made the example even smaller. I also confirmed that this bug is present without the -threaded flag

{-# LANGUAGE Arrows #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE GADTs #-}

import Prelude hiding ((.),id)
import Control.Applicative
import Control.Arrow
import Control.Monad
import Control.Monad.Fix
-- import Data.Monoid
import Control.Category
import Data.Maybe


---------
-- Stripped down Timeless here
---------
data Signal a b where
  SGen ::
    (Maybe a -> IO (Maybe b, Signal a b)) -> Signal a b


instance Category Signal where
    id = SGen (\ma -> return (ma, id))
    s2 . s1 = SGen $ \mx0 -> do
                (mx1, s1') <- stepSignal s1 mx0
                (mx2, s2') <- stepSignal s2 mx1
                mx2 `seq` return (mx2, s2'. s1')

instance Arrow Signal where
    arr f = SGen $ \ma -> case ma of
      Just a -> return (Just (f a), arr f)
      Nothing -> return (Nothing, arr f)

    first s' =
        SGen $ \mxy' ->
            fmap (\(mx, s) -> lstrict (liftA2 (,) mx (fmap snd mxy'), first s))
                  (stepSignal s' (fmap fst mxy'))

instance ArrowLoop Signal where
  loop s =
    SGen $ \mx ->
      fmap (fmap fst ***! loop) .
      mfix $ \ ~(mx',_) ->
        let d | Just (_,d) <- mx' = d
              | otherwise = error "Feedback broken by inhibition"
        in stepSignal s (fmap (,d) mx)

-- | Steps a signal in certain time step
stepSignal ::
              Signal a b
           -- ^ Signal to be stepped
           -> Maybe a
           -- ^ Input
           -- | Stateful output
           -> IO (Maybe b, Signal a b)
stepSignal s Nothing = return (Nothing, s)
stepSignal s (Just x) = x `seq` step s (Just x)
  where
    step (SGen f) = f

-- | Left-strict version of '&&&' for functions.
(&&&!) :: (a -> b) -> (a -> c) -> (a -> (b, c))
(&&&!) f g x' =
    let (x, y) = (f x', g x')
    in x `seq` (x, y)


-- | Left-strict version of '***' for functions.
(***!) :: (a -> c) -> (b -> d) -> ((a, b) -> (c, d))
(***!) f g (x', y') =
    let (x, y) = (f x', g y')
    in x `seq` (x, y)

-- | Left strict tuple
lstrict :: (a,b) -> (a,b)
lstrict (x,y) = x `seq` (x,y)


-- | Make a pure stateful signal from given transition function
mkPure :: (a -> (Maybe b, Signal a b)) -> Signal a b
mkPure f =
  SGen $ \mx ->
  case mx of
    Just x -> return . lstrict $ f x

-- | Make a pure stateful signal from given signal function
mkSF :: (a -> (b, Signal a b)) -> Signal a b
mkSF f = mkPure (lstrict . first Just . f)

-- | Make a pure stateless signal from given signal function
mkSF_ :: (a -> b) -> Signal a b
mkSF_ = arr

delay :: a -> Signal a a
delay x' = mkSF $ \x -> (x', delay x)

-- | Make a stateful wire from chained state transition
-- function. Notice that the output will always be the new value
mkSW_ :: b -> (b -> a -> b) -> Signal a b
mkSW_ b0 f = mkSF $ g b0
    where
      g b0 x = let b1 = f b0 x in
                   lstrict (b1, mkSW_ b1 f)

-- | This command drives a black box of signal network. The driver
-- knows nothing about the internals of the network, only stops when
-- the network is inhibited.
runBox :: Signal () () -> IO ()
runBox n = do
 (mq, n') <- stepSignal n (Just ())
 case mq of
   Just _ -> n' `seq` runBox n'
   Nothing -> return ()

-- | Holds a discrete value to be continuous. An initial value must be given
hold :: a -> Signal (Maybe a) a
hold a0 = mkSW_ a0 fromMaybe

-- | Takes a snapshot of b when an event a comes. Meanwhile, transform the
-- 'Stream' with the 'Cell' value
snapshot :: ((a,b) -> c) -> Signal (Maybe a, b) (Maybe c)
snapshot f = mkSF_ $ \(ma, b) ->
  case ma of
    Just a -> Just $ f (a,b)
    Nothing -> Nothing

state :: s -> ((a, s) -> s) -> Signal (Maybe a) s
state s0 update = loop $ proc (ma, s) -> do
  sDelay <- delay s0 -< s
  s' <- hold s0 <<< snapshot update -< (ma, sDelay)
  returnA -< (s', s')

------
-- Stripped down Timeless ends
------

-- | Problematic Arrow
test0 = proc () -> do
  s <- state 0 (\(_, coin) -> coin + 1) -< Nothing
  returnA -< ()


main :: IO ()
main = runBox test0
Community
  • 1
  • 1
Carl Dong
  • 1,299
  • 15
  • 26
  • 5
    You should really try to reduce this to a small example you can paste in the body of your question. I know that may be hard, but someone will have to do that to fix the bug, or even to determine which package is responsible. – dfeuer Dec 09 '16 at 22:02
  • 4
    This question has the potential to be very interesting, but there's missing information. You should provide (a minimised version of) the problematic code in the question itself. See http://stackoverflow.com/help/how-to-ask for more guidance – Benjamin Hodgson Dec 09 '16 at 22:03
  • This is already the smallest example I can possibly make for now. If you don't count my library Timeless, which is kind of difficult to reduce. I will try – Carl Dong Dec 09 '16 at 22:06
  • Yes, once I put up my "minimal" timeless, you will see that it is used in test0, that `state` statement. No netwire involved, I will put in a single file. – Carl Dong Dec 09 '16 at 22:22
  • 1
    There it is, the minimal example. No external library is needed, and no concurrency and MVar – Carl Dong Dec 09 '16 at 22:36
  • This looks like it could be a GHC bug. What version of GHC are you using? If someone hasn't already done so, you should report it on [the GHC bug tracker](https://ghc.haskell.org/trac/ghc/). – Benjamin Hodgson Dec 09 '16 at 22:58
  • 8.0.1. Maybe I should try something earlier. – Carl Dong Dec 09 '16 at 23:00
  • 7
    `mfix` for `IO` [uses an `MVar`](https://hackage.haskell.org/package/base-4.9.0.0/docs/src/System.IO.html#fixIO). Probably relevant question: [*\`mfix\` not working as expected*](http://stackoverflow.com/q/26635324/2751851). – duplode Dec 09 '16 at 23:03
  • 1
    Remove `seq` in `stepSignal` to get rid of the exception. I suspect that by forcing the value of signal `fixIO` indeed [hangs on empty MVar in IO](http://stackoverflow.com/questions/25876042/what-does-fixio-do) – Ed'ka Dec 10 '16 at 00:59
  • But then I get a heap exhausted problem. I tried to find where space leaks, but cannot locate the problem. Partially because I don't know how to read profile results, partially because `hp2ps` for all current GHC doesn't generate correct PS files – Carl Dong Dec 10 '16 at 21:01

0 Answers0