Base.hs.template 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. -- vim: ft=haskell
  2. {-# LANGUAGE RecordWildCards #-}
  3. {-# LANGUAGE OverloadedStrings #-}
  4. module $module_path$.Base where
  5. import Data.Word (Word32)
  6. import Data.Map.Strict (Map)
  7. import qualified Data.Map.Strict as Map
  8. import Control.Concurrent (forkIO)
  9. import Control.Concurrent.STM
  10. (atomically,STM,TVar,newTVar,writeTVar,readTVar
  11. ,TQueue,readTQueue,writeTQueue
  12. ,TMVar,newEmptyTMVarIO,takeTMVar,putTMVar,newTVarIO
  13. ,modifyTVar)
  14. import Control.Monad (forever)
  15. import Snap.Core (Snap,route)
  16. import qualified Snap.Http.Server as HTTP
  17. import Snap.Util.FileServe (serveDirectory)
  18. data Config = Config { cfgPort :: !Int
  19. -- ^ The port to run on
  20. , cfgStaticDir :: Maybe FilePath
  21. -- ^ Content to be served off of the root, relative to
  22. -- the directory that the server was started in
  23. } deriving (Show)
  24. -- | A default @Config@ value that will produce a server that runs on port 8080,
  25. -- and serves no static content.
  26. defaultConfig :: Config
  27. defaultConfig = Config { cfgPort = 8080, cfgStaticDir = Nothing }
  28. -- | Spawn a snap server, and run the given RPC action.
  29. runServer :: Config -> Snap () -> IO ()
  30. runServer Config { .. } serveRpc =
  31. do let snapCfg :: HTTP.Config Snap ()
  32. snapCfg = HTTP.setPort cfgPort HTTP.defaultConfig
  33. HTTP.simpleHttpServe snapCfg body
  34. where
  35. body =
  36. do serveRpc
  37. case cfgStaticDir of
  38. Just path -> route [ ("", serveDirectory path) ]
  39. Nothing -> return ()
  40. data Request req resp = ReadRequest req (resp -> IO ())
  41. | WriteRequest req
  42. -- Sample Variables ------------------------------------------------------------
  43. -- | A TVar that blocks when it's empty, but allows writes even when it's full.
  44. newtype TSampleVar a = TSampleVar (TVar (Maybe a))
  45. newTSampleVar :: STM (TSampleVar a)
  46. newTSampleVar = TSampleVar `fmap` newTVar Nothing
  47. newTSampleVarIO :: IO (TSampleVar a)
  48. newTSampleVarIO = atomically (TSampleVar `fmap` newTVar Nothing)
  49. writeTSampleVar :: TSampleVar a -> a -> STM ()
  50. writeTSampleVar (TSampleVar tv) a = writeTVar tv (Just a)
  51. readTSampleVar :: TSampleVar a -> STM (Maybe a)
  52. readTSampleVar (TSampleVar tv) = readTVar tv
  53. -- Response Handling -----------------------------------------------------------
  54. data Conn req resp = Conn { connRequests :: TQueue req
  55. , connWaiting :: TVar (Map Word32 (TMVar resp))
  56. , connSeqNum :: TVar Word32
  57. }
  58. -- | Fork a handler thread that will apply handlers to incoming messages. If
  59. -- the handler queue is empty when a response arrives, the response is dropped.
  60. newConn :: TQueue req -> TQueue resp -> (resp -> Word32) -> IO (Conn req resp)
  61. newConn connRequests connResps toSeqNum =
  62. do connWaiting <- newTVarIO Map.empty
  63. connSeqNum <- newTVarIO 0
  64. _ <- forkIO (forever
  65. (do resp <- atomically (readTQueue connResps)
  66. let snum = toSeqNum resp
  67. mb <- atomically (do
  68. m <- readTVar connWaiting
  69. let (mb, m') = Map.updateLookupWithKey (\\_ _ -> Nothing) snum m
  70. writeTVar connWaiting m'
  71. return mb)
  72. case mb of
  73. Just var -> atomically (putTMVar var resp)
  74. Nothing -> return ()))
  75. return Conn { .. }
  76. -- | Send a request, and block until a response is received.
  77. sendRequest :: Conn req resp -> (Word32 -> req) -> IO resp
  78. sendRequest Conn { .. } req =
  79. do var <- newEmptyTMVarIO
  80. atomically (do snum <- readTVar connSeqNum
  81. writeTVar connSeqNum (snum + 1)
  82. modifyTVar connWaiting (Map.insert snum var)
  83. writeTQueue connRequests (req snum))
  84. atomically (takeTMVar var)