
Am Samstag 17 April 2010 22:11:05 schrieb Bertram Felgenhauer:
Daniel Fischer wrote:
Am Samstag 17 April 2010 14:41:28 schrieb Simon Peyton-Jones:
I have not been following the details of this, I'm afraid, but I notice
this:
forever' m = do _ <- m forever' m
When I define that version of forever, the space leak goes away.
What was the old version of forever that led to the leak?
Control.Monad.forever
forever :: Monad m => m a -> m b forever m = m >> forever m
However, that isn't the problem. In my tests, both variants of forever exhibit the same behaviour, what makes it leak or not is the optimisation level.
This definition, plus sharing, is the source of the space leak. Consider this modification of your code:
import Control.Concurrent
always :: Monad m => m a -> m b always a = -- let act = a >> act in act do _ <- a always a
noop :: IO () noop = return ()
body :: IO () body = always noop
spawner :: IO () spawner = do forkIO $ body putStrLn "Delaying" threadDelay 1000000 body `seq` return ()
main :: IO () main = do putStrLn "Spawning" forkIO spawner putStrLn "Delaying main" threadDelay 4000000
Note that the 'always' in 'spawner' is gone, but it still exhibits the space leak. The leak goes away if the final line of 'spawner' is removed, hinting at the real problem: 'always' actually creates a long chain of actions instead of tying the knot.
Except that with optimisations turned on, GHC ties the knot for you (at least if always isn't exported). Without -fno-state-hack, the knot is tied so tightly that always (return ()) is never descheduled (and there's no leak). With -fno-state-hack, I get Rec { Main.main_always :: GHC.Types.IO () -> GHC.Types.IO () GblId [Arity 1 NoCafRefs Str: DmdType L] Main.main_always = \ (a_aeO :: GHC.Types.IO ()) -> let { k_sYz :: GHC.Types.IO () LclId [Str: DmdType] k_sYz = Main.main_always a_aeO } in (\ (eta_ann :: GHC.Prim.State# GHC.Prim.RealWorld) -> case (a_aeO `cast` (GHC.Types.NTCo:IO () :: GHC.Types.IO () ~ (GHC.Prim.State# GHC.Prim.RealWorld -> (# GHC.Prim.State# GHC.Prim.RealWorld, () #)))) eta_ann of _ { (# new_s_anz, _ #) -> (k_sYz `cast` (GHC.Types.NTCo:IO () :: GHC.Types.IO () ~ (GHC.Prim.State# GHC.Prim.RealWorld -> (# GHC.Prim.State# GHC.Prim.RealWorld, () #)))) new_s_anz }) `cast` (sym (GHC.Types.NTCo:IO ()) :: (GHC.Prim.State# GHC.Prim.RealWorld -> (# GHC.Prim.State# GHC.Prim.RealWorld, () #)) ~ GHC.Types.IO ()) end Rec } which, despite tying the knot, leaks (so the program at least terminates).
Indeed the following definition of 'always' (or 'forever') fares better in that regard, but is more susceptible to producing unproductive loops:
Indeed, that doesn't terminate with -O2 -fno-state-hack
always a = let act = a >> act in act
(I used noop = yield for avoiding that problem in my tests)
regards,
Bertram