-
Notifications
You must be signed in to change notification settings - Fork 31
/
Copy pathProcess.hs
114 lines (95 loc) · 3.41 KB
/
Process.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
-- | Core Process code
{-# LANGUAGE ExistentialQuantification, FlexibleInstances,
GeneralizedNewtypeDeriving,
ScopedTypeVariables,
DeriveDataTypeable,
MultiParamTypeClasses, CPP #-}
module Process (
-- * Types
Process
-- * Interface
, runP
, spawnP
, catchP
, cleanupP
, stopP
-- * Log Interface
, Logging(..)
, logP
, infoP
, debugP
, warningP
, criticalP
, errorP
)
where
import Control.Applicative
import Control.Concurrent
import Control.Exception
import Control.Monad.Reader
import Control.Monad.State.Strict
import Data.Typeable
import Prelude hiding (log)
import System.Log.Logger
-- | A @Process a b c@ is the type of processes with access to configuration data @a@, state @b@
-- returning values of type @c@. Usually, the read-only data are configuration parameters and
-- channels, and the state the internal process state. It is implemented by means of a transformer
-- stack on top of IO.
newtype Process a b c = Process (ReaderT a (StateT b IO) c)
deriving (Functor, Applicative, Monad, MonadIO, MonadState b, MonadReader a)
data StopException = StopException
deriving (Show, Typeable)
instance Exception StopException
stopP :: Process a b c
stopP = throw StopException
-- | Run the process monad given a configuation of type @a@ and a initial state of type @b@
runP :: a -> b -> Process a b c -> IO (c, b)
runP c st (Process p) = runStateT (runReaderT p c) st
-- | Spawn and run a process monad
spawnP :: a -> b -> Process a b () -> IO ThreadId
spawnP c st p = forkIO proc
where proc = runP c st p >> return ()
-- | Run the process monad for its side effect, with a stopHandler if exceptions
-- are raised in the process
catchP :: Logging a => Process a b () -> Process a b () -> Process a b ()
catchP proc stopH = cleanupP proc stopH (return ())
-- | Run the process monad for its side effect. @cleanupP p sh ch@ describes to
-- run @p@. If @p@ dies by a kill from a supervisor, run @ch@. Otherwise it runs
-- @ch >> sh@ on death.
cleanupP :: Logging a => Process a b () -> Process a b () -> Process a b () -> Process a b ()
cleanupP proc stopH cleanupH = do
st <- get
c <- ask
(a, s') <- liftIO $ runP c st proc `catches`
[ Handler (\ThreadKilled ->
runP c st ( do infoP $ "Process Terminated by Supervisor"
cleanupH ))
, Handler (\StopException ->
runP c st (do infoP $ "Process Terminating gracefully"
cleanupH >> stopH)) -- This one is ok
, Handler (\(ex :: SomeException) ->
runP c st (do criticalP $ "Process exiting due to ex: " ++ show ex
cleanupH >> stopH))
]
put s'
return a
------ LOGGING
--
-- | The class of types where we have a logger inside them somewhere
class Logging a where
-- | Returns a channel for logging and an Identifying string to use
logName :: a -> String
logP :: Logging a => Priority -> String -> Process a b ()
logP prio msg = do
n <- asks logName
liftIO $ logM n prio (n ++ ":\t" ++ msg)
infoP, debugP, criticalP, warningP, errorP :: Logging a => String -> Process a b ()
infoP = logP INFO
#ifdef NDEBUG
debugP _ = return ()
#else
debugP = logP DEBUG
#endif
criticalP = logP CRITICAL
warningP = logP WARNING
errorP = logP ERROR