{-# LANGUAGE CPP #-}
module Control.AutoUpdate (
UpdateSettings,
defaultUpdateSettings,
updateAction,
updateFreq,
updateSpawnThreshold,
updateThreadName,
mkAutoUpdate,
mkAutoUpdateWithModify,
) where
#if __GLASGOW_HASKELL__ < 709
import Control.Applicative ((<*>))
#endif
import Control.Concurrent (forkIO, threadDelay)
import Control.Concurrent.MVar (
newEmptyMVar,
putMVar,
readMVar,
takeMVar,
tryPutMVar,
)
import Control.Exception (
SomeException,
catch,
mask_,
throw,
try,
)
import Control.Monad (void)
import Data.IORef (newIORef, readIORef, writeIORef)
import Data.Maybe (fromMaybe)
import GHC.Conc.Sync (labelThread)
defaultUpdateSettings :: UpdateSettings ()
defaultUpdateSettings :: UpdateSettings ()
defaultUpdateSettings =
UpdateSettings
{ updateFreq :: Int
updateFreq = Int
1000000
, updateSpawnThreshold :: Int
updateSpawnThreshold = Int
3
, updateAction :: IO ()
updateAction = () -> IO ()
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return ()
, updateThreadName :: String
updateThreadName = String
"AutoUpdate"
}
data UpdateSettings a = UpdateSettings
{ forall a. UpdateSettings a -> Int
updateFreq :: Int
, forall a. UpdateSettings a -> Int
updateSpawnThreshold :: Int
, forall a. UpdateSettings a -> IO a
updateAction :: IO a
, forall a. UpdateSettings a -> String
updateThreadName :: String
}
mkAutoUpdate :: UpdateSettings a -> IO (IO a)
mkAutoUpdate :: forall a. UpdateSettings a -> IO (IO a)
mkAutoUpdate UpdateSettings a
us = UpdateSettings a -> Maybe (a -> IO a) -> IO (IO a)
forall a. UpdateSettings a -> Maybe (a -> IO a) -> IO (IO a)
mkAutoUpdateHelper UpdateSettings a
us Maybe (a -> IO a)
forall a. Maybe a
Nothing
mkAutoUpdateWithModify :: UpdateSettings a -> (a -> IO a) -> IO (IO a)
mkAutoUpdateWithModify :: forall a. UpdateSettings a -> (a -> IO a) -> IO (IO a)
mkAutoUpdateWithModify UpdateSettings a
us a -> IO a
f = UpdateSettings a -> Maybe (a -> IO a) -> IO (IO a)
forall a. UpdateSettings a -> Maybe (a -> IO a) -> IO (IO a)
mkAutoUpdateHelper UpdateSettings a
us ((a -> IO a) -> Maybe (a -> IO a)
forall a. a -> Maybe a
Just a -> IO a
f)
mkAutoUpdateHelper :: UpdateSettings a -> Maybe (a -> IO a) -> IO (IO a)
mkAutoUpdateHelper :: forall a. UpdateSettings a -> Maybe (a -> IO a) -> IO (IO a)
mkAutoUpdateHelper UpdateSettings a
us Maybe (a -> IO a)
updateActionModify = do
MVar ()
needsRunning <- IO (MVar ())
forall a. IO (MVar a)
newEmptyMVar
MVar a
responseVar0 <- IO (MVar a)
forall a. IO (MVar a)
newEmptyMVar
IORef (Either (MVar a) a)
currRef <- Either (MVar a) a -> IO (IORef (Either (MVar a) a))
forall a. a -> IO (IORef a)
newIORef (Either (MVar a) a -> IO (IORef (Either (MVar a) a)))
-> Either (MVar a) a -> IO (IORef (Either (MVar a) a))
forall a b. (a -> b) -> a -> b
$ MVar a -> Either (MVar a) a
forall a b. a -> Either a b
Left MVar a
responseVar0
let fillRefOnExit :: IO () -> IO ()
fillRefOnExit IO ()
f = do
Either SomeException ()
eres <- IO () -> IO (Either SomeException ())
forall e a. Exception e => IO a -> IO (Either e a)
try IO ()
f
case Either SomeException ()
eres of
Left SomeException
e ->
IORef (Either (MVar a) a) -> Either (MVar a) a -> IO ()
forall a. IORef a -> a -> IO ()
writeIORef IORef (Either (MVar a) a)
currRef (Either (MVar a) a -> IO ()) -> Either (MVar a) a -> IO ()
forall a b. (a -> b) -> a -> b
$
String -> Either (MVar a) a
forall a. HasCallStack => String -> a
error (String -> Either (MVar a) a) -> String -> Either (MVar a) a
forall a b. (a -> b) -> a -> b
$
String
"Control.AutoUpdate.mkAutoUpdate: worker thread exited with exception: "
String -> String -> String
forall a. [a] -> [a] -> [a]
++ SomeException -> String
forall a. Show a => a -> String
show (SomeException
e :: SomeException)
Right () ->
IORef (Either (MVar a) a) -> Either (MVar a) a -> IO ()
forall a. IORef a -> a -> IO ()
writeIORef IORef (Either (MVar a) a)
currRef (Either (MVar a) a -> IO ()) -> Either (MVar a) a -> IO ()
forall a b. (a -> b) -> a -> b
$
String -> Either (MVar a) a
forall a. HasCallStack => String -> a
error (String -> Either (MVar a) a) -> String -> Either (MVar a) a
forall a b. (a -> b) -> a -> b
$
String
"Control.AutoUpdate.mkAutoUpdate: worker thread exited normally, "
String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"which should be impossible due to usage of infinite loop"
ThreadId
tid <- IO ThreadId -> IO ThreadId
forall a. IO a -> IO a
mask_ (IO ThreadId -> IO ThreadId) -> IO ThreadId -> IO ThreadId
forall a b. (a -> b) -> a -> b
$ IO () -> IO ThreadId
forkIO (IO () -> IO ThreadId) -> IO () -> IO ThreadId
forall a b. (a -> b) -> a -> b
$ IO () -> IO ()
fillRefOnExit (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ do
let loop :: MVar a -> Maybe a -> IO b
loop MVar a
responseVar Maybe a
maybea = do
MVar () -> IO ()
forall a. MVar a -> IO a
takeMVar MVar ()
needsRunning
a
a <- IO a -> IO a
forall a. IO a -> IO a
catchSome (IO a -> IO a) -> IO a -> IO a
forall a b. (a -> b) -> a -> b
$ IO a -> Maybe (IO a) -> IO a
forall a. a -> Maybe a -> a
fromMaybe (UpdateSettings a -> IO a
forall a. UpdateSettings a -> IO a
updateAction UpdateSettings a
us) (Maybe (a -> IO a)
updateActionModify Maybe (a -> IO a) -> Maybe a -> Maybe (IO a)
forall a b. Maybe (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Maybe a
maybea)
IORef (Either (MVar a) a) -> Either (MVar a) a -> IO ()
forall a. IORef a -> a -> IO ()
writeIORef IORef (Either (MVar a) a)
currRef (Either (MVar a) a -> IO ()) -> Either (MVar a) a -> IO ()
forall a b. (a -> b) -> a -> b
$ a -> Either (MVar a) a
forall a b. b -> Either a b
Right a
a
MVar a -> a -> IO ()
forall a. MVar a -> a -> IO ()
putMVar MVar a
responseVar a
a
Int -> IO ()
threadDelay (Int -> IO ()) -> Int -> IO ()
forall a b. (a -> b) -> a -> b
$ UpdateSettings a -> Int
forall a. UpdateSettings a -> Int
updateFreq UpdateSettings a
us
MVar a
responseVar' <- IO (MVar a)
forall a. IO (MVar a)
newEmptyMVar
IORef (Either (MVar a) a) -> Either (MVar a) a -> IO ()
forall a. IORef a -> a -> IO ()
writeIORef IORef (Either (MVar a) a)
currRef (Either (MVar a) a -> IO ()) -> Either (MVar a) a -> IO ()
forall a b. (a -> b) -> a -> b
$ MVar a -> Either (MVar a) a
forall a b. a -> Either a b
Left MVar a
responseVar'
MVar a -> Maybe a -> IO b
loop MVar a
responseVar' (a -> Maybe a
forall a. a -> Maybe a
Just a
a)
MVar a -> Maybe a -> IO ()
forall {b}. MVar a -> Maybe a -> IO b
loop MVar a
responseVar0 Maybe a
forall a. Maybe a
Nothing
ThreadId -> String -> IO ()
labelThread ThreadId
tid (String -> IO ()) -> String -> IO ()
forall a b. (a -> b) -> a -> b
$ UpdateSettings a -> String
forall a. UpdateSettings a -> String
updateThreadName UpdateSettings a
us
IO a -> IO (IO a)
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (IO a -> IO (IO a)) -> IO a -> IO (IO a)
forall a b. (a -> b) -> a -> b
$ do
Either (MVar a) a
mval <- IORef (Either (MVar a) a) -> IO (Either (MVar a) a)
forall a. IORef a -> IO a
readIORef IORef (Either (MVar a) a)
currRef
case Either (MVar a) a
mval of
Left MVar a
responseVar -> do
IO Bool -> IO ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (IO Bool -> IO ()) -> IO Bool -> IO ()
forall a b. (a -> b) -> a -> b
$ MVar () -> () -> IO Bool
forall a. MVar a -> a -> IO Bool
tryPutMVar MVar ()
needsRunning ()
MVar a -> IO a
forall a. MVar a -> IO a
readMVar MVar a
responseVar
Right a
val -> a -> IO a
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return a
val
catchSome :: IO a -> IO a
catchSome :: forall a. IO a -> IO a
catchSome IO a
act = IO a -> (SomeException -> IO a) -> IO a
forall e a. Exception e => IO a -> (e -> IO a) -> IO a
Control.Exception.catch IO a
act ((SomeException -> IO a) -> IO a)
-> (SomeException -> IO a) -> IO a
forall a b. (a -> b) -> a -> b
$ \SomeException
e -> a -> IO a
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (a -> IO a) -> a -> IO a
forall a b. (a -> b) -> a -> b
$ SomeException -> a
forall a e. Exception e => e -> a
throw (SomeException
e :: SomeException)