transient 0.4.4.1 → 0.5.1
raw patch · 12 files changed
+2170/−1792 lines, 12 filesdep +bytestringsetup-changedPVP ok
version bump matches the API change (PVP)
Dependencies added: bytestring
API changes (from Hackage documentation)
- Transient.Backtrack: instance GHC.Show.Show Transient.Backtrack.FinishReason
- Transient.Backtrack: killOnFinish :: TransIO b -> TransIO b
- Transient.Internals: (!>) :: Show a => b -> a -> b
- Transient.Internals: exit' :: Typeable * a => a -> TransIO b
- Transient.Internals: forkFinally1 :: IO a -> (Either SomeException a -> IO ()) -> IO ThreadId
- Transient.Internals: free :: ThreadId -> EventF -> IO (Maybe EventF)
- Transient.Internals: infixr 0 !>
- Transient.Internals: type EventSetter eventdata response = (eventdata -> IO response) -> IO ()
- Transient.Internals: type ToReturn response = IO response
- Transient.Internals: waitEvents' :: IO b -> TransIO b
- Transient.Logged: instance (GHC.Show.Show a, GHC.Read.Read a, Data.Typeable.Internal.Typeable a) => Transient.Logged.Loggable a
- Transient.Stream.Resource: finish :: Maybe SomeException -> TransIO a
- Transient.Stream.Resource: initFinish :: TransientIO ()
- Transient.Stream.Resource: onFinish :: ((Maybe SomeException) -> TransIO ()) -> TransIO ()
- Transient.Stream.Resource: process :: TransIO a -> IO handle -> (handle -> Maybe SomeException -> IO ()) -> (handle -> a -> TransIO (StreamData b)) -> TransIO b
- Transient.Stream.Resource: sinkFile :: TransIO String -> String -> TransIO ()
- Transient.Stream.Resource: sourceFile :: String -> TransIO String
+ Transient.Base: continue :: TransIO ()
+ Transient.Base: cutExceptions :: TransientIO ()
+ Transient.Base: delState :: (MonadState EventF m, Typeable a) => a -> m ()
+ Transient.Base: getState :: Typeable a => TransIO a
+ Transient.Base: modifyData :: (MonadState EventF m, Typeable a) => (Maybe a -> Maybe a) -> m ()
+ Transient.Base: modifyState :: (MonadState EventF m, Typeable a) => (Maybe a -> Maybe a) -> m ()
+ Transient.Base: onException :: Exception e => (e -> TransIO ()) -> TransIO ()
+ Transient.Base: setState :: (MonadState EventF m, Typeable a) => a -> m ()
+ Transient.Internals: Alive :: LifeCycle
+ Transient.Internals: Backtrack :: Maybe b -> [EventF] -> Backtrack b
+ Transient.Internals: Dead :: LifeCycle
+ Transient.Internals: FinishReason :: (Maybe SomeException) -> FinishReason
+ Transient.Internals: Listener :: LifeCycle
+ Transient.Internals: Parent :: LifeCycle
+ Transient.Internals: [backStack] :: Backtrack b -> [EventF]
+ Transient.Internals: [backtracking] :: Backtrack b -> Maybe b
+ Transient.Internals: [labelth] :: EventF -> IORef (LifeCycle, ByteString)
+ Transient.Internals: back :: (Typeable b, Show b) => b -> TransientIO a
+ Transient.Internals: backCut :: (Typeable reason, Show reason) => reason -> TransientIO ()
+ Transient.Internals: backStateOf :: (Monad m, Show a, Typeable a) => a -> m (Backtrack a)
+ Transient.Internals: checkFinalize :: StreamData a -> TransIO a
+ Transient.Internals: class (Show a, Read a, Typeable a) => Loggable a
+ Transient.Internals: continue :: TransIO ()
+ Transient.Internals: cutExceptions :: TransientIO ()
+ Transient.Internals: data Backtrack b
+ Transient.Internals: data LifeCycle
+ Transient.Internals: delState :: (MonadState EventF m, Typeable a) => a -> m ()
+ Transient.Internals: finish :: Maybe SomeException -> TransIO a
+ Transient.Internals: forward :: (Typeable b, Show b) => b -> TransIO ()
+ Transient.Internals: getState :: Typeable a => TransIO a
+ Transient.Internals: initFinish :: TransientIO ()
+ Transient.Internals: instance (GHC.Num.Num a, GHC.Classes.Eq a) => GHC.Num.Num (Transient.Internals.TransIO a)
+ Transient.Internals: instance (GHC.Show.Show a, GHC.Read.Read a, Data.Typeable.Internal.Typeable a) => Transient.Internals.Loggable a
+ Transient.Internals: instance GHC.Classes.Eq Transient.Internals.LifeCycle
+ Transient.Internals: instance GHC.Show.Show Transient.Internals.FinishReason
+ Transient.Internals: instance GHC.Show.Show Transient.Internals.LifeCycle
+ Transient.Internals: instance GHC.Show.Show Transient.Internals.Log
+ Transient.Internals: killBranch :: TransIO ()
+ Transient.Internals: killBranch' :: MonadIO m => EventF -> m ()
+ Transient.Internals: labelState :: (MonadIO m, MonadState EventF m) => String -> m ()
+ Transient.Internals: modifyData :: (MonadState EventF m, Typeable a) => (Maybe a -> Maybe a) -> m ()
+ Transient.Internals: modifyState :: (MonadState EventF m, Typeable a) => (Maybe a -> Maybe a) -> m ()
+ Transient.Internals: newtype FinishReason
+ Transient.Internals: noFinish :: TransIO ()
+ Transient.Internals: noTrans :: StateT EventF IO x -> TransIO x
+ Transient.Internals: onBack :: (Typeable b, Show b) => TransientIO a -> (b -> TransientIO a) -> TransientIO a
+ Transient.Internals: onException :: Exception e => (e -> TransIO ()) -> TransIO ()
+ Transient.Internals: onFinish :: ((Maybe SomeException) -> TransIO ()) -> TransIO ()
+ Transient.Internals: onFinish' :: TransIO a -> ((Maybe SomeException) -> TransIO a) -> TransIO a
+ Transient.Internals: onUndo :: TransientIO a -> TransientIO a -> TransientIO a
+ Transient.Internals: printBlock :: MVar ()
+ Transient.Internals: registerBack :: (Typeable b, Show b) => b -> TransientIO a -> TransientIO a
+ Transient.Internals: registerUndo :: TransientIO a -> TransientIO a
+ Transient.Internals: retry :: TransIO ()
+ Transient.Internals: setState :: (MonadState EventF m, Typeable a) => a -> m ()
+ Transient.Internals: showState :: (Typeable a, MonadIO m, Alternative m) => String -> EventF -> m (Maybe a)
+ Transient.Internals: sync :: TransIO a -> TransIO a
+ Transient.Internals: topState :: TransIO EventF
+ Transient.Internals: undo :: TransIO a
+ Transient.Internals: undoCut :: TransientIO ()
+ Transient.Logged: param :: Loggable a => TransIO a
+ Transient.Logged: received :: Loggable a => a -> TransIO ()
- Transient.Base: keep :: Typeable a => TransIO a -> IO a
+ Transient.Base: keep :: Typeable a => TransIO a -> IO (Maybe a)
- Transient.Base: keep' :: Typeable a => TransIO a -> IO a
+ Transient.Base: keep' :: Typeable a => TransIO a -> IO (Maybe a)
- Transient.Base: killChilds :: TransientIO ()
+ Transient.Base: killChilds :: TransIO ()
- Transient.Base: oneThread :: TransIO a -> TransientIO a
+ Transient.Base: oneThread :: TransIO a -> TransIO a
- Transient.Base: react :: Typeable eventdata => EventSetter eventdata response -> ToReturn response -> TransIO eventdata
+ Transient.Base: react :: Typeable eventdata => ((eventdata -> IO response) -> IO ()) -> IO response -> TransIO eventdata
- Transient.Base: spawn :: IO b -> TransIO b
+ Transient.Base: spawn :: IO a -> TransIO a
- Transient.Indeterminism: collect' :: Int -> NominalDiffTime -> NominalDiffTime -> TransIO a -> TransIO [a]
+ Transient.Indeterminism: collect' :: Int -> Int -> TransIO a -> TransIO [a]
- Transient.Internals: EventF :: Effects -> Maybe SData -> TransIO a -> [b -> TransIO b] -> Map TypeRep SData -> Int -> ThreadId -> Bool -> Maybe EventF -> TVar [EventF] -> Maybe (IORef Int) -> EventF
+ Transient.Internals: EventF :: () -> Maybe SData -> TransIO a -> [b -> TransIO b] -> Map TypeRep SData -> Int -> ThreadId -> Bool -> Maybe EventF -> MVar [EventF] -> Maybe (IORef Int) -> IORef (LifeCycle, ByteString) -> EventF
- Transient.Internals: [children] :: EventF -> TVar [EventF]
+ Transient.Internals: [children] :: EventF -> MVar [EventF]
- Transient.Internals: [meffects] :: EventF -> Effects
+ Transient.Internals: [meffects] :: EventF -> ()
- Transient.Internals: compose :: (Alternative f, Monad f) => [t -> f t] -> t -> f a
+ Transient.Internals: compose :: (Monad f, Alternative f) => [a1 -> f a1] -> a1 -> f a
- Transient.Internals: getLine' :: (Typeable * a, Read a) => (a -> Bool) -> IO a
+ Transient.Internals: getLine' :: (Read a, Typeable * a) => (a -> Bool) -> IO a
- Transient.Internals: keep :: Typeable a => TransIO a -> IO a
+ Transient.Internals: keep :: Typeable a => TransIO a -> IO (Maybe a)
- Transient.Internals: keep' :: Typeable a => TransIO a -> IO a
+ Transient.Internals: keep' :: Typeable a => TransIO a -> IO (Maybe a)
- Transient.Internals: killChildren :: TVar [EventF] -> IO ()
+ Transient.Internals: killChildren :: MVar [EventF] -> IO ()
- Transient.Internals: killChilds :: TransientIO ()
+ Transient.Internals: killChilds :: TransIO ()
- Transient.Internals: oneThread :: TransIO a -> TransientIO a
+ Transient.Internals: oneThread :: TransIO a -> TransIO a
- Transient.Internals: processLine :: String -> IO ()
+ Transient.Internals: processLine :: MonadIO m => String -> m ()
- Transient.Internals: react :: Typeable eventdata => EventSetter eventdata response -> ToReturn response -> TransIO eventdata
+ Transient.Internals: react :: Typeable eventdata => ((eventdata -> IO response) -> IO ()) -> IO response -> TransIO eventdata
- Transient.Internals: readWithErr :: (Typeable * a, Read a) => [Char] -> IO [(a, String)]
+ Transient.Internals: readWithErr :: (Read a, Typeable * a) => String -> IO [(a, String)]
- Transient.Internals: reads1 :: (Typeable * a, Read a) => [Char] -> [(a, String)]
+ Transient.Internals: reads1 :: (Typeable * a, Read a) => String -> [(a, String)]
- Transient.Internals: readsPrec' :: (Typeable * a, Read a) => t -> [Char] -> [(a, String)]
+ Transient.Internals: readsPrec' :: (Typeable * a, Read a) => t -> String -> [(a, String)]
- Transient.Internals: showThreads :: TransIO empty
+ Transient.Internals: showThreads :: MonadIO m => EventF -> m ()
- Transient.Internals: spawn :: IO b -> TransIO b
+ Transient.Internals: spawn :: IO a -> TransIO a
- Transient.Internals: stay :: MVar (Either String (Maybe b)) -> IO b
+ Transient.Internals: stay :: MVar (Maybe a) -> IO (Maybe a)
- Transient.Internals: waitQSemB :: (Ord a, Num a) => IORef a -> IO Bool
+ Transient.Internals: waitQSemB :: (Num a, Ord a) => IORef a -> IO Bool
Files
- ChangeLog.md +0/−0
- LICENSE +17/−17
- README.md +45/−0
- Setup.hs +2/−2
- src/Transient/Backtrack.hs +141/−139
- src/Transient/Base.hs +49/−35
- src/Transient/EVars.hs +84/−86
- src/Transient/Indeterminism.hs +132/−137
- src/Transient/Internals.hs +1418/−1070
- src/Transient/Logged.hs +227/−179
- src/Transient/Stream/Resource.hs +0/−76
- transient.cabal +55/−51
+ ChangeLog.md view
LICENSE view
@@ -1,18 +1,18 @@-Copyright © 2014-2016 Alberto G. Corona <https://github.com/agocorona>--Permission is hereby granted, free of charge, to any person obtaining a copy of-this software and associated documentation files (the "Software"), to deal in-the Software without restriction, including without limitation the rights to-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of-the Software, and to permit persons to whom the Software is furnished to do so,-subject to the following conditions:--The above copyright notice and this permission notice shall be included in all-copies or substantial portions of the Software.--THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS-FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR-COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER-IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN+Copyright © 2014-2016 Alberto G. Corona <https://github.com/agocorona> + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ README.md view
@@ -0,0 +1,45 @@+ + +NOTE: distributed computing and web primitives have been moved to [transient-universe](https://github.com/agocorona/transient-universe) and [ghcjs-hplay](https://github.com/agocorona/ghcjs-hplay) + +transient +========= + +One of the biggest dreams of software engineering is unrestricted composability. + +This may be put in these terms: + +let `ap1` and `ap2` two applications with arbitrary complexity, with all effects including multiple threads, asynchronous IO, indeterminism, events and perhaps, distributed computing. + +Then the combinations: + + - ap1 <|> ap2 -- Alternative expression + - ap1 >>= \x -> ap2 -- monadic sequence + - ap1 <> ap2 -- monoidal expression + - (,) <$> ap1 <*> ap2 -- Applicative expression + +are possible if the types match, and generate new applications that are composable as well. + +Transient does exactly that. + +The operators `<|>` and `<>` can be used for concurrency, the operator `<|>` can be used for parallelism and `>>=` for sequencing of threads and/or distributed processes. So even in the presence of these effects and others, everything is composable. + +For this purpose transient is an extensible effects monad with all major effects and primitives for parallelism, events, asynchronous IO, early termination, non-determinism logging and distributed computing. Since it is possible to extend it with more effects without adding monad transformers, the composability is assured. + +Documentation +============= + +The [Wiki](https://github.com/agocorona/transient/wiki) is more user oriented + +My video sessions in [livecoding.tv](https://www.livecoding.tv/agocorona/videos/) not intended as tutorials or presentations, but show some of the latest features running. + +The articles are more technical: + +- [Philosophy, async, parallelism, thread control, events, Session state](https://www.fpcomplete.com/user/agocorona/EDSL-for-hard-working-IT-programmers?show=tutorials) +- [Backtracking and undoing IO transactions](https://www.fpcomplete.com/user/agocorona/the-hardworking-programmer-ii-practical-backtracking-to-undo-actions?show=tutorials) +- [Non-deterministic list like processing, multithreading](https://www.fpcomplete.com/user/agocorona/beautiful-parallel-non-determinism-transient-effects-iii?show=tutorials) +- [Distributed computing](https://www.fpcomplete.com/user/agocorona/moving-haskell-processes-between-nodes-transient-effects-iv?show=tutorials) +- [Publish-Subscribe variables](https://www.schoolofhaskell.com/user/agocorona/publish-subscribe-variables-transient-effects-v) +- [Distributed streaming, map-reduce](https://www.schoolofhaskell.com/user/agocorona/estimation-of-using-distributed-computing-streaming-transient-effects-vi-1) + +These articles contain executable examples (not now, since the site no longer support the execution of haskell snippets).
Setup.hs view
@@ -1,2 +1,2 @@-import Distribution.Simple-main = defaultMain+import Distribution.Simple +main = defaultMain
src/Transient/Backtrack.hs view
@@ -10,11 +10,11 @@ onBack, back, forward, backCut,registerBack, -- * finalization primitives -finish, onFinish, onFinish' ,initFinish , noFinish, killOnFinish ,checkFinalize , FinishReason +finish, onFinish, onFinish' ,initFinish , noFinish,checkFinalize , FinishReason ) where -import Transient.Base -import Transient.Internals(EventF(..),killChildren,onNothing,runClosure,runContinuation) +import Transient.Internals + import Data.Typeable import Control.Applicative import Control.Monad.State @@ -23,141 +23,143 @@ import Control.Exception import Control.Concurrent.STM hiding (retry) import Data.Maybe - -data Backtrack b= Show b =>Backtrack{backtracking :: Maybe b - ,backStack :: [EventF] } - deriving Typeable - --- | assures that backtracking will not go further back -backCut :: (Typeable reason, Show reason) => reason -> TransientIO () -backCut reason= Transient $ do - delData $ Backtrack (Just reason) [] - return $ Just () - -undoCut :: TransientIO () -undoCut = backCut () - --- | the second parameter will be executed when backtracking -{-# NOINLINE onBack #-} -onBack :: (Typeable b, Show b) => TransientIO a -> ( b -> TransientIO a) -> TransientIO a -onBack ac bac= registerBack (typeof bac) $ Transient $ do - Backtrack mreason _ <- getData `onNothing` backStateOf (typeof bac) - runTrans $ case mreason of - Nothing -> ac - Just reason -> bac reason - where - typeof :: (b -> TransIO a) -> b - typeof = undefined - -onUndo :: TransientIO a -> TransientIO a -> TransientIO a -onUndo x y= onBack x (\() -> y) - - --- | register an action that will be executed when backtracking -{-# NOINLINE registerUndo #-} -registerBack :: (Typeable b, Show b) => b -> TransientIO a -> TransientIO a -registerBack witness f = Transient $ do - cont@(EventF _ _ x _ _ _ _ _ _ _ _) <- get -- !!> "backregister" - - md <- getData `asTypeOf` (Just <$> backStateOf witness) - - case md of - Just (bss@(Backtrack b (bs@((EventF _ _ x' _ _ _ _ _ _ _ _):_)))) -> - when (isNothing b) $ do - addrx <- addr x - addrx' <- addr x' -- to avoid duplicate backtracking points - setData $ if addrx == addrx' then bss else Backtrack mwit (cont:bs) - Nothing -> setData $ Backtrack mwit [cont] - - runTrans f - where - mwit= Nothing `asTypeOf` (Just witness) - addr x = liftIO $ return . hashStableName =<< (makeStableName $! x) - - -registerUndo :: TransientIO a -> TransientIO a -registerUndo f= registerBack () f - --- | restart the flow forward from this point on -forward :: (Typeable b, Show b) => b -> TransIO () -forward reason= Transient $ do - Backtrack _ stack <- getData `onNothing` (backStateOf reason) - setData $ Backtrack(Nothing `asTypeOf` Just reason) stack - return $ Just () - -retry= forward () - -noFinish= forward (FinishReason Nothing) - --- | execute backtracking. It execute the registered actions in reverse order. -- --- If the backtracking flag is changed the flow proceed forward from that point on. +--data Backtrack b= Show b =>Backtrack{backtracking :: Maybe b +-- ,backStack :: [EventF] } +-- deriving Typeable -- --- If the backtrack stack is finished or undoCut executed, `undo` will stop. -back :: (Typeable b, Show b) => b -> TransientIO a -back reason = Transient $ do - bs <- getData `onNothing` backStateOf reason -- !!>"GOBACK" - goBackt bs - - where - - goBackt (Backtrack _ [] )= return Nothing -- !!> "END" - goBackt (Backtrack b (stack@(first : bs)) )= do - (setData $ Backtrack (Just reason) stack) - - mr <- runClosure first -- !> "RUNCLOSURE" - - Backtrack back _ <- getData `onNothing` backStateOf reason - -- !> "END RUNCLOSURE" - case back of - Nothing -> case mr of - Nothing -> return empty -- !> "FORWARD END" - Just x -> runContinuation first x -- !> "FORWARD EXEC" - justreason -> goBackt $ Backtrack justreason bs -- !> ("BACK AGAIN",back) - -backStateOf :: (Monad m, Show a, Typeable a) => a -> m (Backtrack a) -backStateOf reason= return $ Backtrack (Nothing `asTypeOf` (Just reason)) [] - -undo :: TransIO a -undo= back () - ------- finalization - -newtype FinishReason= FinishReason (Maybe SomeException) deriving (Typeable, Show) - --- | initialize the event variable for finalization. --- all the following computations in different threads will share it --- it also isolate this event from other branches that may have his own finish variable -initFinish= backCut (FinishReason Nothing) - --- | set a computation to be called when the finish event happens -onFinish :: ((Maybe SomeException) ->TransIO ()) -> TransIO () -onFinish f= onFinish' (return ()) f - - --- | set a computation to be called when the finish event happens this only apply for -onFinish' ::TransIO a ->((Maybe SomeException) ->TransIO a) -> TransIO a -onFinish' proc f= proc `onBack` \(FinishReason reason) -> - f reason - - --- | trigger the event, so this closes all the resources -finish :: Maybe SomeException -> TransIO a -finish reason= back (FinishReason reason) - - --- | kill all the processes generated by the parameter when finish event occurs -killOnFinish comp= do - chs <- liftIO $ newTVarIO [] - onFinish $ const $ liftIO $ killChildren chs -- !> "killOnFinish event" - r <- comp - modify $ \ s -> s{children= chs} - return r - --- | trigger finish when the stream of data ends -checkFinalize v= - case v of - SDone -> finish Nothing >> stop - SLast x -> return x - SError e -> liftIO ( print e) >> finish Nothing >> stop - SMore x -> return x +-- +-- +---- | assures that backtracking will not go further back +--backCut :: (Typeable reason, Show reason) => reason -> TransientIO () +--backCut reason= Transient $ do +-- delData $ Backtrack (Just reason) [] +-- return $ Just () +-- +--undoCut :: TransientIO () +--undoCut = backCut () +-- +---- | the second parameter will be executed when backtracking +--{-# NOINLINE onBack #-} +--onBack :: (Typeable b, Show b) => TransientIO a -> ( b -> TransientIO a) -> TransientIO a +--onBack ac bac= registerBack (typeof bac) $ Transient $ do +-- Backtrack mreason _ <- getData `onNothing` backStateOf (typeof bac) +-- runTrans $ case mreason of +-- Nothing -> ac +-- Just reason -> bac reason +-- where +-- typeof :: (b -> TransIO a) -> b +-- typeof = undefined +-- +--onUndo :: TransientIO a -> TransientIO a -> TransientIO a +--onUndo x y= onBack x (\() -> y) +-- +-- +---- | register an action that will be executed when backtracking +--{-# NOINLINE registerUndo #-} +--registerBack :: (Typeable b, Show b) => b -> TransientIO a -> TransientIO a +--registerBack witness f = Transient $ do +-- cont@(EventF _ _ x _ _ _ _ _ _ _ _) <- get -- !!> "backregister" +-- +-- md <- getData `asTypeOf` (Just <$> backStateOf witness) +-- +-- case md of +-- Just (bss@(Backtrack b (bs@((EventF _ _ x' _ _ _ _ _ _ _ _):_)))) -> +-- when (isNothing b) $ do +-- addrx <- addr x +-- addrx' <- addr x' -- to avoid duplicate backtracking points +-- setData $ if addrx == addrx' then bss else Backtrack mwit (cont:bs) +-- Nothing -> setData $ Backtrack mwit [cont] +-- +-- runTrans f +-- where +-- mwit= Nothing `asTypeOf` (Just witness) +-- addr x = liftIO $ return . hashStableName =<< (makeStableName $! x) +-- +-- +--registerUndo :: TransientIO a -> TransientIO a +--registerUndo f= registerBack () f +-- +---- | restart the flow forward from this point on +--forward :: (Typeable b, Show b) => b -> TransIO () +--forward reason= Transient $ do +-- Backtrack _ stack <- getData `onNothing` (backStateOf reason) +-- setData $ Backtrack(Nothing `asTypeOf` Just reason) stack +-- return $ Just () +-- +--retry= forward () +-- +--noFinish= forward (FinishReason Nothing) +-- +---- | execute backtracking. It execute the registered actions in reverse order. +---- +---- If the backtracking flag is changed the flow proceed forward from that point on. +---- +---- If the backtrack stack is finished or undoCut executed, `undo` will stop. +--back :: (Typeable b, Show b) => b -> TransientIO a +--back reason = Transient $ do +-- bs <- getData `onNothing` backStateOf reason -- !!>"GOBACK" +-- goBackt bs +-- +-- where +-- +-- goBackt (Backtrack _ [] )= return Nothing -- !!> "END" +-- goBackt (Backtrack b (stack@(first : bs)) )= do +-- (setData $ Backtrack (Just reason) stack) +-- +-- mr <- runClosure first -- !> "RUNCLOSURE" +-- +-- Backtrack back _ <- getData `onNothing` backStateOf reason +-- -- !> "END RUNCLOSURE" +-- case back of +-- Nothing -> case mr of +-- Nothing -> return empty -- !> "FORWARD END" +-- Just x -> runContinuation first x -- !> "FORWARD EXEC" +-- justreason -> goBackt $ Backtrack justreason bs -- !> ("BACK AGAIN",back) +-- +--backStateOf :: (Monad m, Show a, Typeable a) => a -> m (Backtrack a) +--backStateOf reason= return $ Backtrack (Nothing `asTypeOf` (Just reason)) [] +-- +--undo :: TransIO a +--undo= back () +-- +-------- finalization +-- +--newtype FinishReason= FinishReason (Maybe SomeException) deriving (Typeable, Show) +-- +---- | initialize the event variable for finalization. +---- all the following computations in different threads will share it +---- it also isolate this event from other branches that may have his own finish variable +--initFinish= backCut (FinishReason Nothing) +-- +---- | set a computation to be called when the finish event happens +--onFinish :: ((Maybe SomeException) ->TransIO ()) -> TransIO () +--onFinish f= onFinish' (return ()) f +-- +-- +---- | set a computation to be called when the finish event happens this only apply for +--onFinish' ::TransIO a ->((Maybe SomeException) ->TransIO a) -> TransIO a +--onFinish' proc f= proc `onBack` \(FinishReason reason) -> +-- f reason +-- +-- +---- | trigger the event, so this closes all the resources +--finish :: Maybe SomeException -> TransIO a +--finish reason= back (FinishReason reason) +-- +-- +---- | kill all the processes generated by the parameter when finish event occurs +--killOnFinish comp= do +-- chs <- liftIO $ newTVarIO [] +-- onFinish $ const $ liftIO $ killChildren chs -- !> "killOnFinish event" +-- r <- comp +-- modify $ \ s -> s{children= chs} +-- return r +-- +---- | trigger finish when the stream of data ends +--checkFinalize v= +-- case v of +-- SDone -> finish Nothing >> stop +-- SLast x -> return x +-- SError e -> liftIO ( print e) >> finish Nothing >> stop +-- SMore x -> return x
src/Transient/Base.hs view
@@ -1,35 +1,49 @@-{-# LANGUAGE ScopedTypeVariables #-}------------------------------------------------------------------------------------ Module : Base--- Copyright :--- License : GPL (Just (Version {versionBranch = [3], versionTags = []}))------ Maintainer : agocorona@gmail.com--- Stability :--- Portability :------ | See http://github.com/agocorona/transient--------------------------------------------------------------------------------module Transient.Base(--TransIO(..), TransientIO-,keep, keep', stop-,option, input, exit-,async,waitEvents, spawn, parallel, sample-,react--,setData,getData,getSData,delData--, threads,addThreads, freeThreads, hookedThreads,oneThread, killChilds--, (**>), (<**),(<***), (<|)--, StreamData(..)-,genId)--where---import Transient.Internals+{-# LANGUAGE ScopedTypeVariables #-} +----------------------------------------------------------------------------- +-- +-- Module : Base +-- Copyright : +-- License : GPL (Just (Version {versionBranch = [3], versionTags = []})) +-- +-- Maintainer : agocorona@gmail.com +-- Stability : +-- Portability : +-- +-- | See http://github.com/agocorona/transient +----------------------------------------------------------------------------- + +module Transient.Base( +-- * The Monad +TransIO(..), TransientIO +-- * Running the monad +,keep, keep', stop + +-- * input +,option, input, exit + +-- * Asynchronous operations +,async,waitEvents, spawn, parallel, sample +,react + +-- * State management +,setState, setData, getState, getSData,getData,delState,delData, modifyData,modifyState + +-- * Thread management +, threads,addThreads, freeThreads, hookedThreads,oneThread, killChilds + +-- * Additional operators +, (**>), (<**),(<***), (<|) + +-- * exceptions + +,onException, cutExceptions, continue + +-- * Utilities +, StreamData(..) +,genId +) + +where + + +import Transient.Internals
src/Transient/EVars.hs view
@@ -1,86 +1,84 @@-{-# LANGUAGE DeriveDataTypeable #-}-module Transient.EVars where--import Transient.Base-import Transient.Internals(runTransState,onNothing, EventF(..), killChildren)-import qualified Data.Map as M-import Data.Typeable--import Control.Concurrent-import Control.Applicative-import Control.Concurrent.STM-import Control.Monad.IO.Class-import Control.Exception(SomeException)--import Data.List(nub)-import Control.Monad.State-import Data.IORef----data EVar a= EVar (TChan (StreamData a)) deriving Typeable----- | creates an EVar.------ Evars are event vars. `writeEVar` trigger the execution of all the continuations associated to the `readEVar` of this variable--- (the code that is after them).------ It is like the publish-subscribe pattern but without inversion of control, since a readEVar can be inserted at any place in the--- Transient flow.------ EVars are created upstream and can be used to communicate two sub-threads of the monad. Following the Transient philosophy they--- do not block his own thread if used with alternative operators, unlike the IORefs and TVars. And unlike STM vars, that are composable,--- they wait for their respective events, while TVars execute the whole expression when any variable is modified.------ The execution continues after the writeEVar when all subscribers have been executed.------ Now the continuations are executed in parallel.------ see https://www.fpcomplete.com/user/agocorona/publish-subscribe-variables-transient-effects-v-----newEVar :: TransIO (EVar a)-newEVar = Transient $ do- id <- genId- ref <-liftIO newBroadcastTChanIO- return . Just $ EVar ref---- | delete al the subscriptions for an evar.-cleanEVar :: EVar a -> TransIO ()-cleanEVar (EVar ref1)= liftIO $ atomically $ do- writeTChan ref1 SDone----- | read the EVar. It only succeed when the EVar is being updated--- The continuation gets registered to be executed whenever the variable is updated.------ if readEVar is re-executed in any kind of loop, since each continuation is different, this will register--- again. The effect is that the continuation will be executed multiple times--- To avoid multiple registrations, use `cleanEVar`-readEVar :: EVar a -> TransIO a-readEVar (EVar ref1)= do- tchan <- liftIO . atomically $ dupTChan ref1- r <- parallel $ atomically $ readTChan tchan-- case r of- SDone -> empty- SMore x -> return x- SLast x -> return x- SError e -> empty--- error $ "readEVar: "++ show e---- | update the EVar and execute all readEVar blocks with "last in-first out" priority----writeEVar (EVar ref1) x= liftIO $ atomically $ do- writeTChan ref1 $ SMore x----- | write the EVar and drop all the `readEVar` handlers.------ It is like a combination of `writeEVar` and `cleanEVar`-lastWriteEVar (EVar ref1) x= liftIO $ atomically $ do- writeTChan ref1 $ SLast x---+{-# LANGUAGE DeriveDataTypeable #-} +module Transient.EVars where + +import Transient.Base +import Transient.Internals(runTransState,onNothing, EventF(..), killChildren) +import qualified Data.Map as M +import Data.Typeable + +import Control.Concurrent +import Control.Applicative +import Control.Concurrent.STM +import Control.Monad.IO.Class +import Control.Exception(SomeException) + +import Data.List(nub) +import Control.Monad.State +import Data.IORef + + + +data EVar a= EVar (TChan (StreamData a)) deriving Typeable + + +-- | creates an EVar. +-- +-- Evars are event vars. `writeEVar` trigger the execution of all the continuations associated to the `readEVar` of this variable +-- (the code that is after them). +-- +-- It is like the publish-subscribe pattern but without inversion of control, since a readEVar can be inserted at any place in the +-- Transient flow. +-- +-- EVars are created upstream and can be used to communicate two sub-threads of the monad. Following the Transient philosophy they +-- do not block his own thread if used with alternative operators, unlike the IORefs and TVars. And unlike STM vars, that are composable, +-- they wait for their respective events, while TVars execute the whole expression when any variable is modified. +-- +-- The execution continues after the writeEVar when all subscribers have been executed. +-- +-- Now the continuations are executed in parallel. +-- +-- see https://www.fpcomplete.com/user/agocorona/publish-subscribe-variables-transient-effects-v +-- + +newEVar :: TransIO (EVar a) +newEVar = Transient $ do + ref <-liftIO newBroadcastTChanIO + return . Just $ EVar ref + +-- | delete al the subscriptions for an evar. +cleanEVar :: EVar a -> TransIO () +cleanEVar (EVar ref1)= liftIO $ atomically $ writeTChan ref1 SDone + + +-- | read the EVar. It only succeed when the EVar is being updated +-- The continuation gets registered to be executed whenever the variable is updated. +-- +-- if readEVar is re-executed in any kind of loop, since each continuation is different, this will register +-- again. The effect is that the continuation will be executed multiple times +-- To avoid multiple registrations, use `cleanEVar` +readEVar :: EVar a -> TransIO a +readEVar (EVar ref1)= do + tchan <- liftIO . atomically $ dupTChan ref1 + r <- parallel $ atomically $ readTChan tchan + + case r of + SDone -> empty + SMore x -> return x + SLast x -> return x + SError e -> empty +-- error $ "readEVar: "++ show e + +-- | update the EVar and execute all readEVar blocks with "last in-first out" priority +-- +writeEVar (EVar ref1) x= liftIO $ atomically $ do + writeTChan ref1 $ SMore x + + +-- | write the EVar and drop all the `readEVar` handlers. +-- +-- It is like a combination of `writeEVar` and `cleanEVar` +lastWriteEVar (EVar ref1) x= liftIO $ atomically $ do + writeTChan ref1 $ SLast x + + +
src/Transient/Indeterminism.hs view
@@ -1,137 +1,132 @@------------------------------------------------------------------------------------ Module : Transient.Indeterminism--- Copyright :--- License : GPL (Just (Version {versionBranch = [3], versionTags = []}))------ Maintainer : agocorona@gmail.com--- Stability :--- Portability :------ | see <https://www.fpcomplete.com/user/agocorona/beautiful-parallel-non-determinism-transient-effects-iii>----------------------------------------------------------------------------------{-# LANGUAGE BangPatterns #-}-module Transient.Indeterminism (-choose, choose', collect, collect', group, groupByTime-) where--import Transient.Base-import Transient.Backtrack(checkFinalize)-import Transient.Internals(killChildren, EventF(..),hangThread)-import Data.IORef-import Control.Applicative-import Data.Monoid-import Control.Concurrent-import Data.Typeable-import Control.Monad.State-import Control.Concurrent.STM as STM-import GHC.Conc-import Data.Time.Clock----- | slurp a list of values and process them in parallel . To limit the number of processing--- threads, use `threads`-choose :: Show a => [a] -> TransIO a-choose []= empty-choose xs = do- evs <- liftIO $ newIORef xs- r <- parallel $ do- es <- atomicModifyIORef' evs $ \es -> let !tes= tail es in (tes,es)- case es of- [x] -> x `seq` return $ SLast x- x:_ -> x `seq` return $ SMore x- checkFinalize r----- | group the output of a possible multithreaded process in groups of n elements.-group :: Int -> TransIO a -> TransIO [a]-group num proc = do- v <- liftIO $ newIORef (0,[])- x <- proc-- mn <- liftIO $ atomicModifyIORef' v $ \(n,xs) ->- let !n'=n +1- in if n'== num-- then ((0,[]), Just xs)- else ((n', x:xs),Nothing)- case mn of- Nothing -> stop- Just xs -> return xs---- | group result for a time interval, measured with `diffUTCTime`-groupByTime :: Integer -> TransIO a -> TransIO [a]--groupByTime time proc = do- v <- liftIO $ newIORef (0,[])- t <- liftIO getCurrentTime- x <- proc- t' <- liftIO getCurrentTime- mn <- liftIO $ atomicModifyIORef' v $ \(n,xs) -> let !n'=n +1- in- if diffUTCTime t' t < fromIntegral time- then ((n', x:xs),Nothing)- else ((0,[]), Just xs)- case mn of- Nothing -> stop- Just xs -> return xs---- | alternative definition with more parallelism, as the composition of n `async` sentences-choose' :: [a] -> TransIO a-choose' xs = foldl (<|>) empty $ map (async . return) xs----- collect the results of a search done in parallel, usually initiated by--- `choose` .------ execute a process and get at least the first n solutions (they could be more).--- if the process end without finding the number of solutions requested, it return the found ones--- if he find the number of solutions requested, it kill the non-free threads of the process and return--- It works monitoring the solutions found and the number of active threads.--- If the first parameter is 0, collect will return all the results-collect :: Int -> TransIO a -> TransIO [a]-collect n = collect' n 0.1 0---- | search also between two time intervals. If the first interval has passed and there is no result,---it stops.--- After the second interval, it stop unconditionally and return the current results.--- It also stops as soon as there are enough results specified in the first parameter.-collect' :: Int -> NominalDiffTime -> NominalDiffTime -> TransIO a -> TransIO [a]-collect' n t1 t2 search= hookedThreads $ do- rv <- liftIO $ atomically $ newTVar (0,[]) -- !> "NEWMVAR"- endflag <- liftIO $ newTVarIO False- st <- newPool- t <- liftIO getCurrentTime- let worker = do- r <- search- liftIO $ atomically $ do- (n1,rs) <- readTVar rv- writeTVar rv (n1+1,r:rs) -- !> "MODIFY"- stop-- monitor= freeThreads $ do- xs <- async $ atomically $- do (n', xs) <- readTVar rv- ns <- readTVar $ children st- t' <- unsafeIOToSTM getCurrentTime- if- (n > 0 && n' >= n) ||- (null ns && (diffUTCTime t' t > t1)) ||- (t2 > 0 && diffUTCTime t' t > t2)- -- !> (diffUTCTime t' t, n', length ns)- then return xs else retry--- liftIO . killChildren $ children st-- return xs-- monitor <|> worker- where- newPool = do- chs <- liftIO $ newTVarIO []- s <- get- let s'= s{children= chs}- put s'- return s'+----------------------------------------------------------------------------- +-- +-- Module : Transient.Indeterminism +-- Copyright : +-- License : GPL (Just (Version {versionBranch = [3], versionTags = []})) +-- +-- Maintainer : agocorona@gmail.com +-- Stability : +-- Portability : +-- +-- | see <https://www.fpcomplete.com/user/agocorona/beautiful-parallel-non-determinism-transient-effects-iii> +-- +----------------------------------------------------------------------------- +{-# LANGUAGE ScopedTypeVariables #-} +module Transient.Indeterminism ( +choose, choose', collect, collect', group, groupByTime +) where + +import Transient.Internals hiding (retry) + +import Data.IORef +import Control.Applicative +import Data.Monoid +import Control.Concurrent +import Data.Typeable +import Control.Monad.State +import GHC.Conc +import Data.Time.Clock +import Control.Exception + + +-- | slurp a list of values and process them in parallel . To limit the number of processing +-- threads, use `threads` +choose :: Show a => [a] -> TransIO a +choose []= empty +choose xs = do + evs <- liftIO $ newIORef xs + r <- parallel $ do + es <- atomicModifyIORef' evs $ \es -> let tes= tail es in (tes,es) + case es of + [x] -> x `seq` return $ SLast x + x:_ -> x `seq` return $ SMore x + checkFinalize r + +-- | alternative definition with more parallelism, as the composition of n `async` sentences +choose' :: [a] -> TransIO a +choose' xs = foldl (<|>) empty $ map (async . return) xs + + +-- | group the output of a possible multithreaded process in groups of n elements. +group :: Int -> TransIO a -> TransIO [a] +group num proc = do + v <- liftIO $ newIORef (0,[]) + x <- proc + + mn <- liftIO $ atomicModifyIORef' v $ \(n,xs) -> + let n'=n +1 + in if n'== num + + then ((0,[]), Just $ x:xs) + else ((n', x:xs),Nothing) + case mn of + Nothing -> stop + Just xs -> return xs + +-- | group result for a time interval, measured with `diffUTCTime` +groupByTime :: Integer -> TransIO a -> TransIO [a] + +groupByTime time proc = do + v <- liftIO $ newIORef (0,[]) + t <- liftIO getCurrentTime + x <- proc + t' <- liftIO getCurrentTime + mn <- liftIO $ atomicModifyIORef' v $ \(n,xs) -> let n'=n +1 + in + if diffUTCTime t' t < fromIntegral time + then ((n', x:xs),Nothing) + else ((0,[]), Just $ x:xs) + case mn of + Nothing -> stop + Just xs -> return xs + + + +-- collect the results of a search done in parallel, usually initiated by +-- `choose` . +-- +-- execute a process and get at least the first n solutions (they could be more). +-- if he find the number of solutions requested, it kill the non-free threads of the process and return +collect :: Int -> TransIO a -> TransIO [a] +collect n = collect' n 0 + +-- | search with a timeout +-- After the the timeout, it stop unconditionally and return the current results. +-- It also stops as soon as there are enough results specified in the first parameter. +collect' :: Int -> Int -> TransIO a -> TransIO [a] +collect' n t2 search= do + + rv <- liftIO $ newEmptyMVar -- !> "NEWMVAR" + + + results <- liftIO $ newIORef (0,[]) + + let worker = do + r <- search + liftIO $ putMVar rv r + stop + + timer= if t2 > 0 then async $ threadDelay t2 >> readIORef results >>= return . snd + else empty + + monitor= async loop + + where + loop = do + r <- takeMVar rv + (n',rs) <- readIORef results + let n''= n' +1 + writeIORef results (n'',r:rs) + + t' <- getCurrentTime + if (n > 0 && n'' >= n) + then readIORef results >>= return . snd else loop + `catch` \(e :: BlockedIndefinitelyOnMVar) -> + readIORef results >>= return . snd + + + r <- oneThread $ monitor <|> worker <|> timer + + return r + +
src/Transient/Internals.hs view
@@ -1,1070 +1,1418 @@-{-# LANGUAGE ScopedTypeVariables #-}------------------------------------------------------------------------------------ Module : Base--- Copyright :--- License : GPL (Just (Version {versionBranch = [3], versionTags = []}))------ Maintainer : agocorona@gmail.com--- Stability :--- Portability :------ | See http://github.com/agocorona/transient--- everithing in this module is exported in order to allow extensibility.-------------------------------------------------------------------------------{-# LANGUAGE ExistentialQuantification #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE Rank2Types #-}--- show-module Transient.Internals where--- /show--import Control.Applicative-import Control.Monad.State-import Data.Dynamic-import qualified Data.Map as M-import Data.Monoid-import Debug.Trace-import System.IO.Unsafe-import Unsafe.Coerce-import Control.Exception-import Control.Concurrent-import Control.Concurrent.STM-import System.Mem.StableName-import Data.Maybe-import GHC.Conc-import Data.List-import Data.IORef-import System.Environment-import System.IO (hFlush,stdout)-import System.Exit-{-# INLINE (!>) #-}-(!>) :: Show a => b -> a -> b-(!>) x y= trace (show y) x-infixr 0 !>---data TransIO x = Transient {runTrans :: StateT EventF IO (Maybe x)}-type SData= ()--type EventId= Int--type TransientIO= TransIO--data EventF = forall a b . EventF{meffects :: Effects- ,event :: Maybe SData- ,xcomp :: TransIO a- ,fcomp :: [b -> TransIO b]- ,mfData :: M.Map TypeRep SData- ,mfSequence :: Int- ,threadId :: ThreadId- ,freeTh :: Bool- ,parent :: Maybe EventF- ,children :: TVar[EventF]- ,maxThread :: Maybe (IORef Int)- }- deriving Typeable-----type Effects= forall a b c.TransIO a -> TransIO a -> (a -> TransIO b)- -> StateIO (StateIO (Maybe c) -> StateIO (Maybe c), Maybe a)-----instance MonadState EventF TransIO where- get = Transient $ get >>= return . Just- put x= Transient $ put x >> return (Just ())- state f = Transient $ do- s <- get- let ~(a, s') = f s- put s'- return $ Just a--type StateIO= StateT EventF IO----- | run the transient computation with a blank state-runTransient :: TransIO x -> IO (Maybe x, EventF)-runTransient t= do- th <- myThreadId- let eventf0= EventF baseEffects Nothing empty [] M.empty 0- th False Nothing (unsafePerformIO $ newTVarIO []) Nothing--- runStateT (runTrans t) eventf0---- | run the transient computation with an state-runTransState st x = runStateT (runTrans x) st---- | get the continuation context: closure, continuation, state, child threads etc-getCont :: TransIO EventF-getCont = Transient $ Just <$> get---- | run the closure and the continuation using the state data of the calling thread-runCont :: EventF -> StateIO (Maybe a)-runCont (EventF _ _ x fs _ _ _ _ _ _ _)= runTrans $ do- r <- unsafeCoerce x- compose fs r---- | run the closure and the continuation using his own state data-runCont' cont= runStateT (runCont cont) cont---- | warning: radiactive untyped stuff. handle with care-getContinuations :: StateIO [a -> TransIO b]-getContinuations= do- EventF _ _ _ fs _ _ _ _ _ _ _ <- get- return $ unsafeCoerce fs--{--runCont cont= do- mr <- runClosure cont- case mr of- Nothing -> return Nothing- Just r -> runContinuation cont r--}----- | compose a list of continuations-compose []= const empty-compose (f: fs)= \x -> f x >>= compose fs------ | run the closure (the 'x' in 'x >>= f') of the current bind operation.-runClosure :: EventF -> StateIO (Maybe a)-runClosure (EventF _ _ x _ _ _ _ _ _ _ _) = unsafeCoerce $ runTrans x----- | run the continuation (the 'f' in 'x >>= f') of the current bind operation-runContinuation :: EventF -> a -> StateIO (Maybe b)-runContinuation (EventF _ _ _ fs _ _ _ _ _ _ _) =- runTrans . (unsafeCoerce $ compose $ fs)---setContinuation :: TransIO a -> (a -> TransIO b) -> [c -> TransIO c] -> StateIO ()-setContinuation b c fs = do- (EventF eff ev _ _ d e f g h i j) <- get- put $ EventF eff ev b ( unsafeCoerce c: fs) d e f g h i j--withContinuation c mx= do- EventF eff ev f1 fs d e f g h i j <- get- put $ EventF eff ev mx ( unsafeCoerce c: fs) d e f g h i j- r <- mx- restoreStack fs- return r---- | run a chain of continuations. It is up to the programmer to assure by construction that--- each continuation type-check with the next, that the parameter type match the input of the first--- continuation.--- Normally this makes sense if it stop the current flow with `stop` after the invocation-runContinuations :: [a -> TransIO b] -> c -> TransIO d-runContinuations fs x= (compose $ unsafeCoerce fs) x--instance Functor TransIO where- fmap f mx= -- Transient $ fmap (fmap f) $ runTrans mx- do- x <- mx- return $ f x-----instance Applicative TransIO where- pure a = Transient . return $ Just a-- f <*> g = Transient $ do- rf <- liftIO $ newIORef (Nothing,[])- rg <- liftIO $ newIORef (Nothing,[]) -- !> "NEWIOREF"-- fs <- getContinuations-- let-- hasWait (_:Wait:_)= True- hasWait _ = False-- appf k = Transient $ do- Log rec _ full <- getData `onNothing` return (Log False [] [])- (liftIO $ writeIORef rf (Just k,full))--- !> ( show $ unsafePerformIO myThreadId) ++"APPF"- (x, full2)<- liftIO $ readIORef rg- when (hasWait full ) $- -- !> (hasWait full,"full",full, "\nfull2",full2)) $- let full'= head full: full2- in (setData $ Log rec full' full') -- !> ("result1",full')-- return $ Just k <*> x-- appg x = Transient $ do- Log rec _ full <- getData `onNothing` return (Log False [] [])- liftIO $ writeIORef rg (Just x, full)--- !> ( show $ unsafePerformIO myThreadId)++ "APPG"- (k,full1) <- liftIO $ readIORef rf- when (hasWait full) $- -- !> ("full", full, "\nfull1",full1)) $- let full'= head full: full1- in (setData $ Log rec full' full') -- !> ("result2",full')-- return $ k <*> Just x-- setContinuation f appf fs--- k <- runTrans f- -- !> ( show $ unsafePerformIO myThreadId)++ "RUN f"- was <- getData `onNothing` return NoRemote- when (was == WasParallel) $ setData NoRemote-- Log recovery _ full <- getData `onNothing` return (Log False [] [])---- if was== WasRemote || (not recovery && was == NoRemote && isNothing k )--- !> ("was,recovery,isNothing=",was,recovery, isNothing k)- -- if the first operand was a remote request- -- (so this node is not master and hasn't to execute the whole expression)- -- or it was not an asyncronous term (a normal term without async or parallel- -- like primitives) and is nothing- then do- restoreStack fs- return Nothing- else do- when (isJust k) $ liftIO $ writeIORef rf (k,full)- -- when necessary since it maybe WasParallel and Nothing-- setContinuation g appg fs-- x <- runTrans g- -- !> ( show $ unsafePerformIO myThreadId) ++ "RUN g"- Log recovery _ full' <- getData `onNothing` return (Log False [] [])- liftIO $ writeIORef rg (x,full')- restoreStack fs- k'' <- if was== WasParallel- then do- (k',_) <- liftIO $ readIORef rf -- since k may have been updated by a parallel f- return k'- else return k- return $ k'' <*> x--restoreStack fs=- modify $ \(EventF eff _ f _ a b c d parent children g1) ->- EventF eff Nothing f fs a b c d parent children g1--readWithErr line=- let [(v,left)] = readsPrec 0 line- in (v `seq` return [(v,left)])- `catch` (\(e::SomeException) ->- error ("read error of " ++ show( typeOf v) ++ " in: "++ "\""++line++"\""))---readsPrec' _= unsafePerformIO . readWithErr------ | dynamic serializable data for logging-data IDynamic= IDyns String | forall a.(Read a, Show a,Typeable a) => IDynamic a--instance Show IDynamic where- show (IDynamic x)= show $ show x- show (IDyns s)= show s--instance Read IDynamic where- readsPrec n str= map (\(x,s) -> (IDyns x,s)) $ readsPrec' n str---type Recover= Bool-type CurrentPointer= [LogElem]-type LogEntries= [LogElem]-data LogElem= Wait | Exec | Var IDynamic deriving (Read,Show)-data Log= Log Recover CurrentPointer LogEntries deriving Typeable---instance Alternative TransIO where- empty = Transient $ return Nothing- (<|>) = mplus---data RemoteStatus= WasRemote | WasParallel | NoRemote deriving (Typeable, Eq, Show)--instance MonadPlus TransIO where- mzero= empty- mplus x y= Transient $ do- mx <- runTrans x -- !!> "RUNTRANS11111"- was <- getData `onNothing` return NoRemote- if was== WasRemote -- !> was- then return Nothing- else- case mx of- Nothing -> runTrans y -- !!> "RUNTRANS22222"- justx -> return justx---- | a sinonym of empty that can be used in a monadic expression. it stop the--- computation and execute the next alternative computation (composed with `<|>`)-stop :: Alternative m => m stopped-stop= empty--class AdditionalOperators m where-- -- | executes the second operand even if the frist return empty.- -- A normal imperative (monadic) sequence uses the operator (>>) which in the- -- Transient monad does not execute the next operand if the previous one return empty.- (**>) :: m a -> m b -> m b-- -- | forces the execution of the second operand even if the first stop. It does not execute- -- the second operand as result of internal events occuring in the first operand.- -- Return the first result- (<**) :: m a -> m b -> m a-- atEnd' ::m a -> m b -> m a- atEnd' = (<**)-- -- | forces the execution of the second operand even if the first stop. Return the first result. The second- -- operand is executed also when internal events happens in the first operand and it returns something- (<***) :: m a -> m b -> m a-- atEnd :: m a -> m b -> m a- atEnd= (<***)---instance AdditionalOperators TransIO where---- (**>) :: TransIO a -> TransIO b -> TransIO b- (**>) x y= Transient $ do- runTrans x- runTrans y---- (<***) :: TransIO a -> TransIO b -> TransIO a- (<***) ma mb= Transient $ do- fs <- getContinuations- setContinuation ma (\x -> mb >> return x) fs- a <- runTrans ma- runTrans mb- restoreStack fs- return a---- (<**) :: TransIO a -> TransIO b -> TransIO a- (<**) ma mb= Transient $ do- a <- runTrans ma -- !> "ma"- runTrans mb -- !> "mb"- return a--infixr 1 <*** , <**, **>------ | when the first operand is an asynchronous operation, the second operand is executed once (one single time)--- when the first completes his first asyncronous operation.------ This is useful for spawning asynchronous or distributed tasks that are singletons and that should start--- when the first one is set up.------ for example a streaming where the event receivers are acivated before the senders.--(<|) :: TransIO a -> TransIO b -> TransIO a-(<|) ma mb = Transient $ do- fs <- getContinuations- ref <- liftIO $ newIORef False- setContinuation ma (cont ref ) fs- r <- runTrans ma- restoreStack fs- return r- where- cont ref x= Transient $ do- n <- liftIO $ readIORef ref- if n == True- then return $ Just x- else do liftIO $ writeIORef ref True- runTrans mb- return $ Just x--instance Monoid a => Monoid (TransIO a) where- mappend x y = mappend <$> x <*> y- mempty= return mempty---- | set the current closure and continuation for the current statement-setEventCont :: TransIO a -> (a -> TransIO b) -> StateIO EventF-setEventCont x f = do-- st@(EventF eff e _ fs d n r applic ch rc bs) <- get -- !> "SET"- let cont= EventF eff e x ( unsafeCoerce f : fs) d n r applic ch rc bs- put cont- return cont---- | reset the closure and continuation. remove inner binds than the previous computations may have stacked--- in the list of continuations.---resetEventCont :: Maybe a -> EventF -> StateIO (TransIO b -> TransIO b)-resetEventCont mx _=do- st@(EventF eff e _ fs d n r nr ch rc bs) <- get -- !> "reset"- let f= \mx -> case mx of- Nothing -> empty- Just x -> (unsafeCoerce $ head fs) x- put $ EventF eff e (f mx) ( tailsafe fs) d n r nr ch rc bs- return id--tailsafe []=[]-tailsafe (x:xs)= xs----refEventCont= unsafePerformIO $ newIORef baseEffects--{-# INLINE baseEffects #-}-baseEffects :: Effects--baseEffects x x' f' = do- c <-setEventCont x' f'- mk <- runTrans x- t <- resetEventCont mk c- return (t,mk)--instance Monad TransIO where-- return = pure-- x >>= f = Transient $ do--- effects <- gets effects -- liftIO $ readIORef refEventCont- (t,mk) <- baseEffects x x f- t $ case mk of- Just k -> runTrans (f k)-- Nothing -> return Nothing----instance MonadTrans (Transient ) where--- lift mx = Transient $ mx >>= return . Just--instance MonadIO TransIO where- liftIO x = Transient $ liftIO x >>= return . Just -- let x= liftIO io in x `seq` lift x----- * Threads--waitQSemB sem= atomicModifyIORef sem $ \n -> if n > 0 then(n-1,True) else (n,False)-signalQSemB sem= atomicModifyIORef sem $ \n -> (n + 1,())---- | set the maximun number of threads for a procedure. It is useful to limit the--- parallelization of transient code that uses `parallel` `spawn` and `waitEvents`-threads :: Int -> TransIO a -> TransIO a-threads n proc= Transient $ do- msem <- gets maxThread- sem <- liftIO $ newIORef n- modify $ \s -> s{maxThread= Just sem}- r <- runTrans proc- modify $ \s -> s{maxThread = msem} -- restore it- return r---- | delete all the previous childs generated by the expression taken as parameter and continue execution--- of the current thread.-oneThread :: TransIO a -> TransientIO a-oneThread comp= do- chs <- liftIO $ newTVarIO []- r <- comp- modify $ \ s -> s{children= chs}- killChilds- return r---showThreads :: TransIO empty-showThreads= do- st' <- gets (fromJust . parent)- liftIO $ showTree 0 st'- stop- where- toplevel st =- case parent st of- Nothing -> st- Just p -> toplevel p-- showThreads' n rchs= do- chs <- atomically $ readTVar rchs- mapM_ (showTree n) chs-- showTree n ch= do- putStr $ take n $ repeat ' '- print $ threadId ch- showThreads' (n+4) $ children ch----- | add n threads to the limit of threads. If there is no limit, it set it-addThreads' :: Int -> TransIO ()-addThreads' n= Transient $ do- msem <- gets maxThread- case msem of- Just sem -> liftIO $ modifyIORef sem $ \n' -> n + n'- Nothing -> do- sem <- liftIO (newIORef n)- modify $ \ s -> s{maxThread= Just sem}- return $ Just ()---- | assure that at least there are n threads available-addThreads n= Transient $ do- msem <- gets maxThread- case msem of- Nothing -> return ()- Just sem -> liftIO $ modifyIORef sem $ \n' -> if n' > n then n' else n- return $ Just ()---getNonUsedThreads :: TransIO (Maybe Int)---getNonUsedThreads= Transient $ do--- msem <- gets maxThread--- case msem of--- Just sem -> liftIO $ Just <$> readIORef sem--- Nothing -> return Nothing----- | The threads generated in the process passed as parameter will not be killed by `kill*` primitives-freeThreads :: TransIO a -> TransIO a-freeThreads proc= Transient $ do- st <- get- put st{freeTh= True}- r <- runTrans proc- modify $ \s -> s{freeTh= freeTh st}- return r---- | The threads will be killed when the parent thread dies. That is the default.--- This can be invoked to revert the effect of `freeThreads`-hookedThreads :: TransIO a -> TransIO a-hookedThreads proc= Transient $ do- st <- get- put st{freeTh= False}- r <- runTrans proc- modify $ \st -> st{freeTh= freeTh st}- return r---- | kill all the child threads of the current thread-killChilds :: TransientIO()-killChilds= Transient $ do- cont <- get- liftIO $ killChildren $ children cont- return $ Just ()---- * extensible state: session data management---- | Get the state data for the desired type if there is any.-getData :: (MonadState EventF m,Typeable a) => m (Maybe a)-getData = resp where- resp= gets mfData >>= \list ->- case M.lookup ( typeOf $ typeResp resp ) list of- Just x -> return . Just $ unsafeCoerce x- Nothing -> return Nothing- typeResp :: m (Maybe x) -> x- typeResp= undefined----- | getData specialized for the Transient monad. if Nothing, the--- monadic computation does not continue.------ If there is no such data, `getSData` silently stop the computation.--- That may or may not be the desired behaviour.--- To make sure that this does not get unnoticed, use this construction:------ > getSData <|> error "no data"------ To have the same semantics and guarantees than `get`, use a default value:------ > getInt= getSData <|> return (0 :: Int)------ The default value (0 in this case) has the same role than the initial value in a state monad.--- The difference is that you can define as many `get` as you need for all your data types.------ To distingish two data with the same types, use newtype definitions.-getSData :: Typeable a => TransIO a-getSData= Transient getData------ | set session data for this type. retrieved with getData or getSData--- Note that this is data in a state monad, that means that the update only affect downstream--- in the monad execution. it is not a global state neither a per user or per thread state--- it is a monadic state like the one of a state monad.-setData :: (MonadState EventF m, Typeable a) => a -> m ()-setData x=- let t= typeOf x in modify $ \st -> st{mfData= M.insert t (unsafeCoerce x) (mfData st)}---delData :: ( MonadState EventF m,Typeable a) => a -> m ()-delData x= modify $ \st -> st{mfData= M.delete (typeOf x ) (mfData st)}-----withSData :: ( MonadState EventF m,Typeable a) => (Maybe a -> a) -> m ()---withSData f= modify $ \st -> st{mfData=--- let dat = mfData st--- mx= M.lookup typeofx dat--- mx'= case mx of Nothing -> Nothing; Just x -> unsafeCoerce x--- fx= f mx'--- typeofx= typeOf $ typeoff f--- in M.insert typeofx (unsafeCoerce fx) dat}--- where--- typeoff :: (Maybe a -> a) -> a--- typeoff = undefined--------- | generator of identifiers that are unique withing the current monadic sequence--- They are not unique in the whole program.-genId :: MonadState EventF m => m Int-genId= do- st <- get- let n= mfSequence st- put st{mfSequence= n+1}- return n--getPrevId :: MonadState EventF m => m Int-getPrevId= do- n <- gets mfSequence- return n--instance Read SomeException where- readsPrec n str=- let [(s , r)]= read str in [(SomeException $ ErrorCall s,r)]---- | async calls--data StreamData a= SMore a | SLast a | SDone | SError SomeException deriving (Typeable, Show,Read)----- | variant of `parallel` that repeatedly executes the IO computation and kill the previously created childs------ It is useful in single threaded problems where each event discard the computations spawned by--- previous events-waitEvents :: IO b -> TransIO b-waitEvents io= do- mr <- parallel (SMore <$> io)- case mr of- SMore x -> return x- SError e -> throw e----- Multithreaded version of `waitEvents` that do not kill the computations spawned by previous events-waitEvents' :: IO b -> TransIO b-waitEvents' io= do- mr <- parallel (SMore <$> io)- case mr of- SMore x -> return x- SError e -> throw e---- | variant of `parallel` that execute the IO computation once, and kill the previous child threads-async :: IO b -> TransIO b-async io= do- mr <- parallel (SLast <$> io)- case mr of- SLast x -> return x- SError e -> throw e---- | variant of waitEvents that spawn free threads. It is a little faster at the cost of no thread control-spawn :: IO b -> TransIO b-spawn io= freeThreads $ do- mr <- parallel (SMore <$>io)- case mr of- SMore x -> return x- SError e -> throw e----- | executes an IO action each certain interval of time and return his value if it changes-sample :: Eq a => IO a -> Int -> TransIO a-sample action interval= do- v <- liftIO action- prev <- liftIO $ newIORef v- waitEvents (loop action prev) <|> async (return v)- where- loop action prev= loop'- where- loop'= do- threadDelay interval- v <- action- v' <- readIORef prev- if v /= v' then writeIORef prev v >> return v else loop'------- | return empty to the current thread, in new thread, execute the IO action,--- this IO action modify an internal buffer. then, executes the closure where `parallel` is located--- In this new execution, since the buffer is filled, `parallel` return the content of this buffer.--- Then it launch the continuation after it with this new value returned by the closure.------ If the maximum number of threads, set with `threads` has been reached `parallel` perform--- the work sequentially, in the current thread.--- So `parallel` means that 'it can be parallelized if there are thread available'------ if there is a limitation of threads, when a thread finish, the counter of threads available--- is increased so another `parallel` can make use of it.------ The behaviour of `parallel` depend on `StreamData`; If `SMore`, `parallel` will excute again the--- IO action. with `SLast`, `SDone` and `SError`, `parallel` will not repeat the IO action anymore.-parallel :: IO (StreamData b) -> TransIO (StreamData b)-parallel ioaction= Transient $ do- cont <- get -- !> "PARALLEL"- case event cont of- j@(Just _) -> do- put cont{event=Nothing}- return $ unsafeCoerce j- Nothing -> do- liftIO $ loop cont ioaction- was <- getData `onNothing` return NoRemote- when (was /= WasRemote) $ setData WasParallel- return Nothing----- executes the IO action and then the continuation included in the first parameter-loop :: EventF -> IO (StreamData t) -> IO ()-loop (cont'@(EventF eff e x fs a b c d _ childs g)) rec = do- chs <- liftIO $ newTVarIO []- let cont = EventF eff e x fs a b c d (Just cont') chs g- iocont dat= do- runStateT (runCont cont) cont{event= Just $ unsafeCoerce dat}- return ()-- -- execute the IO computation and then the closure-continuation- loop'= forkMaybe False cont $ do- mdat <- threadDelay 0 >> rec `catch` \(e :: SomeException) -> return $ SError e- case mdat of- se@(SError _) -> iocont se- SDone -> iocont SDone- last@(SLast _) -> iocont last-- more@(SMore _) -> do- forkMaybe False cont $ iocont more- loop'- loop'- return ()- where- forkMaybe True cont proc = forkMaybe' True cont proc- forkMaybe False cont proc = do- dofork <- case maxThread cont of- Nothing -> return True- Just sem -> do- dofork <- waitQSemB sem- if dofork then return True else return False- forkMaybe' dofork cont proc-- forkMaybe' dofork cont proc=- if dofork- then do- forkFinally1 (do- th <- myThreadId- hangThread cont' cont{threadId=th} -- !> "thread created: "++ show th- proc)- $ \me -> do- case me of -- !> "THREAD END" of- Left e -> do- when (fromException e /= Just ThreadKilled)$ liftIO $ print e- killChildren $ children cont -- !> "KILL RECEIVED" ++ (show $ unsafePerformIO myThreadId)-- Right _ -> when(not $ freeTh cont') $ do -- if was not a free thread- -- if parent is alive- -- then remove himself from the parent list (with free)- -- and pass his active children to his parent-- th <- myThreadId- mparent <- free th cont- return ()- -- pass the active children to the parent--- case mparent of--- Nothing -> return()--- Just parent -> atomically $ do--- chs' <- readTVar $ children cont--- chs <- (readTVar $ children parent)--- writeTVar (children parent)$ chs ++ chs'--- return ()-- case maxThread cont of- Just sem -> signalQSemB sem -- !> "freed thread"- Nothing -> return ()- return ()----- else proc -- !> "NO THREAD"--forkFinally1 :: IO a -> (Either SomeException a -> IO ()) -> IO ThreadId-forkFinally1 action and_then =- mask $ \restore -> forkIO $ try (restore action) >>= and_then--free th env= do- if isNothing $ parent env- then return Nothing -- !!> show th ++ " orphan"- else do- let msibling= fmap children $ parent env-- case msibling of- Nothing -> return Nothing- Just sibling -> do- found <- atomically $ do- sbs <- readTVar sibling- let (sbs', found) = drop [] th sbs -- !!> "search "++show th ++ " in " ++ show (map threadId sbs)- when found $ writeTVar sibling sbs' -- !> ("new list",map threadId sbs')- return found- if (not found && isJust (parent env))- then free th $ fromJust $ parent env -- !!> "toparent"- else return $ Just env-- where- drop processed th []= (processed,False)- drop processed th (ev:evts)| th == threadId ev= (processed ++ evts, True)- | otherwise= drop (ev:processed) th evts--hangThread parent child = when(not $ freeTh parent) $ do- let headpths= children parent- atomically $ do- ths <- readTVar headpths- writeTVar headpths $ child:ths -- !!> "thread added: "++ show (threadId child)---- | kill all the child threads associated with the continuation context-killChildren childs = do---- forkIO $ do- ths <- atomically $ do- ths <- readTVar childs- writeTVar childs []- return ths--- mapM_ killChildren ths -- recursive not needed, event handlers do it-- mapM_ (killThread . threadId) ths -- !!> ("KILLEVENT " ++ show (map threadId ths) ++--- if length ths <20 then ""--- else error "long list of threads" )--- return ()---type EventSetter eventdata response= (eventdata -> IO response) -> IO ()-type ToReturn response= IO response---- | deinvert an event handler.------ The first parameter is the setter of the event handler to be--- deinverted. Usually it is the primitive provided by a framework to set an event handler------ the second parameter is the value to return to the event handler. Usually it is `return()`------ it configures the event handler by calling the setter of the event--- handler with the current continuation-react- :: Typeable eventdata- => EventSetter eventdata response- -> ToReturn response- -> TransIO eventdata-react setHandler iob= Transient $ do- cont <- get- case event cont of- Nothing -> do- liftIO $ setHandler $ \dat ->do- runStateT (runCont cont) cont{event= Just $ unsafeCoerce dat}- iob- was <- getData `onNothing` return NoRemote- when (was /= WasRemote) $ setData WasParallel- return Nothing-- j@(Just _) -> do- put cont{event=Nothing}- return $ unsafeCoerce j---- Just dat -> do--- delData dat--- return (Just dat)----- case event cont of--- Nothing -> do--- liftIO $ loop cont ioaction--- was <- getData `onNothing` return NoRemote--- when (was /= WasRemote) $ setData WasParallel------ return Nothing--- j@(Just _) -> do--- put cont{event=Nothing}--- return $ unsafeCoerce j------ * non-blocking keyboard input--getLineRef= unsafePerformIO $ newTVarIO Nothing---roption= unsafePerformIO $ newMVar []---- | install a event receiver that wait for a string and trigger the continuation when this string arrives.-option :: (Typeable b, Show b, Read b, Eq b) =>- b -> String -> TransIO b-option ret message= do- let sret= show ret-- liftIO $ putStrLn $ "Enter "++sret++"\tto: " ++ message- liftIO $ modifyMVar_ roption $ \msgs-> return $ sret:msgs- waitEvents $ getLine' (==ret)- liftIO $ putStrLn $ show ret ++ " chosen"- return ret----- | validates an input entered in the keyboard in non blocking mode. non blocking means that--- the user can enter also anything else to activate other option--- unlike `option`, wich watch continuously, input only wait for one valid response-input :: (Typeable a, Read a,Show a) => (a -> Bool) -> String -> TransIO a-input cond prompt= Transient . liftIO $do- putStr prompt >> hFlush stdout- atomically $ do- mr <- readTVar getLineRef- case mr of- Nothing -> retry- Just r ->- case reads1 r of- (s,_):_ -> if cond s -- !> show (cond s)- then do- unsafeIOToSTM $ print s- writeTVar getLineRef Nothing -- !>"match"- return $ Just s-- else return Nothing- _ -> return Nothing---- | non blocking `getLine` with a validator-getLine' cond= do- atomically $ do- mr <- readTVar getLineRef- case mr of- Nothing -> retry- Just r ->- case reads1 r of -- !> ("received " ++ show r ++ show (unsafePerformIO myThreadId)) of- (s,_):_ -> if cond s -- !> show (cond s)- then do- writeTVar getLineRef Nothing -- !>"match"- return s-- else retry- _ -> retry--reads1 s=x where- x= if typeOf(typeOfr x) == typeOf "" then unsafeCoerce[(s,"")] else readsPrec' 0 s- typeOfr :: [(a,String)] -> a- typeOfr = undefined--inputLoop= do--- putStrLn "Press end to exit"- inputLoop' -- !> "started inputLoop"- where-- inputLoop'= do- r<- getLine- processLine r- inputLoop'--processLine r= do--- when (r=="end") $ atomically $ writeTVar rexit ()- let rs = breakSlash [] r- mapM_ (\ r -> -- if (r=="end") then exit' $ Left "terminated by user" else- do- threadDelay 100000- atomically . writeTVar getLineRef $ Just r ) rs--- where- breakSlash :: [String] -> String -> [String]- breakSlash [] ""= [""]- breakSlash s ""= s- breakSlash res ('\"':s)=- let (r,rest) = span(/= '\"') s- in breakSlash (res++[r]) $ tail1 rest-- breakSlash res s=- let (r,rest) = span(/= '/') s- in breakSlash (res++[r]) $ tail1 rest-- tail1 []=[]- tail1 x= tail x------- | wait for the execution of `exit` and return the result-stay rexit= do- mr <- takeMVar rexit- case mr of- Right Nothing -> stay rexit- Right (Just r) -> return r- Left msg -> putStrLn msg >> exitWith ExitSuccess---- | keep the main thread running, initiate the non blocking keyboard input and execute--- the transient computation.------ It also read a slash-separated list of string that are read by--- `option` and `input` as if they were entered by the keyboard------ > foo -p options/to/be/read/by/option/and/input--newtype Exit a= Exit a deriving Typeable--keep :: Typeable a => TransIO a -> IO a-keep mx = do- rexit <- newEmptyMVar- forkIO $ do- liftIO $ putMVar rexit $ Right Nothing- runTransient $ do- setData $ Exit rexit- async inputLoop- <|> do mx -- ; liftIO (putMVar rexit $ Right Nothing)- -- to avoid "takeMVar blocked in a infinite loop" error- <|> do- option "end" "exit"- killChilds- exit' (Left "terminated by user" `asTypeOf` (type1 mx))--- return ()- threadDelay 10000- execCommandLine- stay rexit- where- type1 :: TransIO a -> Either String (Maybe a)- type1= undefined---- | same than `keep`but do not initiate the asynchronous keyboard input.--- Useful for debugging or for creating background tasks.-keep' :: Typeable a => TransIO a -> IO a-keep' mx = do- rexit <- newEmptyMVar- forkIO $ do- runTransient $ do- setData $ Exit rexit- mx >> liftIO (putMVar rexit $ Right Nothing)- -- to avoid takeMVar in a infinite loop- return ()- threadDelay 10000- execCommandLine-- stay rexit--execCommandLine= do- args <- getArgs- let mindex = findIndex (\o -> o == "-p" || o == "--path" ) args- when (isJust mindex) $ do- let i= fromJust mindex +1- when (length args >= i) $ do- let path= args !! i- putStr "Executing: " >> print path- processLine path---- | force the finalization of the main thread and thus, all the Transient block (and the application--- if there is no more code)-exit :: Typeable a => a -> TransIO a-exit x= do- Exit rexit <- getSData <|> error "exit: not the type expected" `asTypeOf` type1 x- liftIO $ putMVar rexit . Right $ Just x- stop- where- type1 :: a -> TransIO (Exit (MVar (Either String (Maybe a))))- type1= undefined--exit' x= do- Exit rexit <- getSData <|> error "exit: not type expected"- liftIO $ putMVar rexit x ; stop----- | alternative operator for maybe values. Used in infix mode-onNothing :: Monad m => m (Maybe b) -> m b -> m b-onNothing iox iox'= do- mx <- iox- case mx of- Just x -> return x- Nothing -> iox'+{-# LANGUAGE ScopedTypeVariables #-} +----------------------------------------------------------------------------- +-- +-- Module : Base +-- Copyright : +-- License : GPL (Just (Version {versionBranch = [3], versionTags = []})) +-- +-- Maintainer : agocorona@gmail.com +-- Stability : +-- Portability : +-- +-- | See http://github.com/agocorona/transient +-- everithing in this module is exported in order to allow extensibility. +----------------------------------------------------------------------------- +{-# LANGUAGE ExistentialQuantification #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE DeriveDataTypeable #-} +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE Rank2Types #-} + +module Transient.Internals where + + + +import Control.Applicative +import Control.Monad.State +import Data.Dynamic +import qualified Data.Map as M +import Data.Monoid +import Debug.Trace +import System.IO.Unsafe +import Unsafe.Coerce +import Control.Exception +import Control.Concurrent +import GHC.Conc(unsafeIOToSTM) +import Control.Concurrent.STM hiding (retry) +import qualified Control.Concurrent.STM as STM (retry) +import System.Mem.StableName +import Data.Maybe + +import Data.List +import Data.IORef +import System.Environment +import System.IO +import System.Exit + +import qualified Data.ByteString.Char8 as BS + + + +--{-# INLINE (!>) #-} +--(!>) :: Show a => b -> a -> b +--(!>) x y= trace (show y) x +--infixr 0 !> + + + + + +data TransIO x = Transient {runTrans :: StateT EventF IO (Maybe x)} +type SData= () + +type EventId= Int + +type TransientIO= TransIO + +data LifeCycle= Alive | Parent | Listener | Dead deriving (Eq,Show) + +data EventF = forall a b . EventF{meffects :: () + ,event :: Maybe SData + ,xcomp :: TransIO a + ,fcomp :: [b -> TransIO b] + ,mfData :: M.Map TypeRep SData + ,mfSequence :: Int + ,threadId :: ThreadId + ,freeTh :: Bool + ,parent :: Maybe EventF + ,children :: MVar[EventF] + ,maxThread :: Maybe (IORef Int) + ,labelth :: IORef (LifeCycle,BS.ByteString) + } + deriving Typeable + + + + +type Effects= forall a b c.TransIO a -> TransIO a -> (a -> TransIO b) + -> StateIO (StateIO (Maybe c) -> StateIO (Maybe c), Maybe a) + + + + +instance MonadState EventF TransIO where + get = Transient $ get >>= return . Just + put x= Transient $ put x >> return (Just ()) + state f = Transient $ do + s <- get + let ~(a, s') = f s + put s' + return $ Just a + +type StateIO= StateT EventF IO + +-- | Run a "non transient" computation within the underlying state monad, so it is guaranteed +-- that the computation neither can stop neither can trigger additional events/threads +noTrans x= Transient $ x >>= return . Just + +-- | Run the transient computation with a blank state +runTransient :: TransIO x -> IO (Maybe x, EventF) +runTransient t= do + th <- myThreadId + label <- newIORef $ (Alive,BS.pack "top") + childs <- newMVar [] + let eventf0= EventF () Nothing empty [] M.empty 0 + th False Nothing childs Nothing label + + runStateT (runTrans t) eventf0 + +-- | Run the transient computation with an state +runTransState st x = runStateT (runTrans x) st + +-- | Get the continuation context: closure, continuation, state, child threads etc +getCont :: TransIO EventF +getCont = Transient $ Just <$> get + +-- | Run the closure and the continuation using the state data of the calling thread +runCont :: EventF -> StateIO (Maybe a) +runCont (EventF _ _ x fs _ _ _ _ _ _ _ _)= runTrans $ do + r <- unsafeCoerce x + compose fs r + +-- | Run the closure and the continuation using his own state data +runCont' cont= runStateT (runCont cont) cont + +-- | Warning: radiactive untyped stuff. handle with care +getContinuations :: StateIO [a -> TransIO b] +getContinuations= do + EventF _ _ _ fs _ _ _ _ _ _ _ _ <- get + return $ unsafeCoerce fs + +{- +runCont cont= do + mr <- runClosure cont + case mr of + Nothing -> return Nothing + Just r -> runContinuation cont r +-} + + +-- | Compose a list of continuations +compose []= const empty +compose (f: fs)= \x -> f x >>= compose fs + + + +-- | Run the closure (the 'x' in 'x >>= f') of the current bind operation. +runClosure :: EventF -> StateIO (Maybe a) +runClosure (EventF _ _ x _ _ _ _ _ _ _ _ _) = unsafeCoerce $ runTrans x + + +-- | Run the continuation (the 'f' in 'x >>= f') of the current bind operation with the current state +runContinuation :: EventF -> a -> StateIO (Maybe b) +runContinuation (EventF _ _ _ fs _ _ _ _ _ _ _ _) = + runTrans . (unsafeCoerce $ compose $ fs) + + +setContinuation :: TransIO a -> (a -> TransIO b) -> [c -> TransIO c] -> StateIO () +setContinuation b c fs = do + (EventF eff ev _ _ d e f g h i j k) <- get + put $ EventF eff ev b ( unsafeCoerce c: fs) d e f g h i j k + +withContinuation c mx= do + EventF eff ev f1 fs d e f g h i j k<- get + put $ EventF eff ev mx ( unsafeCoerce c: fs) d e f g h i j k + r <- mx + restoreStack fs + return r + +-- | run a chain of continuations. It is up to the programmer to assure by construction that +-- each continuation type-check with the next, that the parameter type match the input of the first +-- continuation. +-- Normally this makes sense if it stop the current flow with `stop` after the invocation +runContinuations :: [a -> TransIO b] -> c -> TransIO d +runContinuations fs x= (compose $ unsafeCoerce fs) x + +instance Functor TransIO where + fmap f mx= -- Transient $ fmap (fmap f) $ runTrans mx + do + x <- mx + return $ f x + + + + +instance Applicative TransIO where + pure a = Transient . return $ Just a + + f <*> g = Transient $ do + rf <- liftIO $ newIORef (Nothing,[]) + rg <- liftIO $ newIORef (Nothing,[]) -- !> "NEWIOREF" + + fs <- getContinuations + + let + + hasWait (_:Wait:_)= True + hasWait _ = False + + appf k = Transient $ do + Log rec _ full <- getData `onNothing` return (Log False [] []) + (liftIO $ writeIORef rf (Just k,full)) +-- !> ( show $ unsafePerformIO myThreadId) ++"APPF" + (x, full2)<- liftIO $ readIORef rg + when (hasWait full ) $ + -- !> (hasWait full,"full",full, "\nfull2",full2)) $ + let full'= head full: full2 + in (setData $ Log rec full' full') -- !> ("result1",full') + + return $ Just k <*> x + + appg x = Transient $ do + Log rec _ full <- getData `onNothing` return (Log False [] []) + liftIO $ writeIORef rg (Just x, full) +-- !> ( show $ unsafePerformIO myThreadId)++ "APPG" + (k,full1) <- liftIO $ readIORef rf + when (hasWait full) $ + -- !> ("full", full, "\nfull1",full1)) $ + let full'= head full: full1 + in (setData $ Log rec full' full') -- !> ("result2",full') + + return $ k <*> Just x + + setContinuation f appf fs + + + k <- runTrans f + -- !> ( show $ unsafePerformIO myThreadId)++ "RUN f" + was <- getData `onNothing` return NoRemote + when (was == WasParallel) $ setData NoRemote + + Log recovery _ full <- getData `onNothing` return (Log False [] []) + + + + if was== WasRemote || (not recovery && was == NoRemote && isNothing k ) +-- !> ("was,recovery,isNothing=",was,recovery, isNothing k) + -- if the first operand was a remote request + -- (so this node is not master and hasn't to execute the whole expression) + -- or it was not an asyncronous term (a normal term without async or parallel + -- like primitives) and is nothing + then do + restoreStack fs + return Nothing + else do + when (isJust k) $ liftIO $ writeIORef rf (k,full) + -- when necessary since it maybe WasParallel and Nothing + + setContinuation g appg fs + + x <- runTrans g +-- !> ( show $ unsafePerformIO myThreadId) ++ "RUN g" + Log recovery _ full' <- getData `onNothing` return (Log False [] []) + liftIO $ writeIORef rg (x,full') + restoreStack fs + k'' <- if was== WasParallel + then do + (k',_) <- liftIO $ readIORef rf -- since k may have been updated by a parallel f + return k' + else return k + return $ k'' <*> x + +restoreStack fs= + modify $ \(EventF eff _ f _ a b c d parent children g1 la) -> + EventF eff Nothing f fs a b c d parent children g1 la + +readWithErr line= + let [(v,left)] = readsPrec 0 line + in (v `seq` return [(v,left)]) + `catch` (\(e::SomeException) -> + error ("read error trying to read type: \"" ++ show( typeOf v) ++ "\" in: "++" <"++ show line++"> ")) + + +readsPrec' _= unsafePerformIO . readWithErr + + +class (Show a, Read a, Typeable a) => Loggable a where + + +instance (Show a, Read a,Typeable a) => Loggable a where + +-- | Dynamic serializable data for logging +data IDynamic= IDyns String | forall a.Loggable a => IDynamic a + +instance Show IDynamic where + show (IDynamic x)= show $ show x + show (IDyns s)= show s + +instance Read IDynamic where + readsPrec n str= map (\(x,s) -> (IDyns x,s)) $ readsPrec' n str + + +type Recover= Bool +type CurrentPointer= [LogElem] +type LogEntries= [LogElem] +data LogElem= Wait | Exec | Var IDynamic deriving (Read,Show) +data Log= Log Recover CurrentPointer LogEntries deriving (Typeable, Show) + + +instance Alternative TransIO where + empty = Transient $ return Nothing + (<|>) = mplus + + +data RemoteStatus= WasRemote | WasParallel | NoRemote deriving (Typeable, Eq, Show) + +instance MonadPlus TransIO where + mzero= empty + mplus x y= Transient $ do + mx <- runTrans x -- !> "RUNTRANS11111" + was <- getData `onNothing` return NoRemote + if was== WasRemote -- !> was + then return Nothing + else + case mx of + Nothing -> runTrans y -- !> "RUNTRANS22222" + justx -> return justx + + + + +-- | A sinonym of empty that can be used in a monadic expression. it stop the +-- computation and execute the next alternative computation (composed with `<|>`) +stop :: Alternative m => m stopped +stop= empty + + +--instance (Num a,Eq a,Fractional a) =>Fractional (TransIO a)where +-- mf / mg = (/) <$> mf <*> mg +-- fromRational (x:%y) = fromInteger x % fromInteger y + + +instance (Num a,Eq a) => Num (TransIO a) where + fromInteger = return . fromInteger + mf + mg = (+) <$> mf <*> mg + mf * mg = (*) <$> mf <*> mg + negate f = f >>= return . negate + abs f = f >>= return . abs + signum f = f >>= return . signum + + +class AdditionalOperators m where + + -- | Executes the second operand even if the frist return empty. + -- A normal imperative (monadic) sequence uses the operator (>>) which in the + -- Transient monad does not execute the next operand if the previous one return empty. + (**>) :: m a -> m b -> m b + + -- | Forces the execution of the second operand even if the first stop. It does not execute + -- the second operand as result of internal events occuring in the first operand. + -- Return the first result + (<**) :: m a -> m b -> m a + + atEnd' ::m a -> m b -> m a + atEnd' = (<**) + + -- | Forces the execution of the second operand even if the first stop. Return the first result. The second + -- operand is executed also when internal events happens in the first operand and it returns something + (<***) :: m a -> m b -> m a + + atEnd :: m a -> m b -> m a + atEnd= (<***) + + +instance AdditionalOperators TransIO where + +-- (**>) :: TransIO a -> TransIO b -> TransIO b + (**>) x y= Transient $ do + runTrans x + runTrans y + +-- (<***) :: TransIO a -> TransIO b -> TransIO a + (<***) ma mb= Transient $ do + fs <- getContinuations + setContinuation ma (\x -> mb >> return x) fs + a <- runTrans ma + runTrans mb + restoreStack fs + return a + +-- (<**) :: TransIO a -> TransIO b -> TransIO a + (<**) ma mb= Transient $ do + a <- runTrans ma -- !> "ma" + runTrans mb -- !> "mb" + return a + +infixr 1 <*** , <**, **> + + + +-- | When the first operand is an asynchronous operation, the second operand is executed once (one single time) +-- when the first completes his first asyncronous operation. +-- +-- This is useful for spawning asynchronous or distributed tasks that are singletons and that should start +-- when the first one is set up. +-- +-- for example a streaming where the event receivers are acivated before the senders. + +(<|) :: TransIO a -> TransIO b -> TransIO a +(<|) ma mb = Transient $ do + fs <- getContinuations + ref <- liftIO $ newIORef False + setContinuation ma (cont ref ) fs + r <- runTrans ma + restoreStack fs + return r + where + cont ref x= Transient $ do + n <- liftIO $ readIORef ref + if n == True + then return $ Just x + else do liftIO $ writeIORef ref True + runTrans mb + return $ Just x + +instance Monoid a => Monoid (TransIO a) where + mappend x y = mappend <$> x <*> y + mempty= return mempty + +-- | Set the current closure and continuation for the current statement +setEventCont :: TransIO a -> (a -> TransIO b) -> StateIO EventF +setEventCont x f = do + + st@(EventF eff e _ fs d n r applic ch rc bs la) <- get -- !> "SET" + let cont= EventF eff e x ( unsafeCoerce f : fs) d n r applic ch rc bs la + put cont + return cont + +-- | Reset the closure and continuation. remove inner binds than the previous computations may have stacked +-- in the list of continuations. +--resetEventCont :: Maybe a -> EventF -> StateIO (TransIO b -> TransIO b) +resetEventCont mx _=do + st@(EventF eff e _ fs d n r nr ch rc bs la) <- get -- !> "reset" + let f= \mx -> case mx of + Nothing -> empty + Just x -> (unsafeCoerce $ head fs) x + put $ EventF eff e (f mx) ( tailsafe fs) d n r nr ch rc bs la + return id + +tailsafe []=[] +tailsafe (x:xs)= xs + +--refEventCont= unsafePerformIO $ newIORef baseEffects + +{-# INLINE baseEffects #-} +baseEffects :: Effects + +baseEffects x x' f' = do + c <-setEventCont x' f' + mk <- runTrans x + t <- resetEventCont mk c + return (t,mk) + +instance Monad TransIO where + + return = pure + + x >>= f = Transient $ do +-- effects <- gets effects -- liftIO $ readIORef refEventCont + (t,mk) <- baseEffects x x f + t $ case mk of + Just k -> runTrans (f k) + + Nothing -> return Nothing + +--instance MonadTrans (Transient ) where +-- lift mx = Transient $ mx >>= return . Just + +instance MonadIO TransIO where + + liftIO mx=do + ex <- liftIO' $ (mx >>= return . Right) `catch` (\(e :: SomeException) -> return $ Left e) + case ex of + Left e -> back e -- finish $ Just e + Right x -> return x + where + liftIO' x = Transient $ liftIO x >>= return . Just -- let x= liftIO io in x `seq` lift x + + +-- * Threads + +waitQSemB sem= atomicModifyIORef sem $ \n -> if n > 0 then(n-1,True) else (n,False) +signalQSemB sem= atomicModifyIORef sem $ \n -> (n + 1,()) + +-- | Set the maximun number of threads for a procedure. It is useful to limit the +-- parallelization of transient code that uses `parallel` `spawn` and `waitEvents` +threads :: Int -> TransIO a -> TransIO a +threads n proc= do + msem <- gets maxThread + sem <- liftIO $ newIORef n + modify $ \s -> s{maxThread= Just sem} + r <- proc <** (modify $ \s -> s{maxThread = msem}) -- restore it + return r + +-- | Delete all the previous child threads generated by the expression taken as parameter and continue execution +-- of the current thread. +oneThread :: TransIO a -> TransIO a +oneThread comp= do + st <- get + chs <- liftIO $ newMVar [] + label <- liftIO $ newIORef (Alive, BS.pack "oneThread") + + let st' = st{parent=Just st,children= chs, labelth= label} + liftIO $ hangThread st st' + put st' + x <- comp + th<- liftIO myThreadId -- !> ("FATHER:", threadId st) + chs <- liftIO $ readMVar chs -- children st' + + liftIO $ mapM_ (killChildren1 th ) chs + return x + where + killChildren1 :: ThreadId -> EventF -> IO () + killChildren1 th state = do + ths' <- modifyMVar (children state) $ \ths -> do + let (inn, ths')= partition (\st -> threadId st == th) ths + return (inn, ths') + + + mapM_ (killChildren1 th ) ths' + mapM_ (killThread . threadId) ths' +-- !> ("KILLEVENT1 ", map threadId ths' ) + + + + +-- | Add a label to the current passing threads so it can be printed by debugging calls like `showThreads` +labelState l= do + st <- get + liftIO $ atomicModifyIORef (labelth st) $ \(status,_) -> ((status,BS.pack l),()) + +printBlock= unsafePerformIO $ newMVar () + + +-- Show the tree of threads hanging from the state +showThreads :: MonadIO m => EventF -> m () +showThreads st= liftIO $ withMVar printBlock $ const $ do + + mythread <- myThreadId +-- !> "showThreads" + + putStrLn "---------Threads-----------" + let showTree n ch= do + liftIO $ do + putStr $ take n $ repeat ' ' + (state,label) <- readIORef $ labelth ch + if BS.null label + then putStr . show $ threadId ch + else do BS.putStr label ; putStr . drop 8 . show $ threadId ch + when (state== Dead) $ putStr " dead" + putStrLn $ if mythread== threadId ch then " <--" else "" + + chs <- readMVar $ children ch + mapM_ (showTree $ n+2) $ reverse chs + + showTree 0 st + + + +-- | Return the state of the thread that initiated the transient computation +topState :: TransIO EventF +topState = do + st <- get + return $ toplevel st + where + toplevel st= do + case parent st of + Nothing -> st + Just p -> toplevel p + +-- | Return the state variable of the type desired with which a thread, identified by his number in the treee was initiated +showState :: (Typeable a, MonadIO m, Alternative m) => String -> EventF -> m (Maybe a) +showState th top = resp + where + resp= do + let thstring= drop 9 . show $ threadId top + if thstring == th then getstate top else do + sts <- liftIO $ readMVar $ children top + foldl (<|>) empty $ map (showState th) sts + where + getstate st= + case M.lookup ( typeOf $ typeResp resp ) $ mfData st of + Just x -> return . Just $ unsafeCoerce x + Nothing -> return Nothing + typeResp :: m (Maybe x) -> x + typeResp= undefined + + +-- | Add n threads to the limit of threads. If there is no limit, it set it +addThreads' :: Int -> TransIO () +addThreads' n= noTrans $ do + msem <- gets maxThread + case msem of + Just sem -> liftIO $ modifyIORef sem $ \n' -> n + n' + Nothing -> do + sem <- liftIO (newIORef n) + modify $ \ s -> s{maxThread= Just sem} + return () + +-- | Assure that at least there are n threads available +addThreads n= noTrans $ do + msem <- gets maxThread + case msem of + Nothing -> return () + Just sem -> liftIO $ modifyIORef sem $ \n' -> if n' > n then n' else n + return () +--getNonUsedThreads :: TransIO (Maybe Int) +--getNonUsedThreads= Transient $ do +-- msem <- gets maxThread +-- case msem of +-- Just sem -> liftIO $ Just <$> readIORef sem +-- Nothing -> return Nothing + + +-- | The threads generated in the process passed as parameter will not be killed by `kill*` +-- primitives. +-- +-- Since there is no thread control, the application run slightly faster. +freeThreads :: TransIO a -> TransIO a +freeThreads proc= Transient $ do + st <- get + put st{freeTh= True} + r <- runTrans proc + modify $ \s -> s{freeTh= freeTh st} + return r + +-- | The threads will be killed when the parent thread dies. That is the default. +-- This can be invoked to revert the effect of `freeThreads` +hookedThreads :: TransIO a -> TransIO a +hookedThreads proc= Transient $ do + st <- get + put st{freeTh= False} + r <- runTrans proc + modify $ \st -> st{freeTh= freeTh st} + return r + +-- | kill all the child threads of the current thread +killChilds :: TransIO() +killChilds= noTrans $ do + cont <- get + + liftIO $ do + killChildren $ children cont + writeIORef (labelth cont) (Alive,mempty) -- !> (threadId cont,"relabeled") + return () + +-- | Kill the current thread and the childs +killBranch= noTrans $ do + st <- get + liftIO $ killBranch' st + +-- | Kill the childs and the thread of an state +killBranch' cont= liftIO $ do + + killChildren $ children cont + let thisth= threadId cont + let mparent= parent cont + when (isJust mparent) $ modifyMVar_ (children $ fromJust mparent) + $ \sts -> return $ filter (\st -> threadId st /= thisth) sts + killThread $ thisth + + +-- * extensible state: session data management + +-- | Get the state data for the desired type if there is any. +getData :: (MonadState EventF m,Typeable a) => m (Maybe a) +getData = resp where + resp= gets mfData >>= \list -> + case M.lookup ( typeOf $ typeResp resp ) list of + Just x -> return . Just $ unsafeCoerce x + Nothing -> return Nothing + typeResp :: m (Maybe x) -> x + typeResp= undefined + + +-- | getData specialized for the Transient monad. if Nothing, the +-- monadic computation does not continue. +-- +-- If there is no such data, `getSData` silently stop the computation. +-- That may or may not be the desired behaviour. +-- To make sure that this does not get unnoticed, use this construction: +-- +-- > getSData <|> error "no data" +-- +-- To have the same semantics and guarantees than `get`, use a default value: +-- +-- > getInt= getSData <|> return (0 :: Int) +-- +-- The default value (0 in this case) has the same role than the initial value in a state monad. +-- The difference is that you can define as many `get` as you need for all your data types. +-- +-- To distingish two data with the same types, use newtype definitions. +getSData :: Typeable a => TransIO a +getSData= Transient getData + +-- | Synonym for `getSData` +getState :: Typeable a => TransIO a +getState= getSData + +-- | Set session data for this type. retrieved with getData or getSData +-- Note that this is data in a state monad, that means that the update only affect downstream +-- in the monad execution. it is not a global state neither a per user or per thread state +-- it is a monadic state like the one of a state monad. +setData :: (MonadState EventF m, Typeable a) => a -> m () +setData x= + let t= typeOf x in modify $ \st -> st{mfData= M.insert t (unsafeCoerce x) (mfData st)} + + +-- | Modify state data. It accept a function that get the current state (if exist) as parameter. +-- The state will be deleted or changed depending on function result +modifyData :: (MonadState EventF m, Typeable a) => (Maybe a -> Maybe a) -> m () +modifyData f= modify $ \st -> st{mfData= + let t= typeOf $ typeResp f + in M.alter alterf t (mfData st)} + where + typeResp :: (Maybe a -> b) -> a + typeResp= undefined + alterf mx = + let x' = case mx of + Just x -> Just $ unsafeCoerce x + Nothing -> Nothing + in unsafeCoerce $ f x' + +-- | Synonym for modifyData +modifyState :: (MonadState EventF m, Typeable a) => (Maybe a -> Maybe a) -> m () +modifyState= modifyData + +-- | Synonym for `setData` +setState :: (MonadState EventF m, Typeable a) => a -> m () +setState= setData + +delData :: ( MonadState EventF m,Typeable a) => a -> m () +delData x= modify $ \st -> st{mfData= M.delete (typeOf x ) (mfData st)} + +delState :: ( MonadState EventF m,Typeable a) => a -> m () +delState= delData + + + +-- | Generator of identifiers that are unique withing the current monadic sequence +-- They are not unique in the whole program. +genId :: MonadState EventF m => m Int +genId= do + st <- get + let n= mfSequence st + put st{mfSequence= n+1} + return n + +getPrevId :: MonadState EventF m => m Int +getPrevId= do + n <- gets mfSequence + return n + +instance Read SomeException where + readsPrec n str= + let [(s , r)]= read str in [(SomeException $ ErrorCall s,r)] + +-- | Async calls + +data StreamData a= SMore a | SLast a | SDone | SError SomeException deriving (Typeable, Show,Read) + + +-- | Variant of `parallel` that repeatedly executes the IO computation without end +-- +waitEvents :: IO b -> TransIO b +waitEvents io= do + mr <- parallel (SMore <$> io) + case mr of + SMore x -> return x + SError e -> back e + +-- | Variant of `parallel` that execute the IO computation once +async :: IO b -> TransIO b +async io= do + mr <- parallel (SLast <$> io) + case mr of + SLast x -> return x + SError e -> back e + +-- | in an alternative computation it executes an async operations synchronously. +-- This means that the alternatives do not execute until the async operation finishes. +-- Do not use in Applicatives. +sync :: TransIO a -> TransIO a +sync x= do + setData WasRemote + r <- x + delData WasRemote + return r + +-- | `spawn= freeThreads . waitEvents` +spawn= freeThreads . waitEvents + +-- | Executes an IO action each certain interval of time and return his value if it changes +sample :: Eq a => IO a -> Int -> TransIO a +sample action interval= do + v <- liftIO action + prev <- liftIO $ newIORef v + waitEvents (loop action prev) <|> async (return v) + where + loop action prev= loop' + where + loop'= do + threadDelay interval + v <- action + v' <- readIORef prev + if v /= v' then writeIORef prev v >> return v else loop' + + +--serial :: IO (StreamData b) -> TransIO (StreamData b) +--serial ioaction= Transient $ do +-- cont <- get -- !> "PARALLEL" +-- case event cont of +-- j@(Just _) -> do +-- put cont{event=Nothing} +-- return $ unsafeCoerce j +-- Nothing -> do +-- liftIO $ loop cont ioaction +-- return Nothing +-- +-- where loop cont ioaction= do +-- let iocont dat= do +-- runStateT (runCont cont) cont{event= Just $ unsafeCoerce dat} +-- return () +-- mdat <- ioaction `catch` \(e :: SomeException) -> return $ SError e +-- case mdat of +-- se@(SError _) -> iocont se +-- SDone -> iocont SDone +-- last@(SLast _) -> iocont last +-- +-- more@(SMore _) -> do +-- iocont more +-- loop cont ioaction + + +-- | Return empty to the current thread and execute the IO action in a new thread. +-- When the IO action returns, the transient computation continues with this value as the result +-- The IO action may be re-executed or not depending on the result. So parallel can spawn any +-- number of threads/results. +-- +-- If the maximum number of threads, set with `threads` has been reached `parallel` perform +-- the work sequentially, in the current thread. +-- So `parallel` means that 'it can be parallelized if there are thread available' +-- +-- if there is a limitation of threads, when a thread finish, the counter of threads available +-- is increased so another `parallel` can make use of it. +-- +-- The behaviour of `parallel` depend on `StreamData`; If `SMore`, `parallel` will excute again the +-- IO action. With `SLast`, `SDone` and `SError`, `parallel` will not repeat the IO action anymore. +parallel :: IO (StreamData b) -> TransIO (StreamData b) +parallel ioaction= Transient $ do + cont <- get -- !> "PARALLEL" + case event cont of + j@(Just _) -> do + put cont{event=Nothing} + return $ unsafeCoerce j + Nothing -> do + liftIO $ atomicModifyIORef (labelth cont) $ \(_,lab) -> ((Parent,lab),()) + liftIO $ loop cont ioaction + was <- getData `onNothing` return NoRemote + when (was /= WasRemote) $ setData WasParallel + + +-- th <- liftIO myThreadId +-- return () !> ("finish",th) + return Nothing + + +-- Execute the IO action and the continuation +loop :: EventF -> IO (StreamData t) -> IO () +loop parentc rec = forkMaybe parentc $ \cont -> do + + -- execute the IO computation and then the closure-continuation + liftIO $ atomicModifyIORef (labelth cont) $ const ((Listener,BS.pack "wait"),()) + let loop'= do + mdat <- rec `catch` \(e :: SomeException) -> return $ SError e + case mdat of + se@(SError _) -> setworker cont >> iocont se cont + SDone -> setworker cont >> iocont SDone cont + last@(SLast _) -> setworker cont >> iocont last cont + + more@(SMore _) -> do + forkMaybe cont $ iocont more + loop' + + where + setworker cont= liftIO $ atomicModifyIORef (labelth cont) $ const ((Alive,BS.pack "work"),()) + + iocont dat cont = do + + let cont'= cont{event= Just $ unsafeCoerce dat} + runStateT (runCont cont') cont' + return () + + + loop' + return () + where + + forkMaybe parent proc = do + case maxThread parent of + Nothing -> forkIt parent proc + Just sem -> do + dofork <- waitQSemB sem + if dofork then forkIt parent proc else proc parent + + + forkIt parent proc= do + chs <- liftIO $ newMVar [] + + label <- newIORef (Alive, BS.pack "work") + let cont = parent{parent=Just parent,children= chs, labelth= label} + + forkFinally1 (do + th <- myThreadId + let cont'= cont{threadId=th} + when(not $ freeTh parent )$ hangThread parent cont' + -- !> ("thread created: ",th,"in",threadId parent ) + + proc cont') + $ \me -> do + +-- case me of -- !> "THREAD END" of +-- Left e -> do +---- when (fromException e /= Just ThreadKilled)$ +-- liftIO $ print e +-- killChildren $ children cont +---- !> "KILL RECEIVED" ++ (show $ unsafePerformIO myThreadId) +-- +-- Right _ -> + + + + case maxThread cont of + Just sem -> signalQSemB sem -- !> "freed thread" + Nothing -> when(not $ freeTh parent ) $ do -- if was not a free thread + + th <- myThreadId + (can,label) <- atomicModifyIORef (labelth cont) $ \(l@(status,label)) -> + ((if status== Alive then Dead else status, label),l) + + + when (can/= Parent ) $ free th parent + return () + + + forkFinally1 :: IO a -> (Either SomeException a -> IO ()) -> IO ThreadId + forkFinally1 action and_then = + mask $ \restore -> forkIO $ try (restore action) >>= and_then + + free th env= do +-- return () !> ("freeing",th,"in",threadId env) + let sibling= children env + + (sbs',found) <- modifyMVar sibling $ \sbs -> do + let (sbs', found) = drop [] th sbs + return (sbs',(sbs',found)) + +-- sbs <- takeMVar sibling +-- let (sbs', found) = drop [] th sbs -- !> "search "++show th ++ " in " ++ show (map threadId sbs) + + if found + then do +-- putMVar sibling sbs' +-- !> ("new list for",threadId env,map threadId sbs') + (typ,_) <- readIORef $ labelth env + if (null sbs' && typ /= Listener && isJust (parent env)) + -- free the parent + then free (threadId env) ( fromJust $ parent env) + else return () + +-- return env + else return () -- putMVar sibling sbs + -- !> (th,"orphan") + + where + drop processed th []= (processed,False) + drop processed th (ev:evts)| th == threadId ev= (processed ++ evts, True) + | otherwise= drop (ev:processed) th evts + + + +hangThread parentProc child = do + + let headpths= children parentProc + + modifyMVar_ headpths $ \ths -> return (child:ths) +-- ths <- takeMVar headpths +-- putMVar headpths (child:ths) + + + -- !> ("hang", threadId child, threadId parentProc,map threadId ths,unsafePerformIO $ readIORef $ labelth parentProc) + +-- | kill all the child threads associated with the continuation context +killChildren childs = do + + + ths <- modifyMVar childs $ \ths -> return ([],ths) +-- ths <- takeMVar childs +-- putMVar childs [] + + mapM_ (killChildren . children) ths + + + mapM_ (killThread . threadId) ths -- !> ("KILL", map threadId ths ) + + + + + +-- | De-invert an event handler. +-- +-- The first parameter is the setter of the event handler to be +-- deinverted. Usually it is the primitive provided by a framework to set an event handler +-- +-- the second parameter is the value to return to the event handler. Usually it is `return()` +-- +-- it configures the event handler by calling the setter of the event +-- handler with the current continuation +react + :: Typeable eventdata + => ((eventdata -> IO response) -> IO ()) + -> IO response + -> TransIO eventdata +react setHandler iob= Transient $ do + cont <- get + case event cont of + Nothing -> do + liftIO $ setHandler $ \dat ->do + runStateT (runCont cont) cont{event= Just $ unsafeCoerce dat} + iob + was <- getData `onNothing` return NoRemote + when (was /= WasRemote) $ setData WasParallel + return Nothing + + j@(Just _) -> do + put cont{event=Nothing} + return $ unsafeCoerce j + + + +-- * non-blocking keyboard input + +getLineRef= unsafePerformIO $ newTVarIO Nothing + + +roption= unsafePerformIO $ newMVar [] + +-- | Install a event receiver that wait for a string and trigger the continuation when this string arrives. +option :: (Typeable b, Show b, Read b, Eq b) => + b -> String -> TransIO b +option ret message= do + let sret= show ret + + liftIO $ putStrLn $ "Enter "++sret++"\tto: " ++ message + liftIO $ modifyMVar_ roption $ \msgs-> return $ sret:msgs + waitEvents $ getLine' (==ret) + liftIO $ putStr "option: " >> putStrLn (show ret) + return ret + + +-- | Validates an input entered in the keyboard in non blocking mode. non blocking means that +-- the user can enter also anything else to activate other option +-- unlike `option`, wich watch continuously, input only wait for one valid response +input :: (Typeable a, Read a,Show a) => (a -> Bool) -> String -> TransIO a +input cond prompt= Transient . liftIO $do + putStr prompt >> hFlush stdout + atomically $ do + mr <- readTVar getLineRef + case mr of + Nothing -> STM.retry + Just r -> + case reads1 r of + (s,_):_ -> if cond s -- !> show (cond s) + then do + unsafeIOToSTM $ print s + writeTVar getLineRef Nothing -- !>"match" + return $ Just s + + else return Nothing + _ -> return Nothing + +-- | Non blocking `getLine` with a validator +getLine' cond= do + atomically $ do + mr <- readTVar getLineRef + case mr of + Nothing -> STM.retry + Just r -> + case reads1 r of -- !> ("received " ++ show r ++ show (unsafePerformIO myThreadId)) of + (s,_):_ -> if cond s -- !> show (cond s) + then do + writeTVar getLineRef Nothing -- !>"match" + return s + + else STM.retry + _ -> STM.retry + +reads1 s=x where + x= if typeOf(typeOfr x) == typeOf "" then unsafeCoerce[(s,"")] else readsPrec' 0 s + typeOfr :: [(a,String)] -> a + typeOfr = undefined + + +inputLoop= do + r<- getLine + atomically $ writeTVar getLineRef Nothing + processLine r + inputLoop + +processLine r= do + + let rs = breakSlash [] r + + liftIO $ mapM_ (\ r -> + atomically $ do +-- threadDelay 1000000 + t <- readTVar getLineRef + when (isJust t) STM.retry + writeTVar getLineRef $ Just r ) rs + + + where + breakSlash :: [String] -> String -> [String] + breakSlash [] ""= [""] + breakSlash s ""= s + breakSlash res ('\"':s)= + let (r,rest) = span(/= '\"') s + in breakSlash (res++[r]) $ tail1 rest + + breakSlash res s= + let (r,rest) = span(/= '/') s + in breakSlash (res++[r]) $ tail1 rest + + tail1 []=[] + tail1 x= tail x + + + + +-- | Wait for the execution of `exit` and return the result or the exhaustion of thread activity + +stay rexit= takeMVar rexit + `catch` \(e :: BlockedIndefinitelyOnMVar) -> return Nothing + +newtype Exit a= Exit a deriving Typeable + +-- | Keep the main thread running, initiate the non blocking keyboard input and execute +-- the transient computation. +-- +-- It also read a slash-separated list of string that are read by +-- `option` and `input` as if they were entered by the keyboard +-- +-- > foo -p options/to/be/read/by/option/and/input +keep :: Typeable a => TransIO a -> IO (Maybe a) +keep mx = do + + liftIO $ hSetBuffering stdout LineBuffering + rexit <- newEmptyMVar + forkIO $ do +-- liftIO $ putMVar rexit $ Right Nothing + runTransient $ do + st <- get + + setData $ Exit rexit + (async (return ()) >> labelState "input" >> liftIO inputLoop) + + <|> do + option "ps" "show threads" + liftIO $ showThreads st + empty + <|> do + option "log" "inspect the log of a thread" + th <- input (const True) "thread number>" + ml <- liftIO $ showState th st + liftIO $ print $ fmap (\(Log _ _ log) -> reverse log) ml + empty + <|> do + option "end" "exit" + killChilds + liftIO $ putMVar rexit Nothing + empty + <|> mx + return () + threadDelay 10000 + execCommandLine + stay rexit + + where + type1 :: TransIO a -> Either String (Maybe a) + type1= undefined + +-- | Same than `keep` but do not initiate the asynchronous keyboard input. +-- Useful for debugging or for creating background tasks, as well as to embed the Transient monad +-- inside another computation. It returns either the value returned by `exit`. +-- or Nothing, when there is no more threads running +keep' :: Typeable a => TransIO a -> IO (Maybe a) +keep' mx = do + liftIO $ hSetBuffering stdout LineBuffering + rexit <- newEmptyMVar + forkIO $ do + runTransient $ do + setData $ Exit rexit + mx + + return () + threadDelay 10000 + forkIO $ execCommandLine + stay rexit + + +execCommandLine= do + args <- getArgs + let mindex = findIndex (\o -> o == "-p" || o == "--path" ) args + when (isJust mindex) $ do + let i= fromJust mindex +1 + when (length args >= i) $ do + let path= args !! i + putStr "Executing: " >> print path + processLine path + +-- | Force the finalization of the main thread and thus, all the Transient block (and the application +-- if there is no more code) +exit :: Typeable a => a -> TransIO a +exit x= do + Exit rexit <- getSData <|> error "exit: not the type expected" `asTypeOf` type1 x + liftIO $ putMVar rexit $ Just x + stop + where + type1 :: a -> TransIO (Exit (MVar (Maybe a))) + type1= undefined + + + +-- | Alternative operator for maybe values. Used in infix mode +onNothing :: Monad m => m (Maybe b) -> m b -> m b +onNothing iox iox'= do + mx <- iox + case mx of + Just x -> return x + Nothing -> iox' + + + + + +----------------------------------backtracking ------------------------ + + +data Backtrack b= Show b =>Backtrack{backtracking :: Maybe b + ,backStack :: [EventF] } + deriving Typeable + + + +-- | Assures that backtracking will not go further back +backCut :: (Typeable reason, Show reason) => reason -> TransientIO () +backCut reason= Transient $ do + delData $ Backtrack (Just reason) [] + return $ Just () + +undoCut :: TransientIO () +undoCut = backCut () + +-- | The second parameter will be executed when backtracking +{-# NOINLINE onBack #-} +onBack :: (Typeable b, Show b) => TransientIO a -> ( b -> TransientIO a) -> TransientIO a +onBack ac bac = registerBack (typeof bac) $ Transient $ do + Backtrack mreason _ <- getData `onNothing` backStateOf (typeof bac) + runTrans $ case mreason of + Nothing -> ac + Just reason -> bac reason + where + typeof :: (b -> TransIO a) -> b + typeof = undefined + +onUndo :: TransientIO a -> TransientIO a -> TransientIO a +onUndo x y= onBack x (\() -> y) + + +-- | Register an action that will be executed when backtracking +{-# NOINLINE registerUndo #-} +registerBack :: (Typeable b, Show b) => b -> TransientIO a -> TransientIO a +registerBack witness f = Transient $ do + cont@(EventF _ _ x _ _ _ _ _ _ _ _ _) <- get -- !!> "backregister" + + md <- getData `asTypeOf` (Just <$> backStateOf witness) + + case md of + Just (bss@(Backtrack b (bs@((EventF _ _ x' _ _ _ _ _ _ _ _ _):_)))) -> + when (isNothing b) $ do + addrx <- addr x + addrx' <- addr x' -- to avoid duplicate backtracking points + setData $ if addrx == addrx' then bss else Backtrack mwit (cont:bs) + Nothing -> setData $ Backtrack mwit [cont] + + runTrans f + where + mwit= Nothing `asTypeOf` (Just witness) + addr x = liftIO $ return . hashStableName =<< (makeStableName $! x) + + +registerUndo :: TransientIO a -> TransientIO a +registerUndo f= registerBack () f + +-- | backtracking is stopped. the exection continues forward from this point on. +forward :: (Typeable b, Show b) => b -> TransIO () +forward reason= Transient $ do + Backtrack _ stack <- getData `onNothing` (backStateOf reason) + setData $ Backtrack(Nothing `asTypeOf` Just reason) stack + return $ Just () + +retry= forward () + +noFinish= forward (FinishReason Nothing) + +-- | Execute backtracking. It execute the registered actions in reverse order. +-- +-- If the backtracking flag is changed the flow proceed forward from that point on. +-- +-- If the backtrack stack is finished or undoCut executed, `undo` will stop. +back :: (Typeable b, Show b) => b -> TransientIO a +back reason = Transient $ do + bs <- getData `onNothing` backStateOf reason -- !!>"GOBACK" + goBackt bs + + where + + goBackt (Backtrack _ [] )= return Nothing -- !!> "END" + goBackt (Backtrack b (stack@(first : bs)) )= do + (setData $ Backtrack (Just reason) stack) + + mr <- runClosure first -- !> "RUNCLOSURE" + + Backtrack back _ <- getData `onNothing` backStateOf reason + -- !> "END RUNCLOSURE" +-- case back of +-- Nothing -> case mr of +-- Nothing -> return empty -- !> "FORWARD END" +-- Just x -> runContinuation first x -- !> "FORWARD EXEC" +-- justreason -> goBackt $ Backtrack justreason bs -- !> ("BACK AGAIN",back) + + case mr of + Nothing -> return empty -- !> "END EXECUTION" + Just x -> case back of + Nothing -> runContinuation first x -- !> "FORWARD EXEC" + justreason -> goBackt $ Backtrack justreason bs -- !> ("BACK AGAIN",back) + +backStateOf :: (Monad m, Show a, Typeable a) => a -> m (Backtrack a) +backStateOf reason= return $ Backtrack (Nothing `asTypeOf` (Just reason)) [] + +undo :: TransIO a +undo= back () + + +------ finalization + +newtype FinishReason= FinishReason (Maybe SomeException) deriving (Typeable, Show) + +-- | Initialize the event variable for finalization. +-- all the following computations in different threads will share it +-- it also isolate this event from other branches that may have his own finish variable +initFinish= backCut (FinishReason Nothing) + +-- | Set a computation to be called when the finish event happens +onFinish :: ((Maybe SomeException) ->TransIO ()) -> TransIO () +onFinish f= onFinish' (return ()) f + + +-- | Set a computation to be called when the finish event happens this only apply for +onFinish' ::TransIO a ->((Maybe SomeException) ->TransIO a) -> TransIO a +onFinish' proc f= proc `onBack` \(FinishReason reason) -> + f reason + + +-- | Trigger the event, so this closes all the resources +finish :: Maybe SomeException -> TransIO a +finish reason= back (FinishReason reason) + + + +-- | trigger finish when the stream of data ends +checkFinalize v= + case v of + SDone -> stop + SLast x -> return x + SError e -> back e + SMore x -> return x + +------ exceptions --- +-- | When a exception is produced in the continuation, the handler is executed. +-- | handlers are executed Last in first out. +onException :: Exception e => (e -> TransIO ()) -> TransIO () +onException f= onAnyException $ \e -> + case fromException e of + Nothing -> return () + Just e' -> f e' + where + onAnyException :: (SomeException ->TransIO ()) -> TransIO () + onAnyException f= (return ()) `onBack` f + +-- | stop the backtracking mechanism to execute further handlers +cutExceptions= backCut (undefined :: SomeException) + +-- | Resume to normal execution at this point +continue = forward (undefined :: SomeException) + +
src/Transient/Logged.hs view
@@ -1,179 +1,227 @@------------------------------------------------------------------------------------ Module : Transient.Logged--- Copyright :--- License : GPL-3------ Maintainer : agocorona@gmail.com--- Stability :--- Portability :------ |----------------------------------------------------------------------------------{-# LANGUAGE ExistentialQuantification, FlexibleInstances, ScopedTypeVariables, UndecidableInstances #-}-module Transient.Logged(restore,checkpoint,suspend,logged,Loggable) where--import Data.Typeable-import Unsafe.Coerce-import Transient.Base-import Transient.Indeterminism(choose)-import Transient.Internals(onNothing,reads1,IDynamic(..),Log(..),LogElem(..),RemoteStatus(..),StateIO)-import Control.Applicative-import Control.Monad.IO.Class-import System.Directory-import Control.Exception-import Control.Monad-import System.Random---class (Show a, Read a,Typeable a) => Loggable a-instance (Show a, Read a,Typeable a) => Loggable a--logs= "logs/"---- re-excutes all the threads whose state has been logged in the "./logs" folder--- .Each log is removed when it is executed.------ example: this program, if executed three times will first print hello <number> some times--- but `suspend` will kill the threads and exit it.---- The second time, it will print "world" <number> and "world22222" <number> and will stay.------ The third time that it is executed, it only present "world22222" <number> messages------ > main= keep $ restore $ do--- > r <- logged $ choose [1..10 :: Int]--- > logged $ liftIO $ print ("hello",r)--- > suspend ()--- > logged $ liftIO $ print ("world",r)--- > checkpoint--- > logged $ liftIO $ print ("world22222",r)--restore :: TransIO a -> TransIO a-restore proc= do- liftIO $ createDirectory logs `catch` (\(e :: SomeException) -> return ())- list <- liftIO $ getDirectoryContents logs- `catch` (\(e::SomeException) -> return [])- if length list== 2 then proc else do-- let list'= filter ((/=) '.' . head) list- file <- choose list' -- !> list'-- logstr <- liftIO $ readFile (logs++file)- let log= length logstr `seq` read' logstr-- log `seq` setData (Log True (reverse log) log)- liftIO $ remove $ logs ++ file- proc- where- read'= fst . head . reads1-- remove f= removeFile f `catch` (\(e::SomeException) -> remove f)------ | save the state of the thread that execute it and exit the transient block initiated with `keep` or similar--- . `keep` will return the value passed by `suspend`.--- If the process is executed again with `restore` it will reexecute the thread from this point on.------ it is useful to insert it in `finish` blocks to gather error information,-suspend :: Typeable a => a -> TransIO a-suspend x= do- Log recovery _ log <- getData `onNothing` return (Log False [] [])- if recovery then return x else do- logAll log- exit x---- | Save the state of every thread at this point. If the process is re-executed with `restore` it will reexecute the thread from this point on..-checkpoint :: TransIO ()-checkpoint = do- Log recovery _ log <- getData `onNothing` return (Log False [] [])- if recovery then return () else logAll log---logAll log= do- newlogfile <- liftIO $ (logs ++) <$> replicateM 7 (randomRIO ('a','z'))- liftIO $ writeFile newlogfile $ show log- :: TransIO ()------fromIDyn :: (Read a, Show a, Typeable a) => IDynamic -> a-fromIDyn (IDynamic x)=r where r= unsafeCoerce x -- !> "coerce" ++ " to type "++ show (typeOf r)--fromIDyn (IDyns s)=r `seq`r where r= read s -- !> "read " ++ s ++ " to type "++ show (typeOf r)--toIDyn x= IDynamic x--{- TODO add save/recover from log-rerun :: Log -> TransIO a -> TransIO a--getLog :: TransIO Log--}---- | write the result of the computation in the log and return it.--- but if there is data in the internal log, it read the data from the log and--- do not execute the computation.------ It accept nested step's. The effect is that if the outer step is executed completely--- the log of the inner steps are erased. If it is not the case, the inner steps are logged--- this reduce the log of large computations to the minimum. That is a feature not present--- in the package Workflow.------ > r <- logged $ do--- > logged this :: TransIO ()--- > logged that :: TransIO ()--- > logged thatOther--- > liftIO $ print r------ when `print` is executed, the log is just the value of r.------ but at the `thatOther` execution the log is: [Exec,(), ()]----logged :: Loggable a => TransientIO a -> TransientIO a-logged mx = Transient $ do- Log recover rs full <- getData `onNothing` return ( Log False [][])- runTrans $- case (recover ,rs) of -- !> ("logged enter",recover,rs) of- (True, Var x: rs') -> do- setData $ Log True rs' full- return $ fromIDyn x--- !> ("read in Var:", x)-- (True, Exec:rs') -> do- setData $ Log True rs' full- mx -- !> "Exec"-- (True, Wait:rs') -> do- setData (Log True rs' full) -- !> "Wait"- empty-- _ -> do--- let add= Exec: full- setData $ Log False (Exec : rs) (Exec: full) -- !> ("setLog False", Exec:rs)-- r <- mx <** ( do -- when p1 <|> p2, to avoid the re-execution of p1 at the- -- recovery when p1 is asynchronous- r <- getSData <|> return NoRemote- case r of- WasParallel ->--- let add= Wait: full- setData $ Log False (Wait: rs) (Wait: full)- _ -> return ())-- Log recoverAfter lognew _ <- getData `onNothing` return ( Log False [][])- let add= Var (toIDyn r): full- if recoverAfter && (not $ null lognew) -- !> ("recoverAfter", recoverAfter)- then (setData $ Log True lognew (reverse lognew ++ add) )- -- !> ("recover",reverse lognew ,add)- else if recoverAfter && (null lognew) then- setData $ Log False [] add- else- (setData $ Log False (Var (toIDyn r):rs) add) -- !> ("restore", (Var (toIDyn r):rs))- return r---+----------------------------------------------------------------------------- +-- +-- Module : Transient.Logged +-- Copyright : +-- License : GPL-3 +-- +-- Maintainer : agocorona@gmail.com +-- Stability : +-- Portability : +-- +-- | +-- +----------------------------------------------------------------------------- +{-# LANGUAGE CPP,ExistentialQuantification, FlexibleInstances, ScopedTypeVariables, UndecidableInstances #-} +module Transient.Logged( + +#ifndef ghcjs_HOST_OS +restore,checkpoint,suspend, +#endif + +logged, received,param, Loggable) where + +import Data.Typeable +import Unsafe.Coerce +import Transient.Base +import Transient.Internals(Loggable) +import Transient.Indeterminism(choose) +import Transient.Internals(onNothing,reads1,IDynamic(..),Log(..),LogElem(..),RemoteStatus(..),StateIO) +import Control.Applicative +import Control.Monad.IO.Class +import System.Directory +import Control.Exception +import Control.Monad + + + +#ifndef ghcjs_HOST_OS +import System.Random +#endif + + + +#ifndef ghcjs_HOST_OS +logs= "logs/" + +-- re-excutes all the threads whose state has been logged in the "./logs" folder +-- .Each log is removed when it is executed. +-- +-- example: this program, if executed three times will first print hello <number> some times +-- but `suspend` will kill the threads and exit it. + +-- The second time, it will print "world" <number> and "world22222" <number> and will stay. +-- +-- The third time that it is executed, it only present "world22222" <number> messages +-- +-- > main= keep $ restore $ do +-- > r <- logged $ choose [1..10 :: Int] +-- > logged $ liftIO $ print ("hello",r) +-- > suspend () +-- > logged $ liftIO $ print ("world",r) +-- > checkpoint +-- > logged $ liftIO $ print ("world22222",r) + +restore :: TransIO a -> TransIO a +restore proc= do + liftIO $ createDirectory logs `catch` (\(e :: SomeException) -> return ()) + list <- liftIO $ getDirectoryContents logs + `catch` (\(e::SomeException) -> return []) + if length list== 2 then proc else do + + let list'= filter ((/=) '.' . head) list + file <- choose list' -- !> list' + + logstr <- liftIO $ readFile (logs++file) + let log= length logstr `seq` read' logstr + + log `seq` setData (Log True (reverse log) log) + liftIO $ remove $ logs ++ file + proc + where + read'= fst . head . reads1 + + remove f= removeFile f `catch` (\(e::SomeException) -> remove f) + + + +-- | save the state of the thread that execute it and exit the transient block initiated with `keep` or similar +-- . `keep` will return the value passed by `suspend`. +-- If the process is executed again with `restore` it will reexecute the thread from this point on. +-- +-- it is useful to insert it in `finish` blocks to gather error information, +suspend :: Typeable a => a -> TransIO a +suspend x= do + Log recovery _ log <- getData `onNothing` return (Log False [] []) + if recovery then return x else do + logAll log + exit x + +-- | Save the state of every thread at this point. If the process is re-executed with `restore` it will reexecute the thread from this point on.. +checkpoint :: TransIO () +checkpoint = do + Log recovery _ log <- getData `onNothing` return (Log False [] []) + if recovery then return () else logAll log + + +logAll log= do + newlogfile <- liftIO $ (logs ++) <$> replicateM 7 (randomRIO ('a','z')) + liftIO $ writeFile newlogfile $ show log + :: TransIO () + +#endif + + +fromIDyn :: Loggable a => IDynamic -> a +fromIDyn (IDynamic x)=r where r= unsafeCoerce x -- !> "coerce" ++ " to type "++ show (typeOf r) + +fromIDyn (IDyns s)=r `seq`r where r= read s -- !> "read " ++ s ++ " to type "++ show (typeOf r) + + + +toIDyn x= IDynamic x + + + +-- | write the result of the computation in the log and return it. +-- but if there is data in the internal log, it read the data from the log and +-- do not execute the computation. +-- +-- It accept nested step's. The effect is that if the outer step is executed completely +-- the log of the inner steps are erased. If it is not the case, the inner steps are logged +-- this reduce the log of large computations to the minimum. That is a feature not present +-- in the package Workflow. +-- +-- > r <- logged $ do +-- > logged this :: TransIO () +-- > logged that :: TransIO () +-- > logged thatOther +-- > liftIO $ print r +-- +-- when `print` is executed, the log is just the value of r. +-- +-- but at the `thatOther` execution the log is: [Exec,(), ()] +-- +logged :: Loggable a => TransientIO a -> TransientIO a +logged mx = Transient $ do + Log recover rs full <- getData `onNothing` return ( Log False [][]) + runTrans $ + case (recover ,rs) of -- !> ("logged enter",recover,rs) of + (True, Var x: rs') -> do + setData $ Log True rs' full + return $ fromIDyn x +-- !> ("Var:", x) + + (True, Exec:rs') -> do + setData $ Log True rs' full + mx +-- !> "Exec" + + (True, Wait:rs') -> do + setData (Log True rs' full) -- !> "Wait" + empty + + _ -> do +-- let add= Exec: full + setData $ Log False (Exec : rs) (Exec: full) -- !> ("setLog False", Exec:rs) + + r <- mx <** ( do -- when p1 <|> p2, to avoid the re-execution of p1 at the + -- recovery when p1 is asynchronous + r <- getSData <|> return NoRemote + case r of + WasParallel -> +-- let add= Wait: full + setData $ Log False (Wait: rs) (Wait: full) + _ -> return ()) + + Log recoverAfter lognew _ <- getData `onNothing` return ( Log False [][]) + let add= Var (toIDyn r): full + if recoverAfter && (not $ null lognew) -- !> ("recoverAfter", recoverAfter) + then (setData $ Log True lognew (reverse lognew ++ add) ) + -- !> ("recover",reverse lognew ,add) + else if recoverAfter && (null lognew) then + setData $ Log False [] add + else + (setData $ Log False (Var (toIDyn r):rs) add) -- !> ("restore", (Var (toIDyn r):rs)) + return r + + + + +-------- parsing the log for API's + +received :: Loggable a => a -> TransIO () +received n=Transient $ do + Log recover rs full <- getData `onNothing` return ( Log False [][]) + case rs of + [] -> return Nothing + Var (IDyns s):t -> if s == show1 n + then do + setData $ Log recover t full + return $ Just () + else return Nothing + _ -> return Nothing + where + show1 x= if typeOf x == typeOf "" then unsafeCoerce x else show x + +param :: Loggable a => TransIO a +param= res where + res= Transient $ do + Log recover rs full <- getData `onNothing` return ( Log False [][]) + case rs of + [] -> return Nothing + Var (IDynamic v):t ->do + setData $ Log recover t full + return $ cast v + Var (IDyns s):t -> do + let mr = reads1 s `asTypeOf` type1 res + + case mr of + [] -> return Nothing + (v,r):_ -> do + setData $ Log recover t full + return $ Just v + _ -> return Nothing + + where + type1 :: TransIO a -> [(a,String)] + type1= error "type1: typelevel"
− src/Transient/Stream/Resource.hs
@@ -1,76 +0,0 @@------------------------------------------------------------------------------------ Module : Transient.Stream.Resource--- Copyright :--- License : GPL-3------ Maintainer : agocorona@gmail.com--- Stability :--- Portability :------ |----------------------------------------------------------------------------------{-# LANGUAGE ScopedTypeVariables, DeriveDataTypeable #-}-module Transient.Stream.Resource(sourceFile, sinkFile, process, initFinish, finish, onFinish) where---import Transient.Base hiding (loop)-import Transient.Backtrack-import Control.Exception-import Control.Applicative-import Data.Typeable-import Data.Char-import System.IO--import Control.Concurrent--import Control.Concurrent.STM-import Control.Monad.State------ | Stream the input to a file-sinkFile :: TransIO String -> String -> TransIO ()-sinkFile input file= process input (openFile file WriteMode) hClose' hPutStrLn'- where- hClose' h _= putStr "closing " >> putStrLn file >> hClose h- hPutStrLn' h x= liftIO $ (SMore <$> hPutStrLn h x)- `catch` (\(e::SomeException)-> return $ SError e)---- | slurp input from a file a line at a time. It creates as much threads as possible.--- to allow single threaded processing, use it with `threads 0`-sourceFile :: String -> TransIO String-sourceFile file= process (return ()) (openFile file ReadMode) hClose' read'- where- hGetLine' h= (SMore <$> hGetLine h)- `catch` (\(e::SomeException)-> return $ SError e)- read' h _ = parallel $ hGetLine' h--- hClose' h _= putStr "closing ">> putStrLn file >> hClose h---- | is the general operation for processing a streamed input, with opening resources before--- processing and closing them when finish is called. The process statements suscribe to the--- `Finish` EVar.------ When this variable is updated, the close procedure is called.------ When the processing return `SDone` or `SError`, the `Finish` variable is updated so all the--- subscribed code, that close the resources, is executed.-process- :: TransIO a -- ^ input computation- -> IO handle -- ^ open computation that gives resources to be used during the computation- -> (handle -> Maybe SomeException -> IO ()) -- ^ close computation that frees the resources- -> (handle -> a -> TransIO (StreamData b)) -- ^ process to be done- -> TransIO b-process input open close proc=do- mh <- liftIO $ (Right <$> open) `catch` (\(e::SomeException)-> return $ Left e)- case mh of- Left e -> liftIO (putStr "process: " >> print e) >> finish (Just e) >> stop- Right h -> do- onFinish (liftIO . close h)- some <- input- v <- proc h some- liftIO $ myThreadId >>= print- checkFinalize v
transient.cabal view
@@ -1,51 +1,55 @@-name: transient--version: 0.4.4.1--author: Alberto G. Corona-maintainer: agocorona@gmail.com--cabal-version: >=1.10--build-type: Simple--license: MIT-license-file: LICENSE--homepage: http://www.fpcomplete.com/user/agocorona-bug-reports: https://github.com/agocorona/transient/issues--synopsis: Making composable programs with multithreading, events and distributed computing-description: See <http://github.com/agocorona/transient>- In this release distributed primitives have been moved to the transient-universe package, and web primitives have been moved to the ghcjs-hplay package.-category: Control--data-dir: ""---library- build-depends: base > 4 && < 5- , containers- , mtl- , transformers- , stm- , time- , directory- , random-- exposed-modules: Transient.Backtrack- Transient.Base- Transient.EVars- Transient.Indeterminism- Transient.Internals- Transient.Logged- Transient.Stream.Resource- exposed: True- buildable: True- exposed: True- default-language: Haskell2010- hs-source-dirs: src .--source-repository head- type: git- location: https://github.com/agocorona/transient+name: transient + +version: 0.5.1 + + +author: Alberto G. Corona +extra-source-files: + ChangeLog.md README.md + +cabal-version: >=1.10 +build-type: Simple + +license: MIT +license-file: LICENSE + +maintainer: agocorona@gmail.com +homepage: http://www.fpcomplete.com/user/agocorona +bug-reports: https://github.com/agocorona/transient/issues + +synopsis: Making composable programs with multithreading, events and distributed computing +description: See <http://github.com/agocorona/transient> + In this release distributed primitives have been moved to the transient-universe package, and web primitives have been moved to the ghcjs-hplay package. +category: Control +data-dir: "" + + +library + build-depends: base > 4 && < 5 + , containers + , mtl + , transformers + , stm + , time + , directory + , random + , bytestring + + + exposed-modules: Transient.Backtrack + Transient.Base + Transient.EVars + Transient.Indeterminism + Transient.Internals + Transient.Logged + + exposed: True + buildable: True + exposed: True + default-language: Haskell2010 + hs-source-dirs: src . + + +source-repository head + type: git + location: https://github.com/agocorona/transient