packages feed

drmaa-0.3.0: src/DRMAA.hs

{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE LambdaCase          #-}
module DRMAA
    ( withSession
    , JobAttributes(..)
    , JobId(..)
    , JobResult(..)
    , RUsage(..)
    , ExitStatus(..)
    , defaultJobAttributes
    , runAndWait
    , runJob
    , waitJob
    , version
    ) where

import           Control.Monad         (when, unless)
import Control.Monad.Catch (MonadMask, bracket, bracket_)
import           Control.Monad.IO.Class                      (liftIO, MonadIO)
import           System.Directory      (getCurrentDirectory)
import Data.Maybe
import Data.List (break)
import Control.Arrow (second)
import Foreign
import Foreign.C.String

import DRMAA.Bindings
import DRMAA.Types

withSession :: (MonadIO m, MonadMask m) => m a -> m a 
withSession = bracket_ start quit
  where
    start = liftIO $ allocaBytes 1000 $ \errMsg -> do
        exitCode <- drmaaInit nullPtr errMsg 1000
        unless (exitCode == 0) $ peekCString errMsg >>= error
    quit = liftIO $ allocaBytes 1000 $ \errMsg -> do
        exitCode <- drmaaExit errMsg 1000
        unless (exitCode == 0) $ peekCString errMsg >>= error

data JobAttributes = JobAttributes
    { _wd :: Maybe FilePath  -- ^ specifies the directory name where the job will be executed.
    , _job_name :: String  -- ^ A job name SHALL contain only alphanumeric and '_' characters.
    , _native_specification :: Maybe String
    , _env :: [(String, String)]
    } deriving (Show)

defaultJobAttributes :: JobAttributes
defaultJobAttributes = JobAttributes
    { _wd = Nothing
    , _job_name = "drmaa"
    , _native_specification = Nothing
    , _env = [] } 

setJobAttributes :: JobAttributes -> JobTemplate -> IO ()
setJobAttributes JobAttributes{..} jt = do
    setAttribute jt "drmaa_job_name" _job_name
    setAttribute jt "drmaa_wd" =<< get_wd _wd
    when (isJust _native_specification) $
        setAttribute jt "drmaa_native_specification" $ fromJust _native_specification
    unless (null _env) $
        setVectorAttribute jt "drmaa_v_env" $ map (\(a,b) -> a ++ "=" ++ b) _env
{-# INLINE setJobAttributes #-}

setAttribute :: JobTemplate -> String -> String -> IO ()
setAttribute jt name value = allocaBytes 200 $ \errMsg -> do
    exitCode <- drmaaSetAttribute jt name value errMsg 200
    unless (exitCode == 0) $ peekCString errMsg >>= error
{-# INLINE setAttribute #-}

setVectorAttribute :: JobTemplate -> String -> [String] -> IO ()
setVectorAttribute jt name value = allocaBytes 200 $ \errMsg ->
    bracket (mapM newCString value) (mapM_ free) $ \strings ->
        withArray0 nullPtr strings $ \vec -> do
            exitCode <- drmaaSetVectorAttribute jt name vec errMsg 200
            unless (exitCode == 0) $ peekCString errMsg >>= error
{-# INLINE setVectorAttribute #-}

withJobAttributes :: JobAttributes -> (JobTemplate -> IO a) -> IO a
withJobAttributes ja fun = allocaJobTemplate $ \jt -> do
    setJobAttributes ja jt
    fun jt
{-# INLINE withJobAttributes #-}

newtype JobId = JobId { getJobId :: String }

-- | Submit a job and wait for the result.
runAndWait :: FilePath   -- ^ command
           -> [String]   -- ^ arguments
           -> JobAttributes   -- ^ attributes
           -> IO (JobResult, RUsage)
runAndWait cmd args ja = runJob cmd args ja >>= \case
    Left err -> error err
    Right jid -> waitJob jid

-- | Submit a job. The thread won't block.
runJob :: FilePath    -- ^ command
       -> [String]    -- ^ arguments
       -> JobAttributes   -- ^ attributes
       -> IO (Either String JobId)
runJob cmd args ja = withJobAttributes ja $ \jt -> allocaBytes 100 $ \jobId ->
    allocaBytes 200 $ \errMsg -> do
        setAttribute jt "drmaa_remote_command" cmd
        setVectorAttribute jt "drmaa_v_argv" args
        exitCode <- drmaaRunJob jobId 100 jt errMsg 200
        if exitCode == 0
            then Right . JobId <$> peekCString jobId
            else Left <$> peekCString errMsg
{-# INLINE runJob #-}

data RUsage = RUsage [(String, String)] deriving (Show)

-- | Wait for a job to finish.
waitJob :: JobId -> IO (JobResult, RUsage)
waitJob (JobId jid) = allocaBytes 100 $ \finished -> alloca $ \status ->
    alloca $ \attr -> allocaBytes 100 $ \errMsg -> do
        exitCode <- drmaaWait jid finished 100 status (-1) attr errMsg 100
        case getExitStatus exitCode of
            Success -> do
                result <- peek status >>= getJobResult
                usage <- consumeAttrValues =<< peek attr
                return (result, RUsage $ map (second tail . break (=='=')) usage)
            e -> error $ "Could not wait job: " ++ show e
  where
    getJobResult st = alloca $ \tmp -> do
        let aborted next = drmaaWifaborted tmp st nullPtr 0 >> peek tmp >>=
                (\x -> if x == 1 then return Aborted else next)
            exited next = drmaaWifexited tmp st nullPtr 0 >> peek tmp >>= ( \x ->
                if x == 1
                    then drmaaWexitstatus tmp st nullPtr 0 >>
                        peek tmp >>= return . Exit . getExitStatus
                    else next )
            signaled next = drmaaWifsignaled tmp st nullPtr 0 >> peek tmp >>= ( \x ->
                if x == 1
                    then allocaBytes 100 $ \buf ->
                        drmaaWtermsig buf 100 st nullPtr 0 >>
                        peekCString buf >>= return . Signaled
                    else next )
        aborted $ exited $ signaled $ error "Job finished with unclear conditions"
{-# INLINE waitJob #-}

consumeAttrValues :: Ptr AttrValues -> IO [String]
consumeAttrValues ptr = allocaBytes 1024 $ \tmp -> do
    result <- loop tmp
    drmaaReleaseAttrValues ptr
    return result
  where
    loop tmp = do
        exitCode <- drmaaGetNextAttrValue ptr tmp 1024
        if (exitCode == 0)
            then do
                x <- peekCString tmp
                xs <- loop tmp
                return $ x : xs
            else return []
{-# INLINE consumeAttrValues #-}

get_wd :: Maybe FilePath -> IO FilePath
get_wd Nothing  = getCurrentDirectory
get_wd (Just x) = return x
{-# INLINE get_wd #-}

version :: IO String
version = alloca $ \major -> alloca $ \minor -> allocaBytes 100 $ \impl ->
    allocaBytes 200 $ \errMsg -> do
        exitCode <- drmaaVersion major minor errMsg 200
        v <- if (exitCode == 0)
            then do
                x <- peek major
                y <- peek minor
                return $ show x ++"." ++ show y
            else error =<< peekCString errMsg

        exitCode' <- drmaaGetDRMAAImplementation impl 100 errMsg 200
        i <- if (exitCode' == 0)
            then peekCString impl
            else error =<< peekCString errMsg
        return $ "DRMAA v" ++ v ++ "; Implementation: " ++ i