on master help sign in
Merge branch 'master' into no-resourcet-tuning

Conflicts: wai-app-static/wai-app-static.cabal warp/Network/Wai/Handler/Warp/FdCache.hs warp/Network/Wai/Handler/Warp/Run.hs

commit729d59a5ce3e0930c226e7011695c24c06f2c8be authorKazu Yamamoto <kazu@iij.ad.jp> dateThu 10 Oct 2013, 18:51 (13 years ago) parent24957db8a07b99
11 files changed+236−66
wai-app-static/WaiAppStatic/Storage/Embedded/TH.hs+19 wai-app-static/wai-app-static.cabal+2−2 wai-eventsource/src/Main.hs+8−9 warp/Network/Wai/Handler/Warp.hs+11−6 warp/Network/Wai/Handler/Warp/FdCache.hs+8−5 warp/Network/Wai/Handler/Warp/Run.hs+28−2 warp/Network/Wai/Handler/Warp/Timeout.hs+80−31 warp/Network/Wai/Handler/Warp/Types.hs+1 warp/test/ExceptionSpec.hs+72 warp/test/PartialFileSpec.hs+6−11 warp/warp.cabal+1
wai-app-static/WaiAppStatic/Storage/Embedded/TH.hs+19
@@ -18,6 +18,10 @@ import WaiAppStatic.Types
1818import WaiAppStatic.Storage.Filesystem (defaultWebAppSettings)
1919import qualified Data.ByteString as B
2020import qualified Data.ByteString.Lazy as BL
21+#if !MIN_VERSION_template_haskell(2, 8, 0)
22+import qualified Data.ByteString.Char8 as B8
23+import qualified Data.ByteString.Lazy.Char8 as BL8
24+#endif
2125import qualified Data.HashMap.Strict as M
2226import qualified Data.Text as T
2327import qualified Data.Text.Encoding as T
@@ -71,15 +75,30 @@ data ReloadEntry = ReloadEntry {
7175-- the IORef will always be holding (NoFinalizers, []). Therefore
7276-- unsafeDupablePerformIO is safe.
7377bytestringE :: B.ByteString -> ExpQ
78+#if MIN_VERSION_template_haskell(2, 8, 0)
7479bytestringE b = [| unsafeDupablePerformIO (unsafePackAddressLen (I# $lenE) $ctE) |]
7580 where
7681 lenE = litE $ intPrimL $ toInteger $ B.length b
7782 ctE = litE $ stringPrimL $ B.unpack b
83+#else
84+bytestringE b =
85+ [| B8.pack $s |]
86+ where
87+ s = litE $ stringL $ B8.unpack b
88+#endif
89+
7890bytestringLazyE :: BL.ByteString -> ExpQ
91+#if MIN_VERSION_template_haskell(2, 8, 0)
7992bytestringLazyE b = [| unsafeDupablePerformIO (unsafePackAddressLen (I# $lenE) $ctE) |]
8093 where
8194 lenE = litE $ intPrimL $ toInteger $ BL.length b
8295 ctE = litE $ stringPrimL $ BL.unpack b
96+#else
97+bytestringLazyE b =
98+ [| B8.pack $s |]
99+ where
100+ s = litE $ stringL $ BL8.unpack b
101+#endif
83102
84103-- | A template haskell expression which creates either an EmbeddedEntry or ReloadEntry.
85104mkEntry :: EmbeddableEntry -> ExpQ
wai-app-static/wai-app-static.cabal+2−2
@@ -14,7 +14,7 @@ homepage: http://www.yesodweb.com/book/web-application-interface
1414Extra-source-files:
1515 images/folder.png
1616 images/haskell.png
17− test/WaiAppStaticTest.hs
17+ test/*.hs
1818 test/a/b
1919 tests.hs
2020
@@ -48,7 +48,7 @@ library
4848 , cereal >= 0.3.5
4949 , mime-types >= 0.1 && < 0.2
5050 , unordered-containers >= 0.2
51− , template-haskell >= 2.8
51+ , template-haskell >= 2.7
5252 , zlib >= 0.5
5353
5454 exposed-modules: Network.Wai.Application.Static
wai-eventsource/src/Main.hs+8−9
@@ -8,7 +8,7 @@ import Control.Concurrent.Chan
88import Network.Wai.Handler.Warp (run)
99import Network.Wai.Middleware.Gzip (gzip, def)
1010import Network.Wai
11−import Network.HTTP.Types (statusOK)
11+import Network.HTTP.Types (status200)
1212import Data.Time.Clock.POSIX
1313import Blaze.ByteString.Builder.Char.Utf8 (fromString)
1414import qualified Data.Conduit as C
@@ -18,8 +18,8 @@ import Network.Wai.EventSource
1818app :: Chan ServerEvent -> Application
1919app chan req =
2020 case pathInfo req of
21− [] -> return $ ResponseFile statusOK [("Content-Type", "text/html")] "static/index.html" Nothing
22− ["esold"] -> eventSourceApp chan req
21+ [] -> return $ ResponseFile status200 [("Content-Type", "text/html")] "static/index.html" Nothing
22+ ["esold"] -> eventSourceAppChan chan req
2323 ["eschan"] -> eventSourceAppChan chan req
2424 ["esio"] -> eventSourceAppIO eventIO req
2525 ["essrc"] -> eventSourceAppSource eventSource req
@@ -39,18 +39,17 @@ eventIO = do
3939 Nothing
4040 [fromString . show $ time]
4141
42−eventSource :: C.Source IO ServerEvent
43−eventSource = C.sourceState () (const pull)
44− where
45− pull = do
42+eventSource :: C.Source (C.ResourceT IO) ServerEvent
43+eventSource = forever $ do
4644 time <- liftIO $ do
4745 threadDelay 1000000
4846 getPOSIXTime
49− return $ C.StateOpen () $ ServerEvent (Just $ fromString "source")
47+ C.yield $ ServerEvent (Just $ fromString "source")
5048 Nothing
5149 [fromString . show $ time]
5250
5351main = do
5452 chan <- newChan
5553 _ <- forkIO . eventChan $ chan
56− run 8000 (gzip def $ app chan)
54+ run 8080 (gzip def $ app chan)
55+
warp/Network/Wai/Handler/Warp.hs+11−6
@@ -43,27 +43,32 @@ module Network.Wai.Handler.Warp (
4343 -- * Datatypes
4444 , Port
4545 , InvalidRequest (..)
46− -- * Internal (Manager)
46+ -- * Internal
47+ -- ** Time out manager
4748 , Manager
4849 , Handle
50+ , TimeoutAction
4951 , initialize
5052 , withManager
5153 , register
5254 , registerKillThread
53− , pause
54− , resume
55+ , tickle
5556 , cancel
56− -- * Internal
57+ , resume
58+ , pause
59+ -- ** Cleaner
60+ , Cleaner
61+ , dummyCleaner
62+ -- ** Request and response
5763 , parseRequest
5864 , sendResponse
59− , dummyCleaner
6065 , socketConnection
6166#if TEST
6267 , takeHeaders
6368 , parseFirst
6469 , readInt
6570#endif
66− -- * Misc
71+ -- ** Version
6772 , warpVersion
6873 ) where
6974
warp/Network/Wai/Handler/Warp/FdCache.hs+8−5
@@ -7,13 +7,13 @@ module Network.Wai.Handler.Warp.FdCache (
77 ) where
88
99import Control.Applicative ((<$>), (<*>))
10−import Control.Concurrent
10+import Control.Concurrent (forkIO, threadDelay, ThreadId, killThread)
1111import Control.Exception (mask_)
12−import Data.Hashable
13−import Data.IORef
12+import Data.Hashable (hash)
13+import Data.IORef (IORef, newIORef, readIORef, writeIORef, atomicModifyIORef, mkWeakIORef)
1414import Network.Wai.Handler.Warp.MultiMap
15−import System.Posix.IO
16−import System.Posix.Types
15+import System.Posix.IO (openFd, defaultFileFlags, OpenMode(ReadOnly), closeFd)
16+import System.Posix.Types (Fd)
1717
1818----------------------------------------------------------------
1919
@@ -80,6 +80,9 @@ initialize :: Int -> IO MutableFdCache
8080initialize duration = do
8181 mfc <- newMutableFdCache
8282 tid <- forkIO $ loop mfc
83+ -- Registering finalizer to this IORef.
84+ -- When Warp is finished in GHCi, this IORef is GCed.
85+ -- At that time, we should close all opened file descriptors.
8386 _ <- mkWeakIORef (unMutableFdCache mfc) $ terminate mfc tid
8487 return mfc
8588 where
warp/Network/Wai/Handler/Warp/Run.hs+28−2
@@ -1,4 +1,5 @@
11{-# LANGUAGE CPP #-}
2+{-# LANGUAGE OverloadedStrings #-}
23{-# LANGUAGE ScopedTypeVariables #-}
34
45module Network.Wai.Handler.Warp.Run where
@@ -14,17 +15,20 @@ import Data.Conduit
1415import Data.Conduit.Internal (ResumableSource (..))
1516import qualified Data.Conduit.List as CL
1617import Data.Conduit.Network (bindPort)
18+import Data.Monoid (mempty)
1719import Network (sClose, Socket)
20+import qualified Network.HTTP.Types as H
1821import Network.Sendfile
1922import Network.Socket (accept, SockAddr)
2023import qualified Network.Socket.ByteString as Sock
2124import Network.Wai
25+import Network.Wai.Internal
26+import Network.Wai.Handler.Warp.Recv
2227import Network.Wai.Handler.Warp.Request
2328import Network.Wai.Handler.Warp.Response
2429import Network.Wai.Handler.Warp.Settings
2530import qualified Network.Wai.Handler.Warp.Timeout as T
2631import Network.Wai.Handler.Warp.Types
27−import Network.Wai.Handler.Warp.Recv
2832
2933#if WINDOWS
3034import qualified Control.Concurrent.MVar as MV
@@ -231,7 +235,7 @@ serveConnection :: T.Handle
231235 -> Cleaner
232236 -> Application -> Connection -> SockAddr-> IO ()
233237serveConnection timeoutHandle settings cleaner app conn remoteHost' =
234− serveConnection'' $ connSource conn th
238+ respondOnException settings cleaner conn remoteHost' $ serveConnection'' $ connSource conn th
235239 where
236240 th = threadHandle cleaner
237241
@@ -269,6 +273,28 @@ serveConnection timeoutHandle settings cleaner app conn remoteHost' =
269273 ResumableSource fromClient' _ <- liftIO getSource
270274 intercept fromClient' conn
271275
276+respondOnException :: Settings -> Cleaner -> Connection -> SockAddr -> IO () -> IO ()
277+respondOnException settings cleaner conn remoteHost' io = io `catch` \e@(SomeException _) -> do
278+ mask $ \restore ->
279+ void $ sendResponse settings cleaner blankRequest conn restore internalError
280+ throwIO e
281+ where
282+ internalError = responseLBS H.internalServerError500 [(H.hContentType, "text/plain")] "Something went wrong"
283+ blankRequest = Request {
284+ requestMethod = H.methodGet
285+ , httpVersion = H.http10
286+ , rawPathInfo = ""
287+ , rawQueryString = ""
288+ , requestHeaders = []
289+ , isSecure = False
290+ , remoteHost = remoteHost'
291+ , pathInfo = []
292+ , queryString = mempty
293+ , requestBody = return mempty
294+ , vault = mempty
295+ , requestBodyLength = KnownLength 0
296+ }
297+
272298connSource :: Connection -> T.Handle -> Source IO ByteString
273299connSource Connection { connRecv = recv } th = src
274300 where
warp/Network/Wai/Handler/Warp/Timeout.hs+80−31
@@ -5,6 +5,7 @@
55module Network.Wai.Handler.Warp.Timeout (
66 Manager
77 , Handle
8+ , TimeoutAction
89 , initialize
910 , stopManager
1011 , register
@@ -17,87 +18,117 @@ module Network.Wai.Handler.Warp.Timeout (
1718 , dummyHandle
1819 ) where
1920
20−import System.Mem.Weak (deRefWeak)
2121#if MIN_VERSION_base(4,6,0)
22−import Control.Concurrent (mkWeakThreadId)
22+import Control.Concurrent (mkWeakThreadId, ThreadId)
2323#else
24−import GHC.Weak (Weak (..))
25−import GHC.Conc.Sync (ThreadId (..))
26−import GHC.IO (IO (IO))
24+import Control.Concurrent (ThreadId(..))
2725import GHC.Exts (mkWeak#)
26+import GHC.IO (IO (IO))
2827#endif
28+import GHC.Weak (Weak (..))
2929import Control.Concurrent (forkIO, threadDelay, myThreadId, killThread)
3030import qualified Control.Exception as E
3131import Control.Monad (forever, void)
32+import Data.IORef (IORef)
3233import qualified Data.IORef as I
33−import System.IO.Unsafe (unsafePerformIO)
3434import Data.Typeable (Typeable)
35+import System.IO.Unsafe (unsafePerformIO)
36+import System.Mem.Weak (deRefWeak)
37+
38+----------------------------------------------------------------
3539
3640-- | A timeout manager
37−newtype Manager = Manager (I.IORef [Handle])
41+newtype Manager = Manager (IORef [Handle])
42+
43+-- | An action to be performed on timeout.
44+type TimeoutAction = IO ()
3845
3946-- | A handle used by 'Manager'
40−--
41−-- First field is action to be performed on timeout.
42−data Handle = Handle (IO ()) (I.IORef State)
47+data Handle = Handle TimeoutAction (IORef State)
48+
49+data State = Active -- Manager turns it to Inactive.
50+ | Inactive -- Manager removes it with timeout action.
51+ | Paused -- Manager does not change it.
52+ | Canceled -- Manager removes it without timeout action.
53+
54+----------------------------------------------------------------
4355
4456-- | A dummy @Handle@.
4557dummyHandle :: Handle
4658dummyHandle = Handle (return ()) (unsafePerformIO $ I.newIORef Active)
4759
48−data State = Active | Inactive | Paused | Canceled
60+----------------------------------------------------------------
4961
62+data TimeoutManagerStopped = TimeoutManagerStopped
63+ deriving (Show, Typeable)
64+instance E.Exception TimeoutManagerStopped
65+
66+----------------------------------------------------------------
67+
68+-- | Creating timeout manager which works every N micro seconds
69+-- where N is the first argument.
5070initialize :: Int -> IO Manager
5171initialize timeout = do
5272 ref <- I.newIORef []
5373 void . forkIO $ E.handle ignoreStop $ forever $ do
5474 threadDelay timeout
55− ms <- I.atomicModifyIORef ref (\x -> ([], x))
56− ms' <- go ms id
57− I.atomicModifyIORef ref (\x -> (ms' x, ()))
75+ -- FIXME: isn't mask_ necessary?
76+ old <- I.atomicModifyIORef ref (\x -> ([], x))
77+ merge <- prune old id
78+ I.atomicModifyIORef ref (\new -> (merge new, ()))
5879 return $ Manager ref
5980 where
6081 ignoreStop TimeoutManagerStopped = return ()
6182
62− go [] front = return front
63− go (m@(Handle onTimeout iactive):rest) front = do
64− state <- I.atomicModifyIORef iactive (\x -> (go' x, x))
83+ prune [] front = return front
84+ prune (m@(Handle onTimeout iactive):rest) front = do
85+ state <- I.atomicModifyIORef iactive (\x -> (inactivate x, x))
6586 case state of
6687 Inactive -> do
6788 onTimeout `E.catch` ignoreAll
68− go rest front
69− Canceled -> go rest front
70− _ -> go rest (front . (:) m)
71− go' Active = Inactive
72− go' x = x
89+ prune rest front
90+ Canceled -> prune rest front
91+ _ -> prune rest (front . (:) m)
92+ inactivate Active = Inactive
93+ inactivate x = x
7394
74−data TimeoutManagerStopped = TimeoutManagerStopped
75− deriving (Show, Typeable)
76−instance E.Exception TimeoutManagerStopped
95+----------------------------------------------------------------
7796
7897stopManager :: Manager -> IO ()
7998stopManager (Manager ihandles) = E.mask_ $ do
8099 -- Put an undefined value in the IORef to kill the worker thread (yes, it's
81100 -- a bit of a hack)
82101 !handles <- I.atomicModifyIORef ihandles $ \h -> (E.throw TimeoutManagerStopped, h)
83− mapM_ go handles
102+ mapM_ fire handles
84103 where
85− go (Handle onTimeout _) = onTimeout `E.catch` ignoreAll
104+ fire (Handle onTimeout _) = onTimeout `E.catch` ignoreAll
86105
87106ignoreAll :: E.SomeException -> IO ()
88107ignoreAll _ = return ()
89108
90−register :: Manager -> IO () -> IO Handle
109+----------------------------------------------------------------
110+
111+-- | Registering a timeout action.
112+register :: Manager -> TimeoutAction -> IO Handle
91113register (Manager ref) onTimeout = do
92114 iactive <- I.newIORef Active
93115 let h = Handle onTimeout iactive
94116 I.atomicModifyIORef ref (\x -> (h : x, ()))
95117 return h
96118
119+-- | Registering a timeout action of killing this thread.
97120registerKillThread :: Manager -> IO Handle
98121registerKillThread m = do
99122 wtid <- myThreadId >>= mkWeakThreadId
100− register m $ deRefWeak wtid >>= maybe (return ()) killThread
123+ register m $ killIfExist wtid
124+
125+-- If ThreadId is hold referred by a strong reference,
126+-- it leaks even after the thread is killed.
127+-- So, let's use a weak reference so that CG can throw ThreadId away.
128+-- deRefWeak checks if ThreadId referenced by the weak reference
129+-- exists. If exists, it means that the thread is alive.
130+killIfExist :: Weak ThreadId -> TimeoutAction
131+killIfExist wtid = deRefWeak wtid >>= maybe (return ()) killThread
101132
102133#if !MIN_VERSION_base(4,6,0)
103134mkWeakThreadId :: ThreadId -> IO (Weak ThreadId)
@@ -106,11 +137,29 @@ mkWeakThreadId t@(ThreadId t#) = IO $ \s ->
106137 (# s1, w #) -> (# s1, Weak w #)
107138#endif
108139
109−tickle, pause, resume, cancel :: Handle -> IO ()
140+----------------------------------------------------------------
141+
142+-- | Setting the state to active.
143+-- 'Manager' turns active to inactive repeatedly.
144+tickle :: Handle -> IO ()
110145tickle (Handle _ iactive) = I.writeIORef iactive Active
146+
147+-- | Setting the state to canceled.
148+-- 'Manager' eventually removes this without timeout action.
149+cancel :: Handle -> IO ()
150+cancel (Handle _ iactive) = I.writeIORef iactive Canceled
151+
152+-- | Setting the state to paused.
153+-- 'Manager' does not change the value.
154+pause :: Handle -> IO ()
111155pause (Handle _ iactive) = I.writeIORef iactive Paused
156+
157+-- | Setting the state to active.
158+-- This is an alias to 'ticle'.
159+resume :: Handle -> IO ()
112160resume = tickle
113−cancel (Handle _ iactive) = I.writeIORef iactive Canceled
161+
162+----------------------------------------------------------------
114163
115164-- | Call the inner function with a timeout manager.
116165withManager :: Int -- ^ timeout in microseconds
warp/Network/Wai/Handler/Warp/Types.hs+1
@@ -87,6 +87,7 @@ dummyCleaner :: Cleaner
8787#if SENDFILEFD
8888dummyCleaner = Cleaner T.dummyHandle Nothing
8989
90+-- | A type used to clean up file descriptor caches.
9091data Cleaner = Cleaner {
9192 threadHandle :: T.Handle
9293 , fdCacher :: Maybe F.MutableFdCache
warp/test/ExceptionSpec.hsnew file+72
@@ -0,0 +1,72 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+
3+module ExceptionSpec (main, spec) where
4+
5+import Control.Applicative
6+import Control.Monad
7+import Control.Concurrent (forkIO, threadDelay)
8+import Network.HTTP
9+import Network.Stream
10+import Network.HTTP.Types hiding (Header)
11+import Network.Wai hiding (Response)
12+import Network.Wai.Internal (Request(..))
13+import Network.Wai.Handler.Warp
14+import System.IO.Unsafe (unsafePerformIO)
15+import Test.Hspec
16+
17+main :: IO ()
18+main = hspec spec
19+
20+testServer :: IO ()
21+testServer =
22+ run 2345 testApp
23+
24+testApp :: Application
25+testApp (Network.Wai.Internal.Request {pathInfo = [x]})
26+ | x == "statusError" =
27+ return $ responseLBS undefined [] "foo"
28+ | x == "headersError" =
29+ return $ responseLBS ok200 undefined "foo"
30+ | x == "headerError" =
31+ return $ responseLBS ok200 [undefined] "foo"
32+ | x == "bodyError" =
33+ return $ responseLBS ok200 [] undefined
34+ | x == "ioException" = do
35+ void $ fail "ioException"
36+ return $ responseLBS ok200 [] "foo"
37+testApp _ =
38+ return $ responseLBS ok200 [] "foo"
39+
40+spec :: Spec
41+spec = unsafePerformIO $ (forkIO testServer >> threadDelay 100000 >>) $ return $
42+ describe "responds even if there is an exception" $ do
43+ it "statusError" $ do
44+ sc <- rspCode <$> sendGET "http://localhost:2345/statusError"
45+ sc `shouldBe` (5,0,0)
46+ it "headersError" $ do
47+ sc <- rspCode <$> sendGET "http://localhost:2345/headersError"
48+ sc `shouldBe` (5,0,0)
49+ it "headerError" $ do
50+ sc <- rspCode <$> sendGET "http://localhost:2345/headerError"
51+ sc `shouldBe` (5,0,0)
52+ it "bodyError" $ do
53+ sc <- rspCode <$> sendGET "http://localhost:2345/bodyError"
54+ sc `shouldBe` (5,0,0)
55+ it "ioException" $ do
56+ sc <- rspCode <$> sendGET "http://localhost:2345/ioException"
57+ sc `shouldBe` (5,0,0)
58+
59+----------------------------------------------------------------
60+
61+sendGET :: String -> IO (Response String)
62+sendGET url = sendGETwH url []
63+
64+sendGETwH :: String -> [Header] -> IO (Response String)
65+sendGETwH url hdr = unResult $ simpleHTTP $ (getRequest url) { rqHeaders = hdr }
66+
67+unResult :: IO (Result (Response String)) -> IO (Response String)
68+unResult action = do
69+ res <- action
70+ case res of
71+ Right rsp -> return rsp
72+ Left _ -> error "Connection error"
warp/test/PartialFileSpec.hs+6−11
@@ -1,22 +1,17 @@
11{-# LANGUAGE OverloadedStrings #-}
22module PartialFileSpec (main, spec) where
33
4−import Network.Wai.Handler.Warp.Conduit
5−import Data.Conduit
6−import qualified Data.Conduit.List as CL
7−import Test.Hspec
8−import qualified Data.IORef as I
4+import Control.Concurrent (threadDelay)
95import qualified Data.ByteString as S
106import qualified Data.ByteString.Char8 as S8
11−import Control.Monad.Trans.Class (lift)
12−import RunSpec (withApp)
7+import Data.Maybe (mapMaybe)
8+import Network (connectTo, PortID (PortNumber))
139import Network.HTTP.Types (status200)
1410import Network.Wai
15−import System.IO (hClose, hFlush)
16−import Network (connectTo, PortID (PortNumber))
1711import Network.Wai.Handler.Warp
18−import Data.Maybe (mapMaybe)
19−import Control.Concurrent (threadDelay)
12+import RunSpec (withApp)
13+import System.IO (hClose, hFlush)
14+import Test.Hspec
2015
2116main :: IO ()
2217main = hspec spec
warp/warp.cabal+1
@@ -85,6 +85,7 @@ Test-Suite spec
8585 , case-insensitive >= 0.2
8686 , conduit >= 0.5
8787 , ghc-prim
88+ , HTTP
8889 , http-types >= 0.7
8990 , lifted-base >= 0.1
9091 , network-conduit