Use resourcet for resource managment

This commit is contained in:
Oleg Grenrus 2017-01-18 11:37:18 +02:00
parent 484bc9cb64
commit bc6ff20f4d
4 changed files with 107 additions and 74 deletions

View file

@ -69,6 +69,7 @@ library
, string-conversions >= 0.3 && < 0.5 , string-conversions >= 0.3 && < 0.5
, system-filepath >= 0.4 && < 0.5 , system-filepath >= 0.4 && < 0.5
, filepath >= 1 && < 1.5 , filepath >= 1 && < 1.5
, resourcet >= 1.1.6 && <1.2
, text >= 1.2 && < 1.3 , text >= 1.2 && < 1.3
, transformers >= 0.3 && < 0.6 , transformers >= 0.3 && < 0.6
, transformers-base >= 0.4.4 && < 0.5 , transformers-base >= 0.4.4 && < 0.5
@ -127,19 +128,20 @@ test-suite spec
, hspec == 2.* , hspec == 2.*
, hspec-wai >= 0.8 && <0.9 , hspec-wai >= 0.8 && <0.9
, http-types , http-types
, mtl
, network >= 2.6 , network >= 2.6
, QuickCheck
, parsec , parsec
, QuickCheck
, resourcet
, safe , safe
, servant , servant
, servant-server , servant-server
, string-conversions
, should-not-typecheck == 2.1.* , should-not-typecheck == 2.1.*
, string-conversions
, temporary , temporary
, text , text
, transformers , transformers
, transformers-compat , transformers-compat
, mtl
, wai , wai
, wai-extra , wai-extra
, warp , warp

View file

@ -23,7 +23,6 @@ module Servant.Server.Internal
, module Servant.Server.Internal.ServantErr , module Servant.Server.Internal.ServantErr
) where ) where
import Control.Exception (finally)
import Control.Monad.Trans (liftIO) import Control.Monad.Trans (liftIO)
import qualified Data.ByteString as B import qualified Data.ByteString as B
import qualified Data.ByteString.Char8 as BC8 import qualified Data.ByteString.Char8 as BC8
@ -404,9 +403,8 @@ instance HasServer Raw context where
-- note: a Raw application doesn't register any cleanup -- note: a Raw application doesn't register any cleanup
-- but for the sake of consistency, we nonetheless run -- but for the sake of consistency, we nonetheless run
-- the cleanup once its done -- the cleanup once its done
cleanupRef <- newCleanupRef r <- runDelayed rawApplication env request
r <- runDelayed rawApplication env request cleanupRef go r request respond
go r request respond `finally` runCleanup cleanupRef
where go r request respond = case r of where go r request respond = case r of
Route app -> app request (respond . Route) Route app -> app request (respond . Route)

View file

@ -1,24 +1,32 @@
{-# LANGUAGE CPP #-} {-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE GADTs #-} {-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE KindSignatures #-} {-# LANGUAGE KindSignatures #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TupleSections #-} {-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
module Servant.Server.Internal.RoutingApplication where module Servant.Server.Internal.RoutingApplication where
import Control.Exception (finally)
import Control.Monad (ap, liftM) import Control.Monad (ap, liftM)
import Control.Monad.Trans (MonadIO(..)) import Control.Monad.Base (MonadBase (..))
import Data.IORef (newIORef, readIORef, IORef, atomicModifyIORef) import Control.Monad.Catch (MonadThrow (..))
import Network.Wai (Application, Request, import Control.Monad.Reader (MonadReader (..), ReaderT, runReaderT)
Response, ResponseReceived) import Control.Monad.Trans (MonadIO (..), MonadTrans (..))
import Control.Monad.Trans.Control (ComposeSt, MonadBaseControl (..), MonadTransControl (..),
defaultLiftBaseWith, defaultRestoreM)
import Control.Monad.Trans.Resource (MonadResource (..), ResourceT, runResourceT)
import Network.Wai (Application, Request, Response, ResponseReceived)
import Prelude () import Prelude ()
import Prelude.Compat import Prelude.Compat
import Servant.Server.Internal.ServantErr
import Servant.Server.Internal.Handler import Servant.Server.Internal.Handler
import Servant.Server.Internal.ServantErr
type RoutingApplication = type RoutingApplication =
Request -- ^ the request, the field 'pathInfo' may be modified by url routing Request -- ^ the request, the field 'pathInfo' may be modified by url routing
@ -32,6 +40,58 @@ data RouteResult a =
| Route !a | Route !a
deriving (Eq, Show, Read, Functor) deriving (Eq, Show, Read, Functor)
instance Applicative RouteResult where
pure = return
(<*>) = ap
instance Monad RouteResult where
return = Route
Route a >>= f = f a
Fail e >>= _ = Fail e
FailFatal e >>= _ = FailFatal e
newtype RouteResultT m a = RouteResultT { runRouteResultT :: m (RouteResult a) }
deriving (Functor)
-- As we write these instances, we get instances for `DelayedIO` with GND.
instance MonadTrans RouteResultT where
lift = RouteResultT . liftM Route
instance (Functor m, Monad m) => Applicative (RouteResultT m) where
pure = return
(<*>) = ap
instance Monad m => Monad (RouteResultT m) where
return = RouteResultT . return . Route
m >>= k = RouteResultT $ do
a <- runRouteResultT m
case a of
Fail e -> return $ Fail e
FailFatal e -> return $ FailFatal e
Route b -> runRouteResultT (k b)
instance MonadIO m => MonadIO (RouteResultT m) where
liftIO = lift . liftIO
instance MonadBase b m => MonadBase b (RouteResultT m) where
liftBase = lift . liftBase
instance MonadBaseControl b m => MonadBaseControl b (RouteResultT m) where
type StM (RouteResultT m) a = ComposeSt RouteResultT m a
liftBaseWith = defaultLiftBaseWith
restoreM = defaultRestoreM
instance MonadTransControl RouteResultT where
type StT RouteResultT a = RouteResult a
liftWith f = RouteResultT $ liftM return $ f $ runRouteResultT
restoreT = RouteResultT
instance MonadThrow m => MonadThrow (RouteResultT m) where
throwM = lift . throwM
-- instance MonadCatch m => MonadCatch (RouteResultT m) where
-- instance MonadError ServantErr (RouteResultT m) where
toApplication :: RoutingApplication -> Application toApplication :: RoutingApplication -> Application
toApplication ra request respond = ra request routingRespond toApplication ra request respond = ra request routingRespond
where where
@ -115,53 +175,30 @@ instance Functor (Delayed env) where
, .. , ..
} -- Note [Existential Record Update] } -- Note [Existential Record Update]
-- | A mutable cleanup action
newtype CleanupRef = CleanupRef (IORef (IO ()))
newCleanupRef :: IO CleanupRef
newCleanupRef = CleanupRef <$> newIORef (return ())
-- | Add a clean up action to a 'CleanupRef'
addCleanup' :: IO () -> CleanupRef -> IO ()
addCleanup' act (CleanupRef ref) = atomicModifyIORef ref (\act' -> (act' >> act, ()))
addCleanup :: IO () -> DelayedIO ()
addCleanup act = DelayedIO $ \_req cleanupRef ->
addCleanup' act cleanupRef >> return (Route ())
-- | Run all the clean up actions registered in
-- a 'CleanupRef'.
runCleanup :: CleanupRef -> IO ()
runCleanup (CleanupRef ref) = do
act <- readIORef ref
act
-- | Computations used in a 'Delayed' can depend on the -- | Computations used in a 'Delayed' can depend on the
-- incoming 'Request', may perform 'IO, and result in a -- incoming 'Request', may perform 'IO, and result in a
-- 'RouteResult, meaning they can either suceed, fail -- 'RouteResult, meaning they can either suceed, fail
-- (with the possibility to recover), or fail fatally. -- (with the possibility to recover), or fail fatally.
-- --
newtype DelayedIO a = DelayedIO { runDelayedIO :: Request -> CleanupRef -> IO (RouteResult a) } newtype DelayedIO a = DelayedIO { runDelayedIO' :: ReaderT Request (ResourceT (RouteResultT IO)) a }
deriving
( Functor, Applicative, Monad
, MonadIO, MonadReader Request
, MonadBase IO
, MonadThrow
, MonadResource
)
instance Functor DelayedIO where returnRouteResult :: RouteResult a -> DelayedIO a
fmap = liftM returnRouteResult x = DelayedIO $ lift . lift $ RouteResultT . return $ x
instance Applicative DelayedIO where instance MonadBaseControl IO DelayedIO where
pure = return type StM DelayedIO a = StM (ReaderT Request (ResourceT (RouteResultT IO))) a
(<*>) = ap liftBaseWith f = DelayedIO $ liftBaseWith $ \g -> f (g . runDelayedIO')
restoreM = DelayedIO . restoreM
instance Monad DelayedIO where runDelayedIO :: DelayedIO a -> Request -> IO (RouteResult a)
return x = DelayedIO (\_req _cleanup -> return (Route x)) runDelayedIO m req = runRouteResultT $ runResourceT $ runReaderT (runDelayedIO' m) req
DelayedIO m >>= f =
DelayedIO $ \ req cl -> do
r <- m req cl
case r of
Fail e -> return $ Fail e
FailFatal e -> return $ FailFatal e
Route a -> runDelayedIO (f a) req cl
instance MonadIO DelayedIO where
liftIO m = DelayedIO (\_req _cl -> Route <$> m)
-- | A 'Delayed' without any stored checks. -- | A 'Delayed' without any stored checks.
emptyDelayed :: RouteResult a -> Delayed env a emptyDelayed :: RouteResult a -> Delayed env a
@ -172,15 +209,17 @@ emptyDelayed result =
-- | Fail with the option to recover. -- | Fail with the option to recover.
delayedFail :: ServantErr -> DelayedIO a delayedFail :: ServantErr -> DelayedIO a
delayedFail err = DelayedIO (\_req _cleanup -> return $ Fail err) delayedFail err = returnRouteResult $ Fail err
-- | Fail fatally, i.e., without any option to recover. -- | Fail fatally, i.e., without any option to recover.
delayedFailFatal :: ServantErr -> DelayedIO a delayedFailFatal :: ServantErr -> DelayedIO a
delayedFailFatal err = DelayedIO (\_req _cleanup -> return $ FailFatal err) delayedFailFatal err = returnRouteResult $ FailFatal err
-- | Gain access to the incoming request. -- | Gain access to the incoming request.
withRequest :: (Request -> DelayedIO a) -> DelayedIO a withRequest :: (Request -> DelayedIO a) -> DelayedIO a
withRequest f = DelayedIO (\ req cl -> runDelayedIO (f req) req cl) withRequest f = do
req <- ask
f req
-- | Add a capture to the end of the capture block. -- | Add a capture to the end of the capture block.
addCapture :: Delayed env (a -> b) addCapture :: Delayed env (a -> b)
@ -264,18 +303,17 @@ passToServer Delayed{..} x =
runDelayed :: Delayed env a runDelayed :: Delayed env a
-> env -> env
-> Request -> Request
-> CleanupRef
-> IO (RouteResult a) -> IO (RouteResult a)
runDelayed Delayed{..} env req cleanupRef = runDelayed Delayed{..} env req =
runDelayedIO runDelayedIO
(do c <- capturesD env (do c <- capturesD env
methodD methodD
a <- authD a <- authD
b <- bodyD b <- bodyD
DelayedIO $ \ r _cleanup -> return (serverD c a b r) r <- ask
returnRouteResult (serverD c a b r)
) )
req req
cleanupRef
-- | Runs a delayed server and the resulting action. -- | Runs a delayed server and the resulting action.
-- Takes a continuation that lets us send a response. -- Takes a continuation that lets us send a response.
@ -288,10 +326,7 @@ runAction :: Delayed env (Handler a)
-> (a -> RouteResult Response) -> (a -> RouteResult Response)
-> IO r -> IO r
runAction action env req respond k = do runAction action env req respond k = do
cleanupRef <- newCleanupRef runDelayed action env req >>= go >>= respond
(runDelayed action env req cleanupRef >>= go >>= respond)
`finally` runCleanup cleanupRef
where where
go (Fail e) = return $ Fail e go (Fail e) = return $ Fail e
go (FailFatal e) = return $ FailFatal e go (FailFatal e) = return $ FailFatal e

View file

@ -4,6 +4,7 @@ import Prelude ()
import Prelude.Compat import Prelude.Compat
import Control.Exception hiding (Handler) import Control.Exception hiding (Handler)
import Control.Monad.Trans.Resource (register)
import Control.Monad.IO.Class import Control.Monad.IO.Class
import Data.Maybe (isJust) import Data.Maybe (isJust)
import Data.IORef import Data.IORef
@ -13,21 +14,18 @@ import Test.Hspec
import System.IO.Unsafe (unsafePerformIO) import System.IO.Unsafe (unsafePerformIO)
ok :: IO (RouteResult ())
ok = return (Route ())
-- Let's not write to the filesystem -- Let's not write to the filesystem
delayedTestRef :: IORef (Maybe String) delayedTestRef :: IORef (Maybe String)
delayedTestRef = unsafePerformIO $ newIORef Nothing delayedTestRef = unsafePerformIO $ newIORef Nothing
delayed :: DelayedIO () -> RouteResult (Handler ()) -> Delayed () (Handler ()) delayed :: DelayedIO () -> RouteResult (Handler ()) -> Delayed () (Handler ())
delayed body srv = Delayed delayed body srv = Delayed
{ capturesD = \() -> DelayedIO $ \_req _cl -> ok { capturesD = \_ -> return ()
, methodD = DelayedIO $ \_req_ _cl -> ok , methodD = return ()
, authD = DelayedIO $ \_req _cl -> ok , authD = return ()
, bodyD = do , bodyD = do
liftIO (writeIORef delayedTestRef (Just "hia") >> putStrLn "garbage created") liftIO (writeIORef delayedTestRef (Just "hia") >> putStrLn "garbage created")
addCleanup (writeIORef delayedTestRef Nothing >> putStrLn "garbage collected") _ <- register (writeIORef delayedTestRef Nothing >> putStrLn "garbage collected")
body body
, serverD = \() () _body _req -> srv , serverD = \() () _body _req -> srv
} }