module Mealstrom.FSMApi where
import Control.Concurrent
import Control.Exception
import Control.Monad (void)
import qualified Data.Text as Text
import System.IO
import System.Timeout
import Mealstrom.FSM
import Mealstrom.FSMEngine
import Mealstrom.FSMStore
import Mealstrom.FSMTable
import Mealstrom.WALStore
data FSMHandle st wal k s e a where
FSMHandle :: (Eq s, Eq e, Eq a, FSMStore st k s e a, WALStore wal k, FSMKey k) => {
fsmStore :: st,
walStore :: wal,
fsmTable :: FSMTable s e a,
effTimeout :: Int,
retryCount :: Int
} -> FSMHandle st wal k s e a
get :: forall st wal k s e a . FSMStore st k s e a => FSMHandle st wal k s e a -> k -> IO(Maybe s)
get FSMHandle{..} k = fsmRead fsmStore k (Proxy :: Proxy k s e a)
post :: forall st wal k s e a . FSMStore st k s e a =>
FSMHandle st wal k s e a ->
k ->
s -> IO Bool
post FSMHandle{..} k s0 =
fsmCreate fsmStore (mkInstance k s0 [] :: Instance k s e a) >>= \case
Nothing -> return True
Just s -> hPutStrLn stderr s >> return False
patch :: forall st wal k s e a . (FSMStore st k s e a, MealyInstance k s e a, FSMKey k) => FSMHandle st wal k s e a -> k -> [Msg e] -> IO Bool
patch h@FSMHandle{..} k es = do
openTxn walStore k
status <- handle (\(e::SomeException) -> hPutStrLn stderr (show e) >> return MealyError)
(fsmUpdate fsmStore k ((patchPhase1 fsmTable es) :: MachineTransformer s e a))
if status /= MealyError
then recover h k >> return True
else return False
recover :: forall st wal k s e a . (FSMStore st k s e a, MealyInstance k s e a, FSMKey k) => FSMHandle st wal k s e a -> k -> IO ()
recover h@FSMHandle{..} k
| retryCount == 0 = hPutStrLn stderr $ "Alarma! Recovery retries for " ++ Text.unpack (toText k) ++ " exhausted. Giving up!"
| otherwise =
void $ forkFinally (timeout (effTimeout*10^6) (fsmUpdate fsmStore k (patchPhase2 fsmTable :: MachineTransformer s e a)))
(\case Left exn -> do
hPutStrLn stderr $ "Exception occurred while trying to recover " ++ Text.unpack (toText k)
hPrint stderr exn
recover h{retryCount = retryCount 1} k
Right Nothing -> do
hPutStrLn stderr $ "Timeout while trying to recover " ++ Text.unpack (toText k)
recover h{retryCount = retryCount 1} k
Right (Just Done) -> closeTxn walStore k
Right (Just Pending) ->
recover h{retryCount = retryCount 1} k)
recoverAll :: forall st wal k s e a . (MealyInstance k s e a) => FSMHandle st wal k s e a -> IO ()
recoverAll h@FSMHandle{..} = do
wals <- walScan walStore effTimeout
mapM_ (recover h . walId) wals
upsert :: forall st wal k s e a . MealyInstance k s e a => FSMStore st k s e a =>
FSMHandle st wal k s e a -> k -> s -> [Msg e] -> IO ()
upsert h k s es = do
ms <- get h k
maybe (post h k s >> void (patch h k es))
(\_s -> void $ patch h k es)
ms