packages feed

futhark-server (empty) → 1.0.0.0

raw patch · 5 files changed

+374/−0 lines, 5 filesdep +basedep +binarydep +bytestring

Dependencies added: base, binary, bytestring, directory, futhark-data, mtl, process, temporary, text

Files

+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# Revision history for futhark-server++## 0.1.0.0 -- 2021-06-17++* First version. Released on an unsuspecting world.
+ LICENSE view
@@ -0,0 +1,17 @@+ISC License++Copyright (c) 2013-2021. DIKU, University of Copenhagen++Permission to use, copy, modify, and/or distribute this software for+any purpose with or without fee is hereby granted, provided that the+above copyright notice and this permission notice appear in all+copies.++THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL+WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED+WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE+AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL+DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR+PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER+TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR+PERFORMANCE OF THIS SOFTWARE.
+ futhark-server.cabal view
@@ -0,0 +1,37 @@+cabal-version:      2.4+name:               futhark-server+version:            1.0.0.0+synopsis: Client implementation of the Futhark server protocol.++description: Provides an easy way to interact with a running Futhark+             server-mode program from a Haskell program.  Provides+             both direct support of the protocol, as well as+             convenience functions for loading and extracting data.++category:           Futhark+author:             Troels Henriksen+maintainer:         athas@sigkill.dk+bug-reports:        https://github.com/diku-dk/futhark-server-haskell/issues+license:            ISC+license-file:       LICENSE+extra-source-files: CHANGELOG.md++source-repository head+  type: git+  location: https://github.com/diku-dk/futhark-server-haskell++library+    exposed-modules:  Futhark.Server+                      Futhark.Server.Values+    build-depends:    base >=4 && < 5+                    , binary+                    , bytestring+                    , directory >=1.3.0.0+                    , futhark-data+                    , text >=1.2.2.2+                    , temporary+                    , process >=1.4.3.0+                    , mtl >=2.2.1+    hs-source-dirs:   src+    ghc-options: -Wall -Wcompat -Wredundant-constraints -Wincomplete-record-updates -Wmissing-export-lists+    default-language: Haskell2010
+ src/Futhark/Server.hs view
@@ -0,0 +1,272 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-}++-- | Haskell code for interacting with a Futhark server program.  This+-- module presents a low-level interface.  See+-- <https://futhark.readthedocs.io/en/latest/server-protocol.html the+-- documentation of the server protocol> for the meaning of the+-- commands.  See also "Futhark.Server.Values" for higher-level+-- functions for loading data into a server.+--+-- Error messages produced by the server will be returned as a+-- 'CmdFailure'.  However, certain errors (such as if the server+-- process terminates unexpectedly, or temporary files cannot be+-- created) will result in an IO exception.+module Futhark.Server+  ( -- * Server creation+    Server,+    ServerCfg (..),+    newServerCfg,+    withServer,++    -- * Commands+    CmdFailure (..),+    VarName,+    TypeName,+    EntryName,+    cmdRestore,+    cmdStore,+    cmdCall,+    cmdFree,+    cmdRename,+    cmdInputs,+    cmdOutputs,+    cmdClear,+    cmdReport,++    -- * Utility+    cmdMaybe,+    cmdEither,++    -- * Raw+    startServer,+    stopServer,+    sendCommand,+  )+where++import Control.Exception+import Control.Monad+import Control.Monad.Except+import Data.Text (Text)+import qualified Data.Text as T+import qualified Data.Text.IO as T+import System.Directory (removeFile)+import System.Exit+import System.IO hiding (stdin, stdout)+import System.IO.Temp (getCanonicalTemporaryDirectory)+import qualified System.Process as P++-- | A handle to a running server.+data Server = Server+  { serverStdin :: Handle,+    serverStdout :: Handle,+    serverErrLog :: FilePath,+    serverProc :: P.ProcessHandle,+    serverDebug :: Bool+  }++-- | Configuration of the server.  Use 'newServerCfg' to conveniently+-- create a sensible default configuration.+data ServerCfg = ServerCfg+  { -- | Path to the server executable.+    cfgProg :: FilePath,+    -- | Command line options to pass to the+    -- server executable.+    cfgProgOpts :: [String],+    -- | If true, print a running log of server communication to stderr.+    cfgDebug :: Bool+  }+  deriving (Eq, Ord, Show)++-- | Create a server config with the given 'cfgProg' and 'cfgProgOpts'.+newServerCfg :: FilePath -> [String] -> ServerCfg+newServerCfg prog opts =+  ServerCfg+    { cfgProg = prog,+      cfgProgOpts = opts,+      cfgDebug = False+    }++-- | Start up a server.  Make sure that 'stopServer' is eventually+-- called on the server.  If this does not happen, then temporary+-- files may be left on the file system.  You almost certainly wish to+-- use 'bracket' or similar to avoid this.+startServer :: ServerCfg -> IO Server+startServer (ServerCfg prog options debug) = do+  tmpdir <- getCanonicalTemporaryDirectory+  (err_log_f, err_log_h) <- openTempFile tmpdir "futhark-server-stderr.log"+  (Just stdin, Just stdout, Nothing, phandle) <-+    P.createProcess+      ( (P.proc prog options)+          { P.std_err = P.UseHandle err_log_h,+            P.std_in = P.CreatePipe,+            P.std_out = P.CreatePipe+          }+      )++  code <- P.getProcessExitCode phandle+  case code of+    Just (ExitFailure e) ->+      error $ "Cannot start " ++ prog ++ ": error " ++ show e+    _ -> do+      let server =+            Server+              { serverStdin = stdin,+                serverStdout = stdout,+                serverProc = phandle,+                serverDebug = debug,+                serverErrLog = err_log_f+              }+      void (responseLines server) `catch` onStartupError server+      pure server+  where+    onStartupError :: Server -> IOError -> IO a+    onStartupError s _ = do+      code <- P.waitForProcess $ serverProc s+      stderr_s <- readFile $ serverErrLog s+      removeFile $ serverErrLog s+      error $+        "Command failed with " ++ show code ++ ":\n"+          ++ unwords (prog : options)+          ++ "\nStderr:\n"+          ++ stderr_s++-- | Shut down a server.  It may not be used again.+stopServer :: Server -> IO ()+stopServer s = do+  hClose $ serverStdin s+  void $ P.waitForProcess $ serverProc s+  removeFile $ serverErrLog s++-- | Start a server, execute an action, then shut down the server.+-- The 'Server' may not be returned from the action.+withServer :: ServerCfg -> (Server -> IO a) -> IO a+withServer cfg = bracket (startServer cfg) stopServer++-- Read lines of response until the next %%% OK (which is what+-- indicates that the server is ready for new instructions).+responseLines :: Server -> IO [Text]+responseLines s = do+  l <- T.hGetLine $ serverStdout s+  when (serverDebug s) $+    T.hPutStrLn stderr $ "<<< " <> l+  case l of+    "%%% OK" -> pure []+    _ -> (l :) <$> responseLines s++-- | The command failed, and this is why.  The first 'Text' is any+-- output before the failure indincator, and the second Text is the+-- output after the indicator.+data CmdFailure = CmdFailure {failureLog :: [Text], failureMsg :: [Text]}+  deriving (Eq, Ord, Show)++-- Figure out whether the response is a failure, and if so, return the+-- failure message.+checkForFailure :: [Text] -> Either CmdFailure [Text]+checkForFailure [] = Right []+checkForFailure ("%%% FAILURE" : ls) = Left $ CmdFailure mempty ls+checkForFailure (l : ls) =+  case checkForFailure ls of+    Left (CmdFailure xs ys) -> Left $ CmdFailure (l : xs) ys+    Right ls' -> Right $ l : ls'++-- Words with spaces in them must be quoted.+quoteWord :: Text -> Text+quoteWord t+  | Just _ <- T.find (== ' ') t =+    "\"" <> t <> "\""+  | otherwise = t++-- | Send an arbitrary command to the server.  This is only useful+-- when the server protocol has been extended without this module+-- having been similarly extended.  Be careful not to send invalid+-- commands.+sendCommand :: Server -> [Text] -> IO (Either CmdFailure [Text])+sendCommand s command = do+  let command' = T.unwords $ map quoteWord command++  when (serverDebug s) $+    T.hPutStrLn stderr $ ">>> " <> command'++  T.hPutStrLn (serverStdin s) command'+  hFlush $ serverStdin s+  checkForFailure <$> responseLines s `catch` onError+  where+    onError :: IOError -> IO a+    onError e = do+      code <- P.getProcessExitCode $ serverProc s+      let code_msg =+            case code of+              Just (ExitFailure x) ->+                "\nServer process exited unexpectedly with exit code: " ++ show x+              _ -> mempty+      stderr_s <- readFile $ serverErrLog s+      error $+        "After sending command " ++ show command ++ " to server process:"+          ++ show e+          ++ code_msg+          ++ "\nServer stderr:\n"+          ++ stderr_s++-- | The name of a server-side variable.+type VarName = Text++-- | The name of a server-side type.+type TypeName = Text++-- | The name of an entry point.+type EntryName = Text++helpCmd :: Server -> [Text] -> IO (Maybe CmdFailure)+helpCmd s cmd =+  either Just (const Nothing) <$> sendCommand s cmd++-- | @restore filename var0 type0 var1 type1...@.+cmdRestore :: Server -> FilePath -> [(VarName, TypeName)] -> IO (Maybe CmdFailure)+cmdRestore s fname vars = helpCmd s $ "restore" : T.pack fname : concatMap f vars+  where+    f (v, t) = [v, t]++-- | @store filename vars...@.+cmdStore :: Server -> FilePath -> [VarName] -> IO (Maybe CmdFailure)+cmdStore s fname vars = helpCmd s $ "store" : T.pack fname : vars++-- | @call entrypoint outs... ins...@.+cmdCall :: Server -> EntryName -> [VarName] -> [VarName] -> IO (Either CmdFailure [T.Text])+cmdCall s entry outs ins =+  sendCommand s $ "call" : entry : outs ++ ins++-- | @free vars...@.+cmdFree :: Server -> [VarName] -> IO (Maybe CmdFailure)+cmdFree s vs = helpCmd s $ "free" : vs++-- | @rename oldname newname@.+cmdRename :: Server -> VarName -> VarName -> IO (Maybe CmdFailure)+cmdRename s oldname newname = helpCmd s ["rename", oldname, newname]++-- | @inputs entryname@+cmdInputs :: Server -> EntryName -> IO (Either CmdFailure [TypeName])+cmdInputs s entry =+  sendCommand s ["inputs", entry]++-- | @outputs entryname@+cmdOutputs :: Server -> EntryName -> IO (Either CmdFailure [TypeName])+cmdOutputs s entry =+  sendCommand s ["outputs", entry]++-- | @clear@+cmdClear :: Server -> IO (Maybe CmdFailure)+cmdClear s = helpCmd s ["clear"]++-- | @report@+cmdReport :: Server -> IO (Either CmdFailure [T.Text])+cmdReport s = sendCommand s ["report"]++-- | Turn a 'Maybe'-producing command into a monadic action.+cmdMaybe :: (MonadError T.Text m, MonadIO m) => IO (Maybe CmdFailure) -> m ()+cmdMaybe = maybe (pure ()) (throwError . T.unlines . failureMsg) <=< liftIO++-- | Turn an 'Either'-producing command into a monadic action.+cmdEither :: (MonadError T.Text m, MonadIO m) => IO (Either CmdFailure a) -> m a+cmdEither = either (throwError . T.unlines . failureMsg) pure <=< liftIO
+ src/Futhark/Server/Values.hs view
@@ -0,0 +1,43 @@+{-# LANGUAGE OverloadedStrings #-}++-- | Convenience functions builds on top of "Futhark.Data" and+-- "Futhark.Server" for passing non-opaque values in and out of a+-- server instance.+module Futhark.Server.Values (getValue, putValue) where++import qualified Data.Binary as Bin+import qualified Data.ByteString.Lazy as LBS+import qualified Data.Text as T+import Futhark.Data (Value, valueType, valueTypeTextNoDims)+import Futhark.Server+import System.IO (hClose)+import System.IO.Temp (withSystemTempFile)++-- | Retrieve a non-opaque value from the server.+getValue :: Server -> VarName -> IO (Either T.Text Value)+getValue server vname =+  withSystemTempFile "futhark-server-get" $ \tmpf tmpf_h -> do+    hClose tmpf_h+    store_res <- cmdStore server tmpf [vname]+    case store_res of+      Just (CmdFailure _ err) ->+        pure $ Left $ T.unlines err+      Nothing -> do+        bytes <- LBS.readFile tmpf+        case Bin.decodeOrFail bytes of+          Left (_, _, e) ->+            pure $ Left $ "Cannot load value from generated byte stream:\n" <> T.pack e+          Right (_, _, val) ->+            pure $ Right val++-- | Store a non-opaque value in the server.  A variable with the+-- given name must not already exist (use 'cmdFree' to free it first+-- if necessary).+putValue :: Server -> VarName -> Value -> IO (Maybe CmdFailure)+putValue server v val =+  withSystemTempFile "futhark-server-put" $ \tmpf tmpf_h -> do+    LBS.hPutStr tmpf_h $ Bin.encode val+    hClose tmpf_h+    cmdRestore server tmpf [(v, t)]+  where+    t = valueTypeTextNoDims $ valueType val