@@ -43,7 +43,6 @@ import GHC.Stack (CallStack, prettyCallStack)
4343
4444#ifdef NO_IGNORE_ASSERTS
4545import Control.Concurrent (yield )
46- import qualified Control.Exception
4746import Data.IORef
4847import GHC.Stack (HasCallStack , callStack )
4948import System.IO.Unsafe (unsafeDupablePerformIO , unsafePerformIO )
@@ -246,6 +245,7 @@ releaseRef ::
246245 -> m ()
247246releaseRef ref@ Ref {refobj} = do
248247 assertNoDoubleRelease ref
248+ assertNoForgottenRefs
249249 releaseRefTracker ref
250250 decrementRefCounter (getRefCounter refobj)
251251
@@ -256,7 +256,7 @@ releaseRef ref@Ref{refobj} = do
256256-- | Get the object in a 'Ref'. Be careful with retaining the object for too
257257-- long, since the object must not be used after 'releaseRef' is called.
258258--
259- pattern DeRef :: obj -> Ref obj
259+ pattern DeRef :: HasCallStackIfDebug => obj -> Ref obj
260260#ifndef NO_IGNORE_ASSERTS
261261pattern DeRef obj <- Ref obj
262262#else
@@ -281,14 +281,19 @@ deRef ref@Ref{refobj} =
281281--
282282withRef ::
283283 forall m obj a .
284- PrimMonad m
284+ ( PrimMonad m , MonadThrow m )
285285 => HasCallStackIfDebug
286286 => Ref obj
287287 -> (obj -> m a )
288288 -> m a
289289withRef ref@ Ref {refobj} f = do
290290 assertNoUseAfterRelease ref
291+ assertNoForgottenRefs
291292 f refobj
293+ #ifndef NO_IGNORE_ASSERTS
294+ where
295+ _unused = throwIO @ m @ SomeException
296+ #endif
292297
293298{-# SPECIALISE
294299 dupRef ::
@@ -300,14 +305,19 @@ withRef ref@Ref{refobj} f = do
300305-- | Duplicate an existing reference, to produce a new reference.
301306--
302307dupRef ::
303- (RefCounted m obj , PrimMonad m )
308+ forall m obj . (RefCounted m obj , PrimMonad m , MonadThrow m )
304309 => HasCallStackIfDebug
305310 => Ref obj
306311 -> m (Ref obj )
307312dupRef ref@ Ref {refobj} = do
308313 assertNoUseAfterRelease ref
314+ assertNoForgottenRefs
309315 incrementRefCounter (getRefCounter refobj)
310316 newRefWithTracker refobj
317+ #ifndef NO_IGNORE_ASSERTS
318+ where
319+ _unused = throwIO @ m @ SomeException
320+ #endif
311321
312322-- | A \"weak\" reference to an object: that is, a reference that does not
313323-- guarantee to keep the object alive. If however the object is still alive
@@ -401,6 +411,10 @@ instance Exception RefException where
401411releaseRefTracker :: PrimMonad m => Ref a -> m ()
402412releaseRefTracker _ = return ()
403413
414+ {-# INLINE assertNoForgottenRefs #-}
415+ assertNoForgottenRefs :: PrimMonad m => m ()
416+ assertNoForgottenRefs = return ()
417+
404418{-# INLINE assertNoUseAfterRelease #-}
405419assertNoUseAfterRelease :: PrimMonad m => Ref a -> m ()
406420assertNoUseAfterRelease _ = return ()
@@ -441,8 +455,19 @@ globalRefIdSupply = unsafePerformIO $ newPrimVar 0
441455globalForgottenRef :: IORef (Maybe (RefId , CallStack ))
442456globalForgottenRef = unsafePerformIO $ newIORef Nothing
443457
458+ -- | This version of 'unsafeIOToPrim' is strict in the result of the arument
459+ -- action.
460+ --
461+ -- Without strictness it seems that some IO side effects are not happening at
462+ -- the right time, like clearing the @globalForgottenRef@ in
463+ -- @assertNoForgottenRefs@.
464+ unsafeIOToPrimStrict :: PrimMonad m => IO a -> m a
465+ unsafeIOToPrimStrict k = do
466+ ! x <- unsafeIOToPrim k
467+ pure x
468+
444469newRefTracker :: PrimMonad m => CallStack -> m RefTracker
445- newRefTracker allocSite = unsafeIOToPrim $ do
470+ newRefTracker allocSite = unsafeIOToPrimStrict $ do
446471 inner <- newIORef Nothing
447472 outer <- newIORef inner
448473 refid <- fetchAddInt globalRefIdSupply 1
@@ -452,7 +477,7 @@ newRefTracker allocSite = unsafeIOToPrim $ do
452477
453478releaseRefTracker :: (HasCallStack , PrimMonad m ) => Ref a -> m ()
454479releaseRefTracker Ref { reftracker = RefTracker _refid _weak outer _ } =
455- unsafeIOToPrim $ do
480+ unsafeIOToPrimStrict $ do
456481 inner <- readIORef outer
457482 let releaseSite = callStack
458483 writeIORef inner (Just releaseSite)
@@ -476,43 +501,44 @@ finaliserRefTracker inner refid allocSite = do
476501 Just (refid', _) | refid < refid' -> return ()
477502 _ -> writeIORef globalForgottenRef (Just (refid, allocSite))
478503
479- assertNoForgottenRefs :: IO ()
504+ assertNoForgottenRefs :: ( PrimMonad m , MonadThrow m ) => m ()
480505assertNoForgottenRefs = do
481- mrefs <- readIORef globalForgottenRef
506+ mrefs <- unsafeIOToPrimStrict $ readIORef globalForgottenRef
482507 case mrefs of
483508 Nothing -> return ()
484509 Just (refid, allocSite) -> do
485510 -- Clear the var so we don't assert again.
486- writeIORef globalForgottenRef Nothing
511+ --
512+ -- Using the strict version is important here: if @m ~ IOSim s@, then
513+ -- using the non-strict version will lead to @RefNeverReleased@
514+ -- exceptions.
515+ unsafeIOToPrimStrict $ writeIORef globalForgottenRef Nothing
487516 throwIO (RefNeverReleased refid allocSite)
488517
489- assertNoUseAfterRelease :: ( PrimMonad m , HasCallStack ) => Ref a -> m ()
490- assertNoUseAfterRelease Ref { reftracker = RefTracker refid _weak outer allocSite } =
491- unsafeIOToPrim $ do
492- released <- readIORef =<< readIORef outer
518+
519+ assertNoUseAfterRelease :: ( PrimMonad m , MonadThrow m , HasCallStack ) => Ref a -> m ()
520+ assertNoUseAfterRelease Ref { reftracker = RefTracker refid _weak outer allocSite } = do
521+ released <- unsafeIOToPrimStrict ( readIORef =<< readIORef outer)
493522 case released of
494523 Nothing -> pure ()
495524 Just releaseSite -> do
496525 -- The site where the reference is used after release
497526 let useSite = callStack
498- Control.Exception. throwIO (RefUseAfterRelease refid allocSite releaseSite useSite)
499- assertNoForgottenRefs
527+ throwIO (RefUseAfterRelease refid allocSite releaseSite useSite)
500528#if !(MIN_VERSION_base(4,20,0))
501529 where
502530 _unused = callStack
503531#endif
504532
505- assertNoDoubleRelease :: (PrimMonad m , HasCallStack ) => Ref a -> m ()
506- assertNoDoubleRelease Ref { reftracker = RefTracker refid _weak outer allocSite } =
507- unsafeIOToPrim $ do
508- released <- readIORef =<< readIORef outer
533+ assertNoDoubleRelease :: (PrimMonad m , MonadThrow m , HasCallStack ) => Ref a -> m ()
534+ assertNoDoubleRelease Ref { reftracker = RefTracker refid _weak outer allocSite } = do
535+ released <- unsafeIOToPrimStrict (readIORef =<< readIORef outer)
509536 case released of
510537 Nothing -> pure ()
511538 Just releaseSite1 -> do
512539 -- The second release site
513540 let releaseSite2 = callStack
514- Control.Exception. throwIO (RefDoubleRelease refid allocSite releaseSite1 releaseSite2)
515- assertNoForgottenRefs
541+ throwIO (RefDoubleRelease refid allocSite releaseSite1 releaseSite2)
516542#if !(MIN_VERSION_base(4,20,0))
517543 where
518544 _unused = callStack
@@ -526,7 +552,7 @@ assertNoDoubleRelease Ref { reftracker = RefTracker refid _weak outer allocSite
526552-- Note however that this is not the only place where 'RefNeverReleased'
527553-- exceptions can be thrown. All Ref operations poll for forgotten refs.
528554--
529- checkForgottenRefs :: IO ()
555+ checkForgottenRefs :: forall m . ( PrimMonad m , MonadThrow m ) => m ()
530556checkForgottenRefs = do
531557#ifndef NO_IGNORE_ASSERTS
532558 return ()
@@ -539,20 +565,22 @@ checkForgottenRefs = do
539565 -- Unfortunately, this relies on the implementation of the GHC scheduler,
540566 -- not on any Haskell specification, and is therefore both non-portable and
541567 -- presumably rather brittle. Therefore, for good measure, we do it twice.
542- performMajorGCWithBlockingIfAvailable
543- yield
544- performMajorGCWithBlockingIfAvailable
545- yield
568+ unsafeIOToPrimStrict $ do
569+ performMajorGCWithBlockingIfAvailable
570+ yield
571+ performMajorGCWithBlockingIfAvailable
572+ yield
546573 assertNoForgottenRefs
547- where
548574#endif
575+ where
576+ _unused = throwIO @ m @ SomeException
549577
550578-- | Ignore and reset the state of forgotten reference tracking. This ensures
551579-- that any stale fogotten references are not reported later.
552580--
553581-- This is especillay important in QC tests with shrinking which otherwise
554582-- leads to confusion.
555- ignoreForgottenRefs :: IO ()
583+ ignoreForgottenRefs :: ( PrimMonad m , MonadCatch m ) => m ()
556584ignoreForgottenRefs = void $ try @ _ @ SomeException $ checkForgottenRefs
557585
558586#ifdef NO_IGNORE_ASSERTS
0 commit comments