Restructure project
This commit is contained in:
parent
176a78b557
commit
348054a994
6 changed files with 132 additions and 122 deletions
|
@ -42,11 +42,14 @@ default-extensions:
|
||||||
- BlockArguments
|
- BlockArguments
|
||||||
- ConstraintKinds
|
- ConstraintKinds
|
||||||
- DataKinds
|
- DataKinds
|
||||||
|
- DerivingStrategies
|
||||||
- DeriveFunctor
|
- DeriveFunctor
|
||||||
- DeriveGeneric
|
- DeriveGeneric
|
||||||
- FlexibleContexts
|
- FlexibleContexts
|
||||||
|
- FlexibleInstances
|
||||||
- FunctionalDependencies
|
- FunctionalDependencies
|
||||||
- GADTs
|
- GADTs
|
||||||
|
- GeneralizedNewtypeDeriving
|
||||||
- LambdaCase
|
- LambdaCase
|
||||||
- MultiParamTypeClasses
|
- MultiParamTypeClasses
|
||||||
- MultiWayIf
|
- MultiWayIf
|
||||||
|
@ -56,6 +59,7 @@ default-extensions:
|
||||||
- PolyKinds
|
- PolyKinds
|
||||||
- RankNTypes
|
- RankNTypes
|
||||||
- ScopedTypeVariables
|
- ScopedTypeVariables
|
||||||
|
- StandaloneDeriving
|
||||||
- TupleSections
|
- TupleSections
|
||||||
- TypeApplications
|
- TypeApplications
|
||||||
- TypeOperators
|
- TypeOperators
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
module Main where
|
module Main where
|
||||||
|
|
||||||
import Control.Monad.Logger
|
|
||||||
|
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import Options.Applicative
|
import Options.Applicative
|
||||||
|
|
||||||
|
@ -32,7 +30,6 @@ main = do
|
||||||
runMyriadT env do
|
runMyriadT env do
|
||||||
buildAllImages
|
buildAllImages
|
||||||
startCleanup
|
startCleanup
|
||||||
runStdoutLoggingT do
|
logInfo ["Finished Docker-related setup"]
|
||||||
logInfoN "Finished Docker-related setup"
|
logInfo ["Starting server"]
|
||||||
logInfoN "Starting server"
|
|
||||||
run (fromIntegral . port . config $ env) $ app env
|
run (fromIntegral . port . config $ env) $ app env
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
{-# LANGUAGE UndecidableInstances #-}
|
||||||
|
|
||||||
module Myriad.Core
|
module Myriad.Core
|
||||||
( Language
|
( Language
|
||||||
, ContainerName
|
, ContainerName
|
||||||
|
@ -7,26 +9,37 @@ module Myriad.Core
|
||||||
, MyriadConfig(..)
|
, MyriadConfig(..)
|
||||||
, LanguageConfig(..)
|
, LanguageConfig(..)
|
||||||
, MyriadT
|
, MyriadT
|
||||||
, MonadWithIO
|
, Myriad
|
||||||
, MyriadIO
|
, Myriadic
|
||||||
, runMyriadT
|
, runMyriadT
|
||||||
, initEnv
|
, initEnv
|
||||||
|
, exec
|
||||||
|
, exec_
|
||||||
|
, logInfo
|
||||||
|
, logError
|
||||||
|
, mapMVar
|
||||||
|
, writeMVar
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Control.Monad.Base
|
import Control.Monad.Base
|
||||||
import Control.Monad.Logger
|
import Control.Monad.Except
|
||||||
|
import Control.Monad.Logger hiding (logError, logInfo)
|
||||||
import Control.Monad.Reader
|
import Control.Monad.Reader
|
||||||
|
import Control.Monad.State
|
||||||
import Control.Monad.Trans.Control
|
import Control.Monad.Trans.Control
|
||||||
|
import Control.Monad.Writer
|
||||||
|
|
||||||
import qualified Data.ByteString.Lazy as BL
|
import qualified Data.ByteString.Lazy as BL
|
||||||
|
import Data.Functor.Identity
|
||||||
import qualified Data.Map.Strict as M
|
import qualified Data.Map.Strict as M
|
||||||
|
import Data.Snowflake
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import Dhall
|
import Dhall
|
||||||
import GHC.Generics (Generic)
|
import GHC.Generics (Generic)
|
||||||
|
|
||||||
import Control.Concurrent.MVar
|
import Control.Concurrent.MVar.Lifted
|
||||||
import Control.Concurrent.QSem
|
import Control.Concurrent.QSem.Lifted
|
||||||
import Data.Snowflake
|
import System.Process.Typed
|
||||||
|
|
||||||
type Language = T.Text
|
type Language = T.Text
|
||||||
type ContainerName = String
|
type ContainerName = String
|
||||||
|
@ -63,11 +76,37 @@ data LanguageConfig = LanguageConfig
|
||||||
|
|
||||||
instance Interpret LanguageConfig
|
instance Interpret LanguageConfig
|
||||||
|
|
||||||
type MyriadT m = ReaderT Env (LoggingT m)
|
newtype MyriadT m a = MyriadT { unMyriadT :: ReaderT Env (LoggingT m) a }
|
||||||
|
deriving newtype
|
||||||
|
( Functor
|
||||||
|
, Applicative
|
||||||
|
, Monad
|
||||||
|
, MonadReader Env
|
||||||
|
, MonadLogger
|
||||||
|
, MonadLoggerIO
|
||||||
|
, MonadIO
|
||||||
|
, MonadError e
|
||||||
|
, MonadState s
|
||||||
|
, MonadWriter w
|
||||||
|
, MonadBase b
|
||||||
|
)
|
||||||
|
|
||||||
type MonadWithIO m = (MonadIO m, MonadBase IO m, MonadBaseControl IO m)
|
instance MonadTrans MyriadT where
|
||||||
|
lift = MyriadT . lift . lift
|
||||||
|
|
||||||
type MyriadIO a = forall m. MonadWithIO m => MyriadT m a
|
instance MonadTransControl MyriadT where
|
||||||
|
type StT MyriadT a = a
|
||||||
|
liftWith = defaultLiftWith2 MyriadT unMyriadT
|
||||||
|
restoreT = defaultRestoreT2 MyriadT
|
||||||
|
|
||||||
|
instance MonadBaseControl b m => MonadBaseControl b (MyriadT m) where
|
||||||
|
type StM (MyriadT m) a = ComposeSt MyriadT m a
|
||||||
|
liftBaseWith = defaultLiftBaseWith
|
||||||
|
restoreM = defaultRestoreM
|
||||||
|
|
||||||
|
type Myriad = MyriadT Identity
|
||||||
|
|
||||||
|
type Myriadic m = (MonadReader Env m, MonadLogger m, MonadLoggerIO m, MonadIO m, MonadBase IO m, MonadBaseControl IO m)
|
||||||
|
|
||||||
readConfig :: T.Text -> IO MyriadConfig
|
readConfig :: T.Text -> IO MyriadConfig
|
||||||
readConfig = input auto
|
readConfig = input auto
|
||||||
|
@ -82,4 +121,22 @@ initEnv configInput =
|
||||||
<*> newSnowflakeGen defaultConfig 0
|
<*> newSnowflakeGen defaultConfig 0
|
||||||
|
|
||||||
runMyriadT :: MonadIO m => Env -> MyriadT m a -> m a
|
runMyriadT :: MonadIO m => Env -> MyriadT m a -> m a
|
||||||
runMyriadT env f = runStdoutLoggingT $ runReaderT f env
|
runMyriadT env = runStdoutLoggingT . flip runReaderT env . unMyriadT
|
||||||
|
|
||||||
|
exec :: MonadIO m => [String] -> m BL.ByteString
|
||||||
|
exec = readProcessInterleaved_ . shell . mconcat
|
||||||
|
|
||||||
|
exec_ :: MonadIO m => [String] -> m ()
|
||||||
|
exec_ = void . exec
|
||||||
|
|
||||||
|
logInfo :: MonadLogger m => [T.Text] -> m ()
|
||||||
|
logInfo = logInfoN . mconcat
|
||||||
|
|
||||||
|
logError :: MonadLogger m => [T.Text] -> m ()
|
||||||
|
logError = logErrorN . mconcat
|
||||||
|
|
||||||
|
mapMVar :: (MonadBase IO m, MonadBaseControl IO m) => MVar a -> (a -> a) -> m ()
|
||||||
|
mapMVar var f = modifyMVar_ var (pure . f)
|
||||||
|
|
||||||
|
writeMVar :: (MonadBase IO m, MonadBaseControl IO m) => MVar a -> a -> m ()
|
||||||
|
writeMVar var x = mapMVar var $ const x
|
||||||
|
|
|
@ -13,6 +13,7 @@ import Control.Monad.Reader
|
||||||
|
|
||||||
import qualified Data.Map.Strict as M
|
import qualified Data.Map.Strict as M
|
||||||
import Data.Snowflake
|
import Data.Snowflake
|
||||||
|
import Data.String.Conversions
|
||||||
|
|
||||||
import Control.Concurrent.Async.Lifted
|
import Control.Concurrent.Async.Lifted
|
||||||
import Control.Concurrent.Lifted (fork, threadDelay)
|
import Control.Concurrent.Lifted (fork, threadDelay)
|
||||||
|
@ -22,18 +23,17 @@ import Control.Exception.Lifted
|
||||||
import System.Process.Typed
|
import System.Process.Typed
|
||||||
|
|
||||||
import Myriad.Core
|
import Myriad.Core
|
||||||
import Myriad.Util
|
|
||||||
|
|
||||||
buildImage :: LanguageConfig -> MyriadIO ()
|
buildImage :: Myriadic m => LanguageConfig -> m ()
|
||||||
buildImage lang@LanguageConfig { name, concurrent } = do
|
buildImage lang@LanguageConfig { name, concurrent } = do
|
||||||
logInfo ["Building image ", cvs $ imageName lang]
|
logInfo ["Building image ", cs $ imageName lang]
|
||||||
exec_ ["docker build -t ", imageName lang, " ./languages/", cvs name]
|
exec_ ["docker build -t ", imageName lang, " ./languages/", cs name]
|
||||||
setupQSems
|
setupQSems
|
||||||
logInfo ["Built image ", cvs $ imageName lang]
|
logInfo ["Built image ", cs $ imageName lang]
|
||||||
MyriadConfig { prepareContainers } <- asks config
|
MyriadConfig { prepareContainers } <- asks config
|
||||||
when_ prepareContainers $ setupContainer lang
|
when_ prepareContainers $ setupContainer lang
|
||||||
where
|
where
|
||||||
setupQSems :: MyriadIO ()
|
setupQSems :: Myriadic m => m ()
|
||||||
setupQSems = do
|
setupQSems = do
|
||||||
Env { containerSems, evalSems } <- ask
|
Env { 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
|
||||||
|
@ -41,48 +41,48 @@ 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 :: MyriadIO ()
|
buildAllImages :: Myriadic m => m ()
|
||||||
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 :: MyriadIO ()
|
startCleanup :: Myriadic m => m ()
|
||||||
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 -> MyriadIO ()
|
timer :: Int -> Myriadic m => m ()
|
||||||
timer t = forever do
|
timer t = forever do
|
||||||
threadDelay t
|
threadDelay t
|
||||||
n <- killContainers
|
n <- killContainers
|
||||||
logInfo ["Cleaned up ", cvs $ show n, " containers"]
|
logInfo ["Cleaned up ", cs $ show n, " containers"]
|
||||||
timer t
|
timer t
|
||||||
|
|
||||||
setupContainer :: LanguageConfig -> MyriadIO ContainerName
|
setupContainer :: Myriadic m => LanguageConfig -> m 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 :: MyriadIO ContainerName
|
setup :: Myriadic m => m ContainerName
|
||||||
setup = do
|
setup = do
|
||||||
ref <- asks containers
|
ref <- asks containers
|
||||||
cnt <- newContainerName lang
|
cnt <- newContainerName lang
|
||||||
exec_
|
exec_
|
||||||
[ "docker run --rm --name="
|
[ "docker run --rm --name="
|
||||||
, cvs cnt
|
, cs cnt
|
||||||
-- User 1000 will be for setting up the environment
|
-- User 1000 will be for setting up the environment
|
||||||
, " -u1000:1000 -w/tmp/ -dt --net=none --cpus="
|
, " -u1000:1000 -w/tmp/ -dt --net=none --cpus="
|
||||||
, cvs cpus
|
, cs cpus
|
||||||
, " -m="
|
, " -m="
|
||||||
, cvs memory
|
, cs memory
|
||||||
, " --memory-swap="
|
, " --memory-swap="
|
||||||
, cvs memory
|
, cs memory
|
||||||
, " "
|
, " "
|
||||||
, imageName lang
|
, imageName lang
|
||||||
, " /bin/sh"
|
, " /bin/sh"
|
||||||
|
@ -92,10 +92,10 @@ setupContainer lang@LanguageConfig { name, memory, cpus } = do
|
||||||
exec_ ["docker exec ", cnt, " mkdir eval"]
|
exec_ ["docker exec ", cnt, " mkdir eval"]
|
||||||
exec_ ["docker exec ", cnt, " chmod 711 eval"]
|
exec_ ["docker exec ", cnt, " chmod 711 eval"]
|
||||||
mapMVar ref $ M.insert name cnt
|
mapMVar ref $ M.insert name cnt
|
||||||
logInfo ["Started container ", cvs cnt]
|
logInfo ["Started container ", cs cnt]
|
||||||
pure cnt
|
pure cnt
|
||||||
|
|
||||||
killContainer :: Language -> MyriadIO Bool
|
killContainer :: Myriadic m => Language -> m 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
|
||||||
|
@ -105,10 +105,10 @@ killContainer lang = do
|
||||||
case res of
|
case res of
|
||||||
Nothing -> pure True
|
Nothing -> pure True
|
||||||
Just err -> do
|
Just err -> do
|
||||||
logError ["An exception occured when killing ", cvs cnt, ":\n", cvs $ show err]
|
logError ["An exception occured when killing ", cs cnt, ":\n", cs $ show err]
|
||||||
pure False
|
pure False
|
||||||
where
|
where
|
||||||
kill :: ContainerName -> MyriadIO (Maybe SomeException)
|
kill :: Myriadic m => ContainerName -> m (Maybe SomeException)
|
||||||
kill cnt = do
|
kill cnt = do
|
||||||
ref <- asks containers
|
ref <- asks containers
|
||||||
mapMVar ref $ M.delete lang
|
mapMVar ref $ M.delete lang
|
||||||
|
@ -116,16 +116,16 @@ killContainer lang = do
|
||||||
case res of
|
case res of
|
||||||
Left err -> pure $ Just err
|
Left err -> pure $ Just err
|
||||||
Right _ -> do
|
Right _ -> do
|
||||||
logInfo ["Killed container ", cvs cnt]
|
logInfo ["Killed container ", cs cnt]
|
||||||
pure Nothing
|
pure Nothing
|
||||||
|
|
||||||
killContainers :: MyriadIO [ContainerName]
|
killContainers :: Myriadic m => m [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 :: LanguageConfig -> Int -> String -> MyriadIO EvalResult
|
evalCode :: Myriadic m => LanguageConfig -> Int -> String -> m 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.
|
||||||
|
@ -139,23 +139,23 @@ evalCode lang@LanguageConfig { name, timeout, retries } numRetries code = withCo
|
||||||
if done
|
if done
|
||||||
-- If we find the eval is done from an exception, then it was timed out.
|
-- If we find the eval is done from an exception, then it was timed out.
|
||||||
then do
|
then do
|
||||||
logError ["Code timed out in container ", cvs cnt, ", evaluation ", cvs $ show snowflake]
|
logError ["Code timed out in container ", cs cnt, ", evaluation ", cs $ show snowflake]
|
||||||
pure EvalTimedOut
|
pure EvalTimedOut
|
||||||
-- Otherwise, the container was killed from another eval, so we should retry.
|
-- Otherwise, the container was killed from another eval, so we should retry.
|
||||||
else do
|
else do
|
||||||
writeMVar doneRef True
|
writeMVar doneRef True
|
||||||
if numRetries < fromIntegral retries
|
if numRetries < fromIntegral retries
|
||||||
then do
|
then do
|
||||||
logError ["An exception occured in ", cvs cnt, ", evaluation ", cvs $ show snowflake, ", retrying:\n", cvs $ show err]
|
logError ["An exception occured in ", cs cnt, ", evaluation ", cs $ show snowflake, ", retrying:\n", cs $ show err]
|
||||||
evalCode lang (numRetries + 1) code
|
evalCode lang (numRetries + 1) code
|
||||||
else do
|
else do
|
||||||
logError ["An exception occured in ", cvs cnt, ", evaluation ", cvs $ show snowflake, ":\n", cvs $ show err]
|
logError ["An exception occured in ", cs cnt, ", evaluation ", cs $ show snowflake, ":\n", cs $ show err]
|
||||||
pure EvalErrored
|
pure EvalErrored
|
||||||
Right x -> do
|
Right x -> do
|
||||||
writeMVar doneRef True
|
writeMVar doneRef True
|
||||||
pure x
|
pure x
|
||||||
where
|
where
|
||||||
withContainer :: (ContainerName -> MyriadIO a) -> MyriadIO a
|
withContainer :: Myriadic m => (ContainerName -> m a) -> m 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
|
||||||
|
@ -164,7 +164,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 :: MVar Bool -> MyriadIO ()
|
timer :: Myriadic m => MVar Bool -> m ()
|
||||||
timer doneRef = do
|
timer doneRef = do
|
||||||
threadDelay $ fromIntegral timeout * 1000000
|
threadDelay $ fromIntegral timeout * 1000000
|
||||||
done <- readMVar doneRef
|
done <- readMVar doneRef
|
||||||
|
@ -172,14 +172,29 @@ evalCode lang@LanguageConfig { name, timeout, retries } numRetries code = withCo
|
||||||
writeMVar doneRef True
|
writeMVar doneRef True
|
||||||
killContainer name
|
killContainer name
|
||||||
|
|
||||||
eval :: ContainerName -> Snowflake -> MyriadIO EvalResult
|
eval :: Myriadic m => ContainerName -> Snowflake -> m EvalResult
|
||||||
eval cnt snowflake = do
|
eval cnt snowflake = do
|
||||||
logInfo ["Running code in container ", cvs cnt, ", evaluation ", cvs $ show snowflake, ":\n", cvs code]
|
logInfo ["Running code in container ", cs cnt, ", evaluation ", cs $ show snowflake, ":\n", cs code]
|
||||||
exec_ ["docker exec ", cvs cnt, " mkdir eval/", show snowflake]
|
exec_ ["docker exec ", cs cnt, " mkdir eval/", show snowflake]
|
||||||
exec_ ["docker exec ", cvs cnt, " chmod 777 eval/", show snowflake]
|
exec_ ["docker exec ", cs cnt, " chmod 777 eval/", show snowflake]
|
||||||
-- User 1001 will be used for the actual execution so that they can't access `eval` itself
|
-- User 1001 will be used for the actual execution so that they can't access `eval` itself
|
||||||
let args = ["exec", "-u1001:1001", "-w/tmp/eval/" <> show snowflake, cnt, "/bin/sh", "/var/run/run.sh", code]
|
let args = ["exec", "-u1001:1001", "-w/tmp/eval/" <> show snowflake, cnt, "/bin/sh", "/var/run/run.sh", code]
|
||||||
output <- readProcessInterleaved_ $ proc "docker" args
|
output <- readProcessInterleaved_ $ proc "docker" args
|
||||||
exec_ ["docker exec ", cnt, " rm -rf eval/", show snowflake]
|
exec_ ["docker exec ", cnt, " rm -rf eval/", show snowflake]
|
||||||
logInfo ["Ran code in container ", cvs cnt, ", evaluation ", cvs $ 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 { name } = do
|
||||||
|
snowflakeGen <- asks snowflakeGen
|
||||||
|
snowflake <- liftIO $ nextSnowflake snowflakeGen
|
||||||
|
pure $ "comp_iler-" <> cs name <> "-" <> show snowflake
|
||||||
|
|
||||||
|
imageName :: LanguageConfig -> ImageName
|
||||||
|
imageName LanguageConfig { name } = "1computer1/comp_iler:" <> cs name
|
||||||
|
|
||||||
|
when_ :: Applicative f => Bool -> f a -> f ()
|
||||||
|
when_ p = when p . void
|
||||||
|
|
||||||
|
unless_ :: Applicative f => Bool -> f a -> f ()
|
||||||
|
unless_ p = unless p . void
|
||||||
|
|
|
@ -10,6 +10,7 @@ import Control.Monad.Reader
|
||||||
import Data.Aeson
|
import Data.Aeson
|
||||||
import Data.List (find)
|
import Data.List (find)
|
||||||
import qualified Data.Map as M
|
import qualified Data.Map as M
|
||||||
|
import Data.String.Conversions
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import GHC.Generics
|
import GHC.Generics
|
||||||
|
|
||||||
|
@ -19,7 +20,6 @@ import Servant
|
||||||
|
|
||||||
import Myriad.Core
|
import Myriad.Core
|
||||||
import Myriad.Docker
|
import Myriad.Docker
|
||||||
import Myriad.Util
|
|
||||||
|
|
||||||
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 +36,35 @@ 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 :: forall m. (MonadWithIO m, MonadError ServantErr m) => ServerT API (MyriadT m)
|
serverT :: ServerT API (MyriadT Handler)
|
||||||
serverT = handleLanguages :<|> handleEval :<|> handleContainers :<|> handleCleanup
|
serverT = handleLanguages :<|> handleEval :<|> handleContainers :<|> handleCleanup
|
||||||
where
|
where
|
||||||
handleLanguages :: MyriadT m [T.Text]
|
handleLanguages :: MyriadT Handler [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 m EvalResponse
|
handleEval :: EvalRequest -> MyriadT Handler 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 " <> cvs 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 $ cvs code) wait
|
res <- withAsync (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 $ cvs xs
|
EvalOk xs -> pure . EvalResponse $ cs xs
|
||||||
|
|
||||||
handleContainers :: MyriadT m [T.Text]
|
handleContainers :: MyriadT Handler [T.Text]
|
||||||
handleContainers = do
|
handleContainers = do
|
||||||
logInfo ["GET /containers"]
|
logInfo ["GET /containers"]
|
||||||
containers <- asks containers >>= readMVar
|
containers <- asks containers >>= readMVar
|
||||||
pure . map cvs $ M.elems containers
|
pure . map cs $ M.elems containers
|
||||||
|
|
||||||
handleCleanup :: MyriadT m [T.Text]
|
handleCleanup :: MyriadT Handler [T.Text]
|
||||||
handleCleanup = do
|
handleCleanup = do
|
||||||
logInfo ["POST /cleanup"]
|
logInfo ["POST /cleanup"]
|
||||||
map cvs <$> killContainers
|
map cs <$> killContainers
|
||||||
|
|
|
@ -1,63 +0,0 @@
|
||||||
module Myriad.Util
|
|
||||||
( newContainerName
|
|
||||||
, imageName
|
|
||||||
, cvs
|
|
||||||
, when_
|
|
||||||
, unless_
|
|
||||||
, exec
|
|
||||||
, exec_
|
|
||||||
, logInfo
|
|
||||||
, logError
|
|
||||||
, mapMVar
|
|
||||||
, writeMVar
|
|
||||||
) where
|
|
||||||
|
|
||||||
import qualified Control.Monad.Logger as L
|
|
||||||
import Control.Monad.Reader
|
|
||||||
|
|
||||||
import qualified Data.ByteString.Lazy as BL
|
|
||||||
import Data.Snowflake
|
|
||||||
import Data.String.Conversions
|
|
||||||
import qualified Data.Text as T
|
|
||||||
|
|
||||||
import Control.Concurrent.MVar.Lifted
|
|
||||||
import System.Process.Typed
|
|
||||||
|
|
||||||
import Myriad.Core
|
|
||||||
|
|
||||||
newContainerName :: MonadIO m => LanguageConfig -> MyriadT m ContainerName
|
|
||||||
newContainerName LanguageConfig { name } = do
|
|
||||||
snowflakeGen <- asks snowflakeGen
|
|
||||||
snowflake <- liftIO $ nextSnowflake snowflakeGen
|
|
||||||
pure $ "comp_iler-" <> convertString name <> "-" <> show snowflake
|
|
||||||
|
|
||||||
imageName :: LanguageConfig -> ImageName
|
|
||||||
imageName LanguageConfig { name } = "1computer1/comp_iler:" <> convertString name
|
|
||||||
|
|
||||||
-- Shorthand because laziness
|
|
||||||
cvs :: ConvertibleStrings a b => a -> b
|
|
||||||
cvs = convertString
|
|
||||||
|
|
||||||
when_ :: Applicative f => Bool -> f a -> f ()
|
|
||||||
when_ p = when p . void
|
|
||||||
|
|
||||||
unless_ :: Applicative f => Bool -> f a -> f ()
|
|
||||||
unless_ p = unless p . void
|
|
||||||
|
|
||||||
exec :: [String] -> MyriadIO BL.ByteString
|
|
||||||
exec = readProcessInterleaved_ . shell . mconcat
|
|
||||||
|
|
||||||
exec_ :: [String] -> MyriadIO ()
|
|
||||||
exec_ = void . exec
|
|
||||||
|
|
||||||
logInfo :: [T.Text] -> MyriadIO ()
|
|
||||||
logInfo = L.logInfoN . mconcat
|
|
||||||
|
|
||||||
logError :: [T.Text] -> MyriadIO ()
|
|
||||||
logError = L.logErrorN . mconcat
|
|
||||||
|
|
||||||
mapMVar :: MVar a -> (a -> a) -> MyriadIO ()
|
|
||||||
mapMVar var f = modifyMVar_ var (pure . f)
|
|
||||||
|
|
||||||
writeMVar :: MVar a -> a -> MyriadIO ()
|
|
||||||
writeMVar var x = mapMVar var $ const x
|
|
Loading…
Reference in a new issue