core: use concrete monads
This commit is contained in:
parent
4ea6c721c5
commit
57ea8696e7
3 changed files with 28 additions and 25 deletions
|
@ -10,7 +10,6 @@ module Myriad.Core
|
||||||
, DefaultLanguageConfig(..)
|
, DefaultLanguageConfig(..)
|
||||||
, LanguageConfig(..)
|
, LanguageConfig(..)
|
||||||
, MyriadT
|
, MyriadT
|
||||||
, Myriadic
|
|
||||||
, runMyriadT
|
, runMyriadT
|
||||||
, initEnv
|
, initEnv
|
||||||
, exec
|
, exec
|
||||||
|
@ -134,8 +133,6 @@ instance MonadBaseControl b m => MonadBaseControl b (MyriadT m) where
|
||||||
liftBaseWith = defaultLiftBaseWith
|
liftBaseWith = defaultLiftBaseWith
|
||||||
restoreM = defaultRestoreM
|
restoreM = defaultRestoreM
|
||||||
|
|
||||||
type Myriadic m = (MonadReader Env m, MonadLogger m, MonadLoggerIO m, MonadIO m, MonadBase IO m, MonadBaseControl IO m)
|
|
||||||
|
|
||||||
readConfig :: FilePath -> IO MyriadConfig
|
readConfig :: FilePath -> IO MyriadConfig
|
||||||
readConfig f = do
|
readConfig f = do
|
||||||
x <- BL.readFile f
|
x <- BL.readFile f
|
||||||
|
|
|
@ -26,7 +26,9 @@ import System.Process.Typed
|
||||||
|
|
||||||
import Myriad.Core
|
import Myriad.Core
|
||||||
|
|
||||||
buildImage :: Myriadic m => LanguageConfig -> m ()
|
type Myriad = MyriadT IO
|
||||||
|
|
||||||
|
buildImage :: LanguageConfig -> Myriad ()
|
||||||
buildImage lang@LanguageConfig { name, concurrent } = do
|
buildImage lang@LanguageConfig { name, concurrent } = do
|
||||||
Env { config = MyriadConfig { prepareContainers }, languagesDir } <- ask
|
Env { config = MyriadConfig { prepareContainers }, languagesDir } <- ask
|
||||||
logInfo ["Building image ", cs $ imageName lang]
|
logInfo ["Building image ", cs $ imageName lang]
|
||||||
|
@ -35,7 +37,7 @@ buildImage lang@LanguageConfig { name, concurrent } = do
|
||||||
logInfo ["Built image ", cs $ imageName lang]
|
logInfo ["Built image ", cs $ imageName lang]
|
||||||
when_ prepareContainers $ setupContainer lang
|
when_ prepareContainers $ setupContainer lang
|
||||||
where
|
where
|
||||||
setupQSems :: Myriadic m => m ()
|
setupQSems :: Myriad ()
|
||||||
setupQSems = do
|
setupQSems = do
|
||||||
Env { config = MyriadConfig { defaultLanguage }, containerSems, evalSems } <- ask
|
Env { config = MyriadConfig { defaultLanguage }, containerSems, evalSems } <- ask
|
||||||
csem <- newQSem 1 -- We only want one container to be set up at a time
|
csem <- newQSem 1 -- We only want one container to be set up at a time
|
||||||
|
@ -43,35 +45,35 @@ buildImage lang@LanguageConfig { name, concurrent } = do
|
||||||
mapMVar containerSems $ M.insert name csem
|
mapMVar containerSems $ M.insert name csem
|
||||||
mapMVar evalSems $ M.insert name esem
|
mapMVar evalSems $ M.insert name esem
|
||||||
|
|
||||||
buildAllImages :: Myriadic m => m ()
|
buildAllImages :: Myriad ()
|
||||||
buildAllImages = do
|
buildAllImages = do
|
||||||
MyriadConfig { languages, buildConcurrently } <- asks config
|
MyriadConfig { languages, buildConcurrently } <- asks config
|
||||||
if buildConcurrently
|
if buildConcurrently
|
||||||
then forConcurrently_ languages buildImage
|
then forConcurrently_ languages buildImage
|
||||||
else forM_ languages buildImage
|
else forM_ languages buildImage
|
||||||
|
|
||||||
startCleanup :: Myriadic m => m ()
|
startCleanup :: Myriad ()
|
||||||
startCleanup = do
|
startCleanup = do
|
||||||
MyriadConfig { cleanupInterval } <- asks config
|
MyriadConfig { cleanupInterval } <- asks config
|
||||||
when_ (cleanupInterval > 0) do
|
when_ (cleanupInterval > 0) do
|
||||||
let t = fromIntegral cleanupInterval * 60000000
|
let t = fromIntegral cleanupInterval * 60000000
|
||||||
fork $ timer t
|
fork $ timer t
|
||||||
where
|
where
|
||||||
timer :: Int -> Myriadic m => m ()
|
timer :: Int -> Myriad ()
|
||||||
timer t = forever do
|
timer t = forever do
|
||||||
threadDelay t
|
threadDelay t
|
||||||
n <- killContainers
|
n <- killContainers
|
||||||
logInfo ["Cleaned up ", cs $ show n, " containers"]
|
logInfo ["Cleaned up ", cs $ show n, " containers"]
|
||||||
timer t
|
timer t
|
||||||
|
|
||||||
setupContainer :: Myriadic m => LanguageConfig -> m ContainerName
|
setupContainer :: LanguageConfig -> Myriad ContainerName
|
||||||
setupContainer lang@LanguageConfig { name, memory, cpus } = do
|
setupContainer lang@LanguageConfig { name, memory, cpus } = do
|
||||||
cnts <- asks containers >>= readMVar
|
cnts <- asks containers >>= readMVar
|
||||||
case cnts M.!? name of
|
case cnts M.!? name of
|
||||||
Nothing -> setup
|
Nothing -> setup
|
||||||
Just cnt -> pure cnt
|
Just cnt -> pure cnt
|
||||||
where
|
where
|
||||||
setup :: Myriadic m => m ContainerName
|
setup :: Myriad ContainerName
|
||||||
setup = do
|
setup = do
|
||||||
Env { config = MyriadConfig { defaultLanguage }, containers = ref } <- ask
|
Env { config = MyriadConfig { defaultLanguage }, containers = ref } <- ask
|
||||||
cnt <- newContainerName lang
|
cnt <- newContainerName lang
|
||||||
|
@ -97,7 +99,7 @@ setupContainer lang@LanguageConfig { name, memory, cpus } = do
|
||||||
logInfo ["Started container ", cs cnt]
|
logInfo ["Started container ", cs cnt]
|
||||||
pure cnt
|
pure cnt
|
||||||
|
|
||||||
killContainer :: Myriadic m => Language -> m Bool
|
killContainer :: Language -> Myriad Bool
|
||||||
killContainer lang = do
|
killContainer lang = do
|
||||||
containers <- asks containers >>= readMVar
|
containers <- asks containers >>= readMVar
|
||||||
case containers M.!? lang of
|
case containers M.!? lang of
|
||||||
|
@ -110,7 +112,7 @@ killContainer lang = do
|
||||||
logError ["An exception occured when killing ", cs cnt, ":\n", cs $ show err]
|
logError ["An exception occured when killing ", cs cnt, ":\n", cs $ show err]
|
||||||
pure False
|
pure False
|
||||||
where
|
where
|
||||||
kill :: Myriadic m => ContainerName -> m (Maybe SomeException)
|
kill :: ContainerName -> Myriad (Maybe SomeException)
|
||||||
kill cnt = do
|
kill cnt = do
|
||||||
ref <- asks containers
|
ref <- asks containers
|
||||||
mapMVar ref $ M.delete lang
|
mapMVar ref $ M.delete lang
|
||||||
|
@ -121,13 +123,13 @@ killContainer lang = do
|
||||||
logInfo ["Killed container ", cs cnt]
|
logInfo ["Killed container ", cs cnt]
|
||||||
pure Nothing
|
pure Nothing
|
||||||
|
|
||||||
killContainers :: Myriadic m => m [ContainerName]
|
killContainers :: Myriad [ContainerName]
|
||||||
killContainers = do
|
killContainers = do
|
||||||
containers <- asks containers >>= readMVar
|
containers <- asks containers >>= readMVar
|
||||||
xs <- forConcurrently (M.toList containers) \(k, v) -> (v,) <$> killContainer k
|
xs <- forConcurrently (M.toList containers) \(k, v) -> (v,) <$> killContainer k
|
||||||
pure . map fst $ filter snd xs
|
pure . map fst $ filter snd xs
|
||||||
|
|
||||||
evalCode :: Myriadic m => LanguageConfig -> Int -> String -> m EvalResult
|
evalCode :: LanguageConfig -> Int -> String -> Myriad EvalResult
|
||||||
evalCode lang@LanguageConfig { name, timeout, retries } numRetries code = withContainer \cnt -> do
|
evalCode lang@LanguageConfig { name, timeout, retries } numRetries code = withContainer \cnt -> do
|
||||||
doneRef <- newMVar False -- For keeping track of if the evaluation is done, i.e. succeeded or timed out.
|
doneRef <- newMVar False -- For keeping track of if the evaluation is done, i.e. succeeded or timed out.
|
||||||
void . fork $ timer doneRef -- `race` could not have been used here since some evals can't be cancelled.
|
void . fork $ timer doneRef -- `race` could not have been used here since some evals can't be cancelled.
|
||||||
|
@ -157,7 +159,7 @@ evalCode lang@LanguageConfig { name, timeout, retries } numRetries code = withCo
|
||||||
writeMVar doneRef True
|
writeMVar doneRef True
|
||||||
pure x
|
pure x
|
||||||
where
|
where
|
||||||
withContainer :: Myriadic m => (ContainerName -> m a) -> m a
|
withContainer :: (ContainerName -> Myriad a) -> Myriad a
|
||||||
withContainer f = do
|
withContainer f = do
|
||||||
Env { containerSems, evalSems } <- ask
|
Env { containerSems, evalSems } <- ask
|
||||||
csem <- (M.! name) <$> readMVar containerSems
|
csem <- (M.! name) <$> readMVar containerSems
|
||||||
|
@ -166,7 +168,7 @@ evalCode lang@LanguageConfig { name, timeout, retries } numRetries code = withCo
|
||||||
cnt <- bracket_ (waitQSem csem) (signalQSem csem) $ setupContainer lang
|
cnt <- bracket_ (waitQSem csem) (signalQSem csem) $ setupContainer lang
|
||||||
f cnt
|
f cnt
|
||||||
|
|
||||||
timer :: Myriadic m => MVar Bool -> m ()
|
timer :: MVar Bool -> Myriad ()
|
||||||
timer doneRef = do
|
timer doneRef = do
|
||||||
Env { config = MyriadConfig { defaultLanguage } } <- ask
|
Env { config = MyriadConfig { defaultLanguage } } <- ask
|
||||||
threadDelay $ (fromIntegral $ fromMaybe (defTimeout defaultLanguage) timeout) * 1000000
|
threadDelay $ (fromIntegral $ fromMaybe (defTimeout defaultLanguage) timeout) * 1000000
|
||||||
|
@ -175,7 +177,7 @@ evalCode lang@LanguageConfig { name, timeout, retries } numRetries code = withCo
|
||||||
writeMVar doneRef True
|
writeMVar doneRef True
|
||||||
killContainer name
|
killContainer name
|
||||||
|
|
||||||
eval :: Myriadic m => ContainerName -> Snowflake -> m EvalResult
|
eval :: ContainerName -> Snowflake -> Myriad EvalResult
|
||||||
eval cnt snowflake = do
|
eval cnt snowflake = do
|
||||||
logInfo ["Running code in container ", cs cnt, ", evaluation ", cs $ show snowflake, ":\n", cs code]
|
logInfo ["Running code in container ", cs cnt, ", evaluation ", cs $ show snowflake, ":\n", cs code]
|
||||||
exec_ ["docker exec ", cs cnt, " mkdir eval/", show snowflake]
|
exec_ ["docker exec ", cs cnt, " mkdir eval/", show snowflake]
|
||||||
|
@ -188,7 +190,7 @@ evalCode lang@LanguageConfig { name, timeout, retries } numRetries code = withCo
|
||||||
logInfo ["Ran code in container ", cs cnt, ", evaluation ", cs $ show snowflake]
|
logInfo ["Ran code in container ", cs cnt, ", evaluation ", cs $ show snowflake]
|
||||||
pure $ EvalOk output
|
pure $ EvalOk output
|
||||||
|
|
||||||
newContainerName :: Myriadic m => LanguageConfig -> m ContainerName
|
newContainerName :: LanguageConfig -> Myriad ContainerName
|
||||||
newContainerName LanguageConfig { name } = do
|
newContainerName LanguageConfig { name } = do
|
||||||
snowflakeGen <- asks snowflakeGen
|
snowflakeGen <- asks snowflakeGen
|
||||||
snowflake <- liftIO $ nextSnowflake snowflakeGen
|
snowflake <- liftIO $ nextSnowflake snowflakeGen
|
||||||
|
|
|
@ -21,6 +21,8 @@ import Servant
|
||||||
import Myriad.Core
|
import Myriad.Core
|
||||||
import Myriad.Docker
|
import Myriad.Docker
|
||||||
|
|
||||||
|
type Myriad = MyriadT Handler
|
||||||
|
|
||||||
data EvalRequest = EvalRequest { language :: T.Text, code :: String } deriving (Generic, FromJSON)
|
data EvalRequest = EvalRequest { language :: T.Text, code :: String } deriving (Generic, FromJSON)
|
||||||
data EvalResponse = EvalResponse { result :: T.Text } deriving (Generic, ToJSON)
|
data EvalResponse = EvalResponse { result :: T.Text } deriving (Generic, ToJSON)
|
||||||
|
|
||||||
|
@ -36,35 +38,37 @@ app = serve (Proxy @API) . server
|
||||||
server :: Env -> Server API
|
server :: Env -> Server API
|
||||||
server env = hoistServer (Proxy @API) (runMyriadT env) serverT
|
server env = hoistServer (Proxy @API) (runMyriadT env) serverT
|
||||||
|
|
||||||
serverT :: ServerT API (MyriadT Handler)
|
serverT :: ServerT API Myriad
|
||||||
serverT = handleLanguages :<|> handleEval :<|> handleContainers :<|> handleCleanup
|
serverT = handleLanguages :<|> handleEval :<|> handleContainers :<|> handleCleanup
|
||||||
where
|
where
|
||||||
handleLanguages :: MyriadT Handler [T.Text]
|
handleLanguages :: Myriad [T.Text]
|
||||||
handleLanguages = do
|
handleLanguages = do
|
||||||
logInfo ["GET /languages"]
|
logInfo ["GET /languages"]
|
||||||
MyriadConfig { languages } <- asks config
|
MyriadConfig { languages } <- asks config
|
||||||
pure . map name $ languages
|
pure . map name $ languages
|
||||||
|
|
||||||
handleEval :: EvalRequest -> MyriadT Handler EvalResponse
|
handleEval :: EvalRequest -> Myriad EvalResponse
|
||||||
handleEval EvalRequest { language, code } = do
|
handleEval EvalRequest { language, code } = do
|
||||||
logInfo ["POST /eval"]
|
logInfo ["POST /eval"]
|
||||||
MyriadConfig { languages } <- asks config
|
MyriadConfig { languages } <- asks config
|
||||||
case find (\x -> name x == language) languages of
|
case find (\x -> name x == language) languages of
|
||||||
Nothing -> throwError $ err404 { errBody = "Language " <> cs language <> " was not found" }
|
Nothing -> throwError $ err404 { errBody = "Language " <> cs language <> " was not found" }
|
||||||
Just cfg -> do
|
Just cfg -> do
|
||||||
res <- withAsync (evalCode cfg 0 $ cs code) wait
|
env <- ask
|
||||||
|
res <- withAsync (liftIO . runMyriadT env . evalCode cfg 0 $ cs code) wait
|
||||||
case res of
|
case res of
|
||||||
EvalErrored -> throwError $ err500 { errBody = "Evaluation failed" }
|
EvalErrored -> throwError $ err500 { errBody = "Evaluation failed" }
|
||||||
EvalTimedOut -> throwError $ err504 { errBody = "Evaluation timed out" }
|
EvalTimedOut -> throwError $ err504 { errBody = "Evaluation timed out" }
|
||||||
EvalOk xs -> pure . EvalResponse $ cs xs
|
EvalOk xs -> pure . EvalResponse $ cs xs
|
||||||
|
|
||||||
handleContainers :: MyriadT Handler [T.Text]
|
handleContainers :: Myriad [T.Text]
|
||||||
handleContainers = do
|
handleContainers = do
|
||||||
logInfo ["GET /containers"]
|
logInfo ["GET /containers"]
|
||||||
containers <- asks containers >>= readMVar
|
containers <- asks containers >>= readMVar
|
||||||
pure . map cs $ M.elems containers
|
pure . map cs $ M.elems containers
|
||||||
|
|
||||||
handleCleanup :: MyriadT Handler [T.Text]
|
handleCleanup :: Myriad [T.Text]
|
||||||
handleCleanup = do
|
handleCleanup = do
|
||||||
logInfo ["POST /cleanup"]
|
logInfo ["POST /cleanup"]
|
||||||
map cs <$> killContainers
|
env <- ask
|
||||||
|
liftIO $ map cs <$> runMyriadT env killContainers
|
||||||
|
|
Loading…
Reference in a new issue