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
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!
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 I strongly recommend to not do that!
…because top-level MVar
s 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