use an ioref to store clean up actions instead of a field in Delayed, allowing early clean up registration

This commit is contained in:
Alp Mestanogullari 2016-10-24 19:21:28 +02:00
parent 9beedb59a9
commit 81a876c3e3
2 changed files with 50 additions and 30 deletions

View file

@ -399,8 +399,12 @@ instance HasServer Raw context where
type ServerT Raw m = Application type ServerT Raw m = Application
route Proxy _ rawApplication = RawRouter $ \ env request respond -> do route Proxy _ rawApplication = RawRouter $ \ env request respond -> do
(r, cleanup) <- runDelayed rawApplication env request -- note: a Raw application doesn't register any cleanup
go r request respond `finally` cleanup -- but for the sake of consistency, we nonetheless run
-- the cleanup once its done
cleanupRef <- newCleanupRef
r <- runDelayed rawApplication env request cleanupRef
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

@ -10,10 +10,10 @@
module Servant.Server.Internal.RoutingApplication where module Servant.Server.Internal.RoutingApplication where
import Control.Exception (bracket) import Control.Exception (bracket)
import Control.Monad (ap, liftM) import Control.Monad (ap, liftM, (>=>))
import Control.Monad.Trans (MonadIO(..)) import Control.Monad.Trans (MonadIO(..))
import Control.Monad.Trans.Except (runExceptT) import Control.Monad.Trans.Except (runExceptT)
import Data.IORef (newIORef, readIORef, writeIORef) import Data.IORef (newIORef, readIORef, writeIORef, IORef, atomicModifyIORef)
import Network.Wai (Application, Request, import Network.Wai (Application, Request,
Response, ResponseReceived) Response, ResponseReceived)
import Prelude () import Prelude ()
@ -106,10 +106,6 @@ data Delayed env c where
, authD :: DelayedIO auth , authD :: DelayedIO auth
, bodyD :: DelayedIO body , bodyD :: DelayedIO body
, serverD :: captures -> auth -> body -> Request -> RouteResult c , serverD :: captures -> auth -> body -> Request -> RouteResult c
, cleanupD :: body -> IO ()
-- not in DelayedIO because:
-- - most likely should not depend on the request
-- - simpler
} -> Delayed env c } -> Delayed env c
instance Functor (Delayed env) where instance Functor (Delayed env) where
@ -119,12 +115,33 @@ 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 -> IO (RouteResult a) } newtype DelayedIO a = DelayedIO { runDelayedIO :: Request -> CleanupRef -> IO (RouteResult a) }
instance Functor DelayedIO where instance Functor DelayedIO where
fmap = liftM fmap = liftM
@ -134,36 +151,36 @@ instance Applicative DelayedIO where
(<*>) = ap (<*>) = ap
instance Monad DelayedIO where instance Monad DelayedIO where
return x = DelayedIO (const $ return (Route x)) return x = DelayedIO (\_req _cleanup -> return (Route x))
DelayedIO m >>= f = DelayedIO m >>= f =
DelayedIO $ \ req -> do DelayedIO $ \ req cl -> do
r <- m req r <- m req cl
case r of case r of
Fail e -> return $ Fail e Fail e -> return $ Fail e
FailFatal e -> return $ FailFatal e FailFatal e -> return $ FailFatal e
Route a -> runDelayedIO (f a) req Route a -> runDelayedIO (f a) req cl
instance MonadIO DelayedIO where instance MonadIO DelayedIO where
liftIO m = DelayedIO (const $ Route <$> m) 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
emptyDelayed result = emptyDelayed result =
Delayed (const r) r r r (\ _ _ _ _ -> result) (const $ return ()) Delayed (const r) r r r (\ _ _ _ _ -> result)
where where
r = return () r = return ()
-- | Fail with the option to recover. -- | Fail with the option to recover.
delayedFail :: ServantErr -> DelayedIO a delayedFail :: ServantErr -> DelayedIO a
delayedFail err = DelayedIO (const $ return $ Fail err) delayedFail err = DelayedIO (\_req _cleanup -> return $ 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 (const $ return $ FailFatal err) delayedFailFatal err = DelayedIO (\_req _cleanup -> return $ 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 -> runDelayedIO (f req) req) withRequest f = DelayedIO (\ req cl -> runDelayedIO (f req) req cl)
-- | 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)
@ -205,7 +222,6 @@ addBodyCheck Delayed{..} new =
Delayed Delayed
{ bodyD = (,) <$> bodyD <*> new { bodyD = (,) <$> bodyD <*> new
, serverD = \ c a (z, v) req -> ($ v) <$> serverD c a z req , serverD = \ c a (z, v) req -> ($ v) <$> serverD c a z req
, cleanupD = cleanupD . fst -- not sure that's right
, .. , ..
} -- Note [Existential Record Update] } -- Note [Existential Record Update]
@ -248,19 +264,18 @@ passToServer Delayed{..} x =
runDelayed :: Delayed env a runDelayed :: Delayed env a
-> env -> env
-> Request -> Request
-> IO (RouteResult a, IO ()) -> CleanupRef
runDelayed Delayed{..} env req = do -> IO (RouteResult a)
cleanupRef <- newIORef (return ()) runDelayed Delayed{..} env req cleanupRef =
routeRes <- runDelayedIO runDelayedIO
(do c <- capturesD env (do c <- capturesD env
methodD methodD
a <- authD a <- authD
b <- bodyD b <- bodyD
liftIO (writeIORef cleanupRef $ cleanupD b) DelayedIO $ \ r _cleanup -> return (serverD c a b r)
DelayedIO $ \ r -> return (serverD c a b r)
) )
req req
fmap (routeRes,) $ readIORef cleanupRef 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.
@ -272,10 +287,11 @@ runAction :: Delayed env (Handler a)
-> (RouteResult Response -> IO r) -> (RouteResult Response -> IO r)
-> (a -> RouteResult Response) -> (a -> RouteResult Response)
-> IO r -> IO r
runAction action env req respond k = runAction action env req respond k = do
bracket (runDelayed action env req) cleanupRef <- newCleanupRef
snd bracket (runDelayed action env req cleanupRef)
(\(res, _) -> go res >>= respond) (const $ runCleanup cleanupRef)
(go >=> respond)
where where
go (Fail e) = return $ Fail e go (Fail e) = return $ Fail e