{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StaticPointers #-}
{-# LANGUAGE TypeApplications #-}
module Network.Serverless.Execute.Internal
( const_SERVERLESS_EXECUTOR_MODE
, initServerless
, Backend (..)
, BackendM (..)
, ExecutorStatus (..)
, ExecutorPendingStatus (..)
, ExecutorFinalStatus (..)
, runBackend
) where
import Control.Concurrent
import Control.Concurrent.STM
import Control.Distributed.Closure
import Control.Monad.Catch
import Control.Monad.IO.Class
import Control.Monad.Trans.Reader
import Data.Binary
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BL
import Data.Monoid ((<>))
import Data.Text (Text)
import qualified Data.Text as T
import Data.Void
import System.Environment
import System.Exit
import System.IO
import GHC.Generics
const_SERVERLESS_EXECUTOR_MODE :: String
const_SERVERLESS_EXECUTOR_MODE = "SERVERLESS_EXECUTOR_MODE"
initServerless :: IO ()
initServerless = do
getArgs >>= \case
[x]
| x == const_SERVERLESS_EXECUTOR_MODE -> vacuous runExecutor
_ -> return ()
runExecutor :: IO Void
runExecutor = do
BL.hGetContents stdin
>>= unclosure . decode @ExecutorClosure
>> exitWith ExitSuccess
type ExecutorClosure = Closure (IO ())
data Backend = Backend
{ bExecute :: BS.ByteString -> BackendM BS.ByteString
}
newtype BackendM a =
BackendM (ReaderT (ExecutorPendingStatus -> IO ()) IO a)
deriving (Functor, Applicative, Monad, MonadIO, MonadCatch, MonadThrow)
data ExecutorStatus a
= ExecutorPending ExecutorPendingStatus
| ExecutorFinished (ExecutorFinalStatus a)
data ExecutorPendingStatus
= ExecutorWaiting (Maybe Text)
| ExecutorSubmitted (Maybe Text)
| ExecutorStarted (Maybe Text)
data ExecutorFinalStatus a
= ExecutorFailed Text
| ExecutorSucceeded a
deriving (Generic)
instance Binary a => Binary (ExecutorFinalStatus a)
runBackend ::
Closure (Dict (Serializable i))
-> Closure (IO i)
-> Backend
-> IO (TVar (ExecutorStatus i))
runBackend dict cls (Backend backend) =
case unclosure dict of
Dict -> do
let BackendM m =
backend $ BL.toStrict $ encode @ExecutorClosure (toExecutorClosure dict cls)
t <- atomically (newTVar $ ExecutorPending (ExecutorWaiting Nothing))
_ <-
forkIO $ do
r <-
either
(\(err :: SomeException) ->
ExecutorFailed $
"Backend threw an exception: " <> T.pack (show err))
parseAnswer <$>
try (runReaderT m (atomically . writeTVar t . ExecutorPending))
atomically $ writeTVar t (ExecutorFinished r)
return ()
return t
toExecutorClosure :: Closure (Dict (Serializable a)) -> Closure (IO a) -> Closure (IO ())
toExecutorClosure dict cls =
case unclosure dict of
Dict -> static run `cap` dict `cap` cls
where
run :: forall a. Dict (Serializable a) -> IO a -> IO ()
run Dict a =
(a >>= BL.putStr . encode . ExecutorSucceeded)
`catch` (\(ex :: SomeException) ->
BL.putStr . encode . ExecutorFailed @a $
"Exception from executor: " <> T.pack (show ex))
parseAnswer :: Binary a => BS.ByteString -> ExecutorFinalStatus a
parseAnswer bs =
case decodeOrFail (BL.fromStrict bs) of
Left (_, _, err) -> ExecutorFailed $ "Error decoding answer: " <> T.pack err
Right (_, _, a) -> a