Global IORefs + Circular dep = surprising behavior?

2 Likes

I’m going to have a half-educated guess here: despite using {-# NOINLINE #-} to prevent it, myRef is still being copied.

Out of curiosity, what happens when the obligatory “read/write duo” is used instead of exporting myRef?

module MyLib (
  readMyRef,      -- :: IO Int
  writeMyRef,     -- :: Int -> IO () 
  readRefFromLib  -- :: IO ()
) where
         â‹® 
readRefFromLib =
 do x <- readMyRef
    putStrLn $ "readRefFromLib: " <> show x
1 Like

That’s actually closer to what my original code at work was, and it does make the problem much more obvious / surprising. The same thing happens with your example. I’ll update the issue with that example, thanks!

1 Like

Hrm - following a "trail of crumbs" has lead to this post:

https://github.com/commercialhaskell/stack/issues/2904#issuecomment-284944974

But at least @taylorfausak’s observation from:

https://github.com/commercialhaskell/stack/issues/3130

suggests the problem exists independently of the circular dependency in your example (and the use of top-level IORefs) - you can now produce a smaller MRE.

I’m not surprised that a “global” IORef (allocated with unsafePerformIO) leads to “surprising” behavior :sweat_smile: I strongly recommend to not do that!

…because top-level MVars at least provide basic concurrent access? :-P


I’m going to speculate that, due to helpful articles like:

…the majority of Haskellers are quite aware of the problems that this combination can cause.

I mean, it normally works. It’s a useful hack to inject things only for tests. The only reason it fails to work here is because of Stack rebuilding and linking things incorrectly.

But I’ve actually gotten a smaller repro without a mutable IORef (although the mutable IORef makes the issue much less obvious). I updated the GitHub issue

2 Likes