Skip to content

Commit ec24627

Browse files
authored
Merge pull request #607 from IntersectMBO/jdral/iosim-refs
Make `RefException`s `catch`able in `IOSim`
2 parents fd3cbec + 9a93adf commit ec24627

File tree

3 files changed

+159
-74
lines changed

3 files changed

+159
-74
lines changed

src-control/Control/RefCount.hs

Lines changed: 56 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ import GHC.Stack (CallStack, prettyCallStack)
4343

4444
#ifdef NO_IGNORE_ASSERTS
4545
import Control.Concurrent (yield)
46-
import qualified Control.Exception
4746
import Data.IORef
4847
import GHC.Stack (HasCallStack, callStack)
4948
import System.IO.Unsafe (unsafeDupablePerformIO, unsafePerformIO)
@@ -246,6 +245,7 @@ releaseRef ::
246245
-> m ()
247246
releaseRef 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
261261
pattern DeRef obj <- Ref obj
262262
#else
@@ -281,14 +281,19 @@ deRef ref@Ref{refobj} =
281281
--
282282
withRef ::
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
289289
withRef 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
--
302307
dupRef ::
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)
307312
dupRef 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
401411
releaseRefTracker :: PrimMonad m => Ref a -> m ()
402412
releaseRefTracker _ = return ()
403413

414+
{-# INLINE assertNoForgottenRefs #-}
415+
assertNoForgottenRefs :: PrimMonad m => m ()
416+
assertNoForgottenRefs = return ()
417+
404418
{-# INLINE assertNoUseAfterRelease #-}
405419
assertNoUseAfterRelease :: PrimMonad m => Ref a -> m ()
406420
assertNoUseAfterRelease _ = return ()
@@ -441,8 +455,19 @@ globalRefIdSupply = unsafePerformIO $ newPrimVar 0
441455
globalForgottenRef :: IORef (Maybe (RefId, CallStack))
442456
globalForgottenRef = 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+
444469
newRefTracker :: 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

453478
releaseRefTracker :: (HasCallStack, PrimMonad m) => Ref a -> m ()
454479
releaseRefTracker 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 ()
480505
assertNoForgottenRefs = 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 ()
530556
checkForgottenRefs = 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 ()
556584
ignoreForgottenRefs = void $ try @_ @SomeException $ checkForgottenRefs
557585

558586
#ifdef NO_IGNORE_ASSERTS

src/Database/LSMTree/Internal/MergeSchedule.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,7 @@ releaseIncomingRun (Merging _ _ _ mr) = releaseRef mr
420420
-> Ref (Run IO h)
421421
-> IO (IncomingRun IO h) #-}
422422
newIncomingSingleRun ::
423-
PrimMonad m
423+
(PrimMonad m, MonadThrow m)
424424
=> Tracer m (AtLevel MergeTrace)
425425
-> LevelNo
426426
-> Ref (Run m h)

0 commit comments

Comments
 (0)