packages feed

ghc-debug-client-0.8.0.0: src/GHC/Debug/Async.hs

{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveTraversable #-}
-- | Async API for heap traversals or computations that can be executed
-- asynchronously.
--
-- Async variations of heap traverals produce a result of 'AsyncTraceT' or 'AsyncTrace'
-- which can be periodically queried for progress.
-- This useful for implementing responsive UIs, as the user doesn't have to wait for the
-- heap traversal to finish before seeing the result.
module GHC.Debug.Async (
  -- * Async API
  AsyncTraceT(..),
  AsyncTrace,
  mkAsyncTrace,
  withAsyncTrace,
  waitForAsyncTraceResultM,
  AsyncTraceResult(..),
  expectAsyncTraceResultIsDone,
  expectAsyncTraceResultIsDoneM,
) where

import GHC.Debug.Client.Monad
import GHC.Stack.Types (HasCallStack)
import Data.IORef

data AsyncTraceResult s = Done s  -- ^ The traversal has finished, the result is the accumulated state.
                        | Running s -- ^ The traversal is running, the current result
                        | NotStarted -- ^ The traversal has not started yet.
                        deriving (Foldable, Traversable, Functor, Show)

data AsyncTraceT m s = AsyncTrace
  { asyncResult :: IO (AsyncTraceResult s)
  -- ^ The current status of the computation.
  -- This should not be blocking indefinitely.
  , asyncWait :: m ()
  -- ^ Wait for the result to complete.
  -- Once completed, 'asyncResult' must contain a 'Done' value.
  } deriving (Functor)

type AsyncTrace = AsyncTraceT DebugM

mkAsyncTrace :: IO (AsyncTraceResult s) -> m () -> AsyncTraceT m s
mkAsyncTrace res waitRes = AsyncTrace
  { asyncResult = res
  , asyncWait = waitRes
  }

waitForAsyncTraceResultM :: DebugMonad m => AsyncTraceT m s -> m s
waitForAsyncTraceResultM asyncTrace = do
  asyncWait asyncTrace
  expectAsyncTraceResultIsDoneM $ asyncResult asyncTrace

expectAsyncTraceResultIsDone :: HasCallStack => IO (AsyncTraceResult a) -> IO a
expectAsyncTraceResultIsDone read_results = do
  res <- read_results
  case res of
    Done s -> return s
    NotStarted -> error "expectAsyncTraceResultIsDone: NotStarted"
    Running _ -> error "expectAsyncTraceResultIsDone: Running"

expectAsyncTraceResultIsDoneM :: (HasCallStack, DebugMonad m) => IO (AsyncTraceResult a) -> m a
expectAsyncTraceResultIsDoneM = unsafeLiftIO . expectAsyncTraceResultIsDone

-- | Simplified API, provide an action that be used to peek at the current progress of the traversal
-- and an action that is executed asynchronously. It is the responsibility of the caller to make sure that
-- the asynchronous action updates the progress.
withAsyncTrace :: forall a m . DebugMonad m => IO a -> m () -> m (AsyncTraceT m a)
withAsyncTrace read_results runHeapTrace = do
  progressVar <- unsafeLiftIO $ newIORef NotStarted

  let
    reportProgress :: IO (AsyncTraceResult a)
    reportProgress = do
      progress <- readIORef progressVar
      traverse (\_ -> read_results) progress

    performAction :: m ()
    performAction = do
        unsafeLiftIO $ writeIORef progressVar (Running ())
        runHeapTrace
        unsafeLiftIO $ writeIORef progressVar (Done ())

  pure $ mkAsyncTrace reportProgress performAction