{-# OPTIONS_GHC -Wno-missing-signatures #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE CPP #-}
module Debug.Breakpoint.TimerManager
  ( modifyTimeouts
  , suspendTimeouts
  ) where

import           Control.Concurrent(rtsSupportsBoundThreads)
import           Control.Monad (when)
import           Data.Foldable (foldl')
import           Data.IORef (atomicModifyIORef')
import           Data.Word (Word64)
#if MIN_VERSION_ghc(9,2,0)
import qualified GHC.Clock as Clock
#endif
import           GHC.Event
import           Language.Haskell.TH
import           Language.Haskell.TH.Syntax

--------------------------------------------------------------------------------
-- Hidden functions imported via TH
--------------------------------------------------------------------------------

psqToList :: IntPSQ v -> [Elem v]
psqToList =
  $(pure $ VarE $
      Name (OccName "toList")
           (NameG VarName (PkgName "base") (ModName "GHC.Event.PSQ"))
   )

psqAdjust :: (Prio -> Prio) -> Key -> PSQ a -> PSQ a
psqAdjust =
  $(pure $ VarE $
      Name (OccName "adjust")
           (NameG VarName (PkgName "base") (ModName "GHC.Event.PSQ"))
   )

psqKey :: Elem a -> Key
psqKey =
  $(pure $ VarE $
      Name (OccName "key")
           (NameG VarName (PkgName "base") (ModName "GHC.Event.PSQ"))
   )

-- emTimeouts :: TimerManager -> IORef TimeoutQueue
emTimeouts :: TimerManager -> IORef TimeoutQueue
emTimeouts =
  $(pure $ VarE $
      Name (OccName "emTimeouts")
           (NameG VarName (PkgName "base") (ModName "GHC.Event.TimerManager"))
   )

wakeManager :: TimerManager -> IO ()
wakeManager :: TimerManager -> IO ()
wakeManager =
  $(pure $ VarE $
      Name (OccName "wakeManager")
           (NameG VarName (PkgName "base") (ModName "GHC.Event.TimerManager"))
   )

-- Windows specific definitions
-- #if defined(mingw32_HOST_OS)
-- modifyDelay =
--   $( do
--      let delayName = Name (OccName "Delay")
--                           (NameG DataName (PkgName "base") (ModName "GHC.Conc.Windows"))
-- 
--          matchDelay f =
--            match (conP delayName [varP $ mkName "secs", varP $ mkName "mvar"]) body []
--              where
--                body = normalB $ appsE [ conE delayName
--                                       , appE (varE $ mkName "f") (varE $ mkName "secs")
--                                       , varE $ mkName "mvar"
--                                       ]
-- 
--          delaySTMName = Name (OccName "DelaySTM")
--                           (NameG DataName (PkgName "base") (ModName "GHC.Conc.Windows"))
-- 
--          matchDelaySTM f =
--            match (conP delaySTMName [varP $ mkName "secs", varP $ mkName "tvar"]) body []
--              where
--                body = normalB $ appsE [ conE delaySTMName
--                                       , appE (varE $ mkName "f") (varE $ mkName "secs")
--                                       , varE $ mkName "tvar"
--                                       ]
-- 
--      lamE [varP $ mkName "f", varP $ mkName "delay"] $
--        caseE (varE $ mkName "delay")
--          [ matchDelay
--          , matchDelaySTM
--          ]
--    )
-- 
-- pendingDelays =
--   $(pure $ VarE $
--       Name (OccName "pendingDelays")
--            (NameG VarName (PkgName "base") (ModName "GHC.Conc.Windows"))
--   )
-- #endif

--------------------------------------------------------------------------------
-- Timeout editing
--------------------------------------------------------------------------------

-- editTimeouts :: TimerManager -> TimeoutEdit -> IO ()
editTimeouts :: TimerManager -> (TimeoutQueue -> TimeoutQueue) -> IO ()
editTimeouts TimerManager
mgr TimeoutQueue -> TimeoutQueue
g = do
  forall a b. IORef a -> (a -> (a, b)) -> IO b
atomicModifyIORef' (TimerManager -> IORef TimeoutQueue
emTimeouts TimerManager
mgr) TimeoutQueue -> (TimeoutQueue, ())
f
  TimerManager -> IO ()
wakeManager TimerManager
mgr
  where
    f :: TimeoutQueue -> (TimeoutQueue, ())
f TimeoutQueue
q = (TimeoutQueue -> TimeoutQueue
g TimeoutQueue
q, ())

-- | Modify the times in nanoseconds at which all currently registered timeouts
-- will expire.
modifyTimeouts :: (Word64 -> Word64) -> IO ()
modifyTimeouts :: (Prio -> Prio) -> IO ()
modifyTimeouts Prio -> Prio
f =
  -- This only works for the threaded RTS
  forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when Bool
rtsSupportsBoundThreads forall a b. (a -> b) -> a -> b
$ do
#if defined(mingw32_HOST_OS)
    pure ()
    -- Windows has its own way of tracking delays
--     let modifyDelay = \case
--           Delay x y -> Delay (f x) y
--           DelaySTM x y -> DelaySTM (f x) y
--     atomicModifyIORef'_ pendingDelays (fmap $ modifyDelay f)
#else
    TimerManager
mgr <- IO TimerManager
getSystemTimerManager
    TimerManager -> (TimeoutQueue -> TimeoutQueue) -> IO ()
editTimeouts TimerManager
mgr forall a b. (a -> b) -> a -> b
$ \TimeoutQueue
pq ->
      let els :: [Elem (IO ())]
els = forall v. IntPSQ v -> [Elem v]
psqToList TimeoutQueue
pq
          upd :: PSQ a -> Key -> PSQ a
upd PSQ a
pq' Key
k =
            forall a. (Prio -> Prio) -> Key -> PSQ a -> PSQ a
psqAdjust Prio -> Prio
f Key
k PSQ a
pq'
       in forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl' forall {a}. PSQ a -> Key -> PSQ a
upd TimeoutQueue
pq (forall a. Elem a -> Key
psqKey forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [Elem (IO ())]
els)
#endif

-- | has the effect of suspending timeouts while an action is occurring. This
-- is only used for GHC >= 9.2 because the semantics are too strange without
-- the ability to freeze the runtime.
suspendTimeouts :: IO a -> IO a
#if MIN_VERSION_ghc(9,2,0)
suspendTimeouts :: forall a. IO a -> IO a
suspendTimeouts IO a
action = do
  let oneYear :: Prio
oneYear = Prio
1000 forall a. Num a => a -> a -> a
* Prio
1000000 forall a. Num a => a -> a -> a
* Prio
60 forall a. Num a => a -> a -> a
* Prio
60 forall a. Num a => a -> a -> a
* Prio
24 forall a. Num a => a -> a -> a
* Prio
365
  -- Add a large length of time to all timeouts so that they don't immediately
  -- expire when blocking ends
  (Prio -> Prio) -> IO ()
modifyTimeouts (forall a. Num a => a -> a -> a
+ Prio
oneYear)
  Prio
before <- IO Prio
Clock.getMonotonicTimeNSec
  a
r <- IO a
action
  Prio
after <- IO Prio
Clock.getMonotonicTimeNSec
  let elapsed :: Prio
elapsed = Prio
after forall a. Num a => a -> a -> a
- Prio
before
  -- Set timeouts back to where they were plus the length of time spent blocking
  (Prio -> Prio) -> IO ()
modifyTimeouts (forall a. Num a => a -> a -> a
subtract forall a b. (a -> b) -> a -> b
$ Prio
oneYear forall a. Num a => a -> a -> a
- Prio
elapsed)
  -- NB: any timeouts registered right before the block or immediately afterwards
  -- would result in strange behavior. Perhaps do an atomic modify of the IORef
  -- holding the timeout queue that covers the whole transaction?
  forall (f :: * -> *) a. Applicative f => a -> f a
pure a
r
#else
suspendTimeouts = id
#endif