Skip to content

Commit be1276c

Browse files
committed
Throw RefExceptions in the current monad, instead of in IO
This improves reliability of catching `RefException`s in `IOSim`. In `IOSim`, we can not catch exceptions that were thrown in `IO`.
1 parent 8aa695c commit be1276c

File tree

2 files changed

+33
-26
lines changed

2 files changed

+33
-26
lines changed

src-control/Control/RefCount.hs

Lines changed: 32 additions & 25 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)
@@ -282,7 +281,7 @@ deRef ref@Ref{refobj} =
282281
--
283282
withRef ::
284283
forall m obj a.
285-
PrimMonad m
284+
(PrimMonad m, MonadThrow m)
286285
=> HasCallStackIfDebug
287286
=> Ref obj
288287
-> (obj -> m a)
@@ -291,6 +290,10 @@ withRef ref@Ref{refobj} f = do
291290
assertNoUseAfterRelease ref
292291
assertNoForgottenRefs
293292
f refobj
293+
#ifndef NO_IGNORE_ASSERTS
294+
where
295+
_unused = throwIO @m @SomeException
296+
#endif
294297

295298
{-# SPECIALISE
296299
dupRef ::
@@ -302,7 +305,7 @@ withRef ref@Ref{refobj} f = do
302305
-- | Duplicate an existing reference, to produce a new reference.
303306
--
304307
dupRef ::
305-
(RefCounted m obj, PrimMonad m)
308+
forall m obj. (RefCounted m obj, PrimMonad m, MonadThrow m)
306309
=> HasCallStackIfDebug
307310
=> Ref obj
308311
-> m (Ref obj)
@@ -311,6 +314,10 @@ dupRef ref@Ref{refobj} = do
311314
assertNoForgottenRefs
312315
incrementRefCounter (getRefCounter refobj)
313316
newRefWithTracker refobj
317+
#ifndef NO_IGNORE_ASSERTS
318+
where
319+
_unused = throwIO @m @SomeException
320+
#endif
314321

315322
-- | A \"weak\" reference to an object: that is, a reference that does not
316323
-- guarantee to keep the object alive. If however the object is still alive
@@ -483,42 +490,40 @@ finaliserRefTracker inner refid allocSite = do
483490
Just (refid', _) | refid < refid' -> return ()
484491
_ -> writeIORef globalForgottenRef (Just (refid, allocSite))
485492

486-
assertNoForgottenRefs :: PrimMonad m => m ()
487-
assertNoForgottenRefs =
488-
unsafeIOToPrim $ do
489-
mrefs <- readIORef globalForgottenRef
493+
assertNoForgottenRefs :: (PrimMonad m, MonadThrow m) => m ()
494+
assertNoForgottenRefs = do
495+
mrefs <- unsafeIOToPrim $ readIORef globalForgottenRef
490496
case mrefs of
491497
Nothing -> return ()
492498
Just (refid, allocSite) -> do
493499
-- Clear the var so we don't assert again.
494-
writeIORef globalForgottenRef Nothing
500+
unsafeIOToPrim $ writeIORef globalForgottenRef Nothing
495501
throwIO (RefNeverReleased refid allocSite)
496502

497-
assertNoUseAfterRelease :: (PrimMonad m, HasCallStack) => Ref a -> m ()
498-
assertNoUseAfterRelease Ref { reftracker = RefTracker refid _weak outer allocSite } =
499-
unsafeIOToPrim $ do
500-
released <- readIORef =<< readIORef outer
503+
504+
assertNoUseAfterRelease :: (PrimMonad m, MonadThrow m, HasCallStack) => Ref a -> m ()
505+
assertNoUseAfterRelease Ref { reftracker = RefTracker refid _weak outer allocSite } = do
506+
released <- unsafeIOToPrim (readIORef =<< readIORef outer)
501507
case released of
502508
Nothing -> pure ()
503509
Just releaseSite -> do
504510
-- The site where the reference is used after release
505511
let useSite = callStack
506-
Control.Exception.throwIO (RefUseAfterRelease refid allocSite releaseSite useSite)
512+
throwIO (RefUseAfterRelease refid allocSite releaseSite useSite)
507513
#if !(MIN_VERSION_base(4,20,0))
508514
where
509515
_unused = callStack
510516
#endif
511517

512-
assertNoDoubleRelease :: (PrimMonad m, HasCallStack) => Ref a -> m ()
513-
assertNoDoubleRelease Ref { reftracker = RefTracker refid _weak outer allocSite } =
514-
unsafeIOToPrim $ do
515-
released <- readIORef =<< readIORef outer
518+
assertNoDoubleRelease :: (PrimMonad m, MonadThrow m, HasCallStack) => Ref a -> m ()
519+
assertNoDoubleRelease Ref { reftracker = RefTracker refid _weak outer allocSite } = do
520+
released <- unsafeIOToPrim (readIORef =<< readIORef outer)
516521
case released of
517522
Nothing -> pure ()
518523
Just releaseSite1 -> do
519524
-- The second release site
520525
let releaseSite2 = callStack
521-
Control.Exception.throwIO (RefDoubleRelease refid allocSite releaseSite1 releaseSite2)
526+
throwIO (RefDoubleRelease refid allocSite releaseSite1 releaseSite2)
522527
#if !(MIN_VERSION_base(4,20,0))
523528
where
524529
_unused = callStack
@@ -532,7 +537,7 @@ assertNoDoubleRelease Ref { reftracker = RefTracker refid _weak outer allocSite
532537
-- Note however that this is not the only place where 'RefNeverReleased'
533538
-- exceptions can be thrown. All Ref operations poll for forgotten refs.
534539
--
535-
checkForgottenRefs :: IO ()
540+
checkForgottenRefs :: forall m. (PrimMonad m, MonadThrow m) => m ()
536541
checkForgottenRefs = do
537542
#ifndef NO_IGNORE_ASSERTS
538543
return ()
@@ -545,20 +550,22 @@ checkForgottenRefs = do
545550
-- Unfortunately, this relies on the implementation of the GHC scheduler,
546551
-- not on any Haskell specification, and is therefore both non-portable and
547552
-- presumably rather brittle. Therefore, for good measure, we do it twice.
548-
performMajorGCWithBlockingIfAvailable
549-
yield
550-
performMajorGCWithBlockingIfAvailable
551-
yield
553+
unsafeIOToPrim $ do
554+
performMajorGCWithBlockingIfAvailable
555+
yield
556+
performMajorGCWithBlockingIfAvailable
557+
yield
552558
assertNoForgottenRefs
553-
where
554559
#endif
560+
where
561+
_unused = throwIO @m @SomeException
555562

556563
-- | Ignore and reset the state of forgotten reference tracking. This ensures
557564
-- that any stale fogotten references are not reported later.
558565
--
559566
-- This is especillay important in QC tests with shrinking which otherwise
560567
-- leads to confusion.
561-
ignoreForgottenRefs :: IO ()
568+
ignoreForgottenRefs :: (PrimMonad m, MonadCatch m) => m ()
562569
ignoreForgottenRefs = void $ try @_ @SomeException $ checkForgottenRefs
563570

564571
#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)