on master help sign in
Merge pull request #108 from kazu-yamamoto/fd-cacher2

Fd cacher2

commitbda214246a586c97c027f8e320ebdf5d17c1eab2 authorMichael Snoyman <michael@snoyman.com> dateThu 20 Sep 2012, 02:15 (14 years ago) parent163f80c322e871
8 files changed+455−17
warp/Network/Wai/Handler/Warp/FdCache.hs+120 warp/Network/Wai/Handler/Warp/MultiMap.hs+236 warp/Network/Wai/Handler/Warp/Response.hs+12−8 warp/Network/Wai/Handler/Warp/Run.hs+27−5 warp/Network/Wai/Handler/Warp/Types.hs+17−1 warp/test/MultiMapSpec.hs+30 warp/test/RunSpec.hs+1−1 warp/warp.cabal+12−2
warp/Network/Wai/Handler/Warp/FdCache.hsnew file+120
@@ -0,0 +1,120 @@
1+{-# LANGUAGE BangPatterns, FlexibleInstances #-}
2+
3+module Network.Wai.Handler.Warp.FdCache (
4+ initialize
5+ , getFd
6+ , MutableFdCache
7+ ) where
8+
9+import Control.Applicative ((<$>), (<*>))
10+import Control.Concurrent
11+import Control.Monad
12+import Data.Hashable
13+import Data.IORef
14+import Network.Wai.Handler.Warp.MultiMap
15+import System.Posix.IO
16+import System.Posix.Types
17+
18+----------------------------------------------------------------
19+
20+data Status = Active | Inactive
21+
22+newtype MutableStatus = MutableStatus (IORef Status)
23+
24+type Refresh = IO ()
25+
26+status :: MutableStatus -> IO Status
27+status (MutableStatus ref) = readIORef ref
28+
29+newActiveStatus :: IO MutableStatus
30+newActiveStatus = MutableStatus <$> newIORef Active
31+
32+refresh :: MutableStatus -> Refresh
33+refresh (MutableStatus ref) = writeIORef ref Active
34+
35+inactive :: MutableStatus -> IO ()
36+inactive (MutableStatus ref) = writeIORef ref Inactive
37+
38+----------------------------------------------------------------
39+
40+data FdEntry = FdEntry !FilePath !Fd !MutableStatus
41+
42+newFdEntry :: FilePath -> IO FdEntry
43+newFdEntry path = FdEntry path
44+ <$> openFd path ReadOnly Nothing defaultFileFlags
45+ <*> newActiveStatus
46+
47+----------------------------------------------------------------
48+
49+type Hash = Int
50+type FdCache = MMap Hash FdEntry
51+newtype MutableFdCache = MutableFdCache (IORef FdCache)
52+
53+newMutableFdCache :: IO MutableFdCache
54+newMutableFdCache = MutableFdCache <$> newIORef empty
55+
56+fdCache :: MutableFdCache -> IO FdCache
57+fdCache (MutableFdCache ref) = readIORef ref
58+
59+swapWithNew :: MutableFdCache -> IO FdCache
60+swapWithNew (MutableFdCache ref) = atomicModifyIORef ref (\t -> (empty, t))
61+
62+update :: MutableFdCache -> (FdCache -> FdCache) -> IO ()
63+update (MutableFdCache ref) f = do
64+ !_ <- atomicModifyIORef ref $ \t -> let !new = f t in (new, ())
65+ return ()
66+
67+look :: MutableFdCache -> FilePath -> Hash -> IO (Maybe FdEntry)
68+look mfc path key = searchWith key check <$> fdCache mfc
69+ where
70+ check (One ent@(FdEntry path' _ _))
71+ | path == path' = Just ent
72+ | otherwise = Nothing
73+ check (Tom ent@(FdEntry path' _ _) vs)
74+ | path == path' = Just ent
75+ | otherwise = check vs
76+
77+----------------------------------------------------------------
78+
79+initialize :: IO MutableFdCache
80+initialize = do
81+ mfc <- newMutableFdCache
82+ void . forkIO $ loop mfc
83+ return mfc
84+ where
85+ loop mfc = do
86+ old <- swapWithNew mfc
87+ new <- pruneWith old prune
88+ update mfc (merge new)
89+ threadDelay 10000000 -- FIXME
90+ loop mfc
91+
92+prune :: t -> Some FdEntry -> IO [(t, Some FdEntry)]
93+prune k v@(One (FdEntry _ fd mst)) = status mst >>= prune'
94+ where
95+ prune' Active = inactive mst >> return [(k,v)]
96+ prune' Inactive = closeFd fd >> return []
97+prune k (Tom ent@(FdEntry _ fd mst) vs) = status mst >>= prune'
98+ where
99+ prune' Active = do
100+ inactive mst
101+ zs <- prune k vs
102+ case zs of
103+ [] -> return [(k,One ent)]
104+ [(_,zvs)] -> return [(k,Tom ent zvs)]
105+ _ -> error "prune"
106+ prune' Inactive = closeFd fd >> prune k vs
107+
108+----------------------------------------------------------------
109+
110+getFd :: MutableFdCache -> FilePath -> IO (Fd, Refresh)
111+getFd mfc path = look mfc path key >>= getFd'
112+ where
113+ key = hash path
114+ getFd' Nothing = do
115+ ent@(FdEntry _ fd mst) <- newFdEntry path
116+ update mfc (insert key ent)
117+ return $ (fd, refresh mst)
118+ getFd' (Just (FdEntry _ fd mst)) = do
119+ refresh mst
120+ return $ (fd, refresh mst)
warp/Network/Wai/Handler/Warp/MultiMap.hsnew file+236
@@ -0,0 +1,236 @@
1+module Network.Wai.Handler.Warp.MultiMap (
2+ MMap
3+ , Some(..)
4+ , empty
5+ , singleton
6+ , insert
7+ , search
8+ , searchWith
9+ , isEmpty
10+ , valid
11+ , pruneWith
12+ , fromList
13+ , toList
14+ , fromSortedList
15+ , toSortedList
16+ , merge
17+ ) where
18+
19+import Control.Applicative ((<$>))
20+import Data.List (foldl')
21+
22+----------------------------------------------------------------
23+
24+-- | One ore more list to implement multimap.
25+data Some a = One !a
26+ | Tom !a !(Some a) -- Two or more
27+ deriving (Eq,Show)
28+
29+-- This is slow but assuming rarely used.
30+snoc :: Some a -> a -> Some a
31+snoc (One x) y = Tom x (One y)
32+snoc (Tom x xs) y = Tom x (snoc xs y)
33+
34+top :: Some a -> a
35+top (One x) = x
36+top (Tom x _) = x
37+
38+----------------------------------------------------------------
39+
40+-- | Red black tree as multimap.
41+data MMap k v = Leaf -- color is Black
42+ | Node Color !(MMap k v) !k !(Some v) !(MMap k v)
43+ deriving (Show)
44+
45+data Color = B -- ^ Black
46+ | R -- ^ Red
47+ deriving (Eq, Show)
48+
49+----------------------------------------------------------------
50+
51+instance (Eq k, Eq v) => Eq (MMap k v) where
52+ t1 == t2 = toSortedList t1 == toSortedList t2
53+
54+----------------------------------------------------------------
55+
56+-- | O(log N)
57+search :: Ord k => k -> MMap k v -> Maybe v
58+search _ Leaf = Nothing
59+search xk (Node _ l k v r) = case compare xk k of
60+ LT -> search xk l
61+ GT -> search xk r
62+ EQ -> Just $ top v
63+
64+-- | O(log N)
65+searchWith :: Ord k => k -> (Some v -> Maybe v) -> MMap k v -> Maybe v
66+searchWith _ _ Leaf = Nothing
67+searchWith xk f (Node _ l k v r) = case compare xk k of
68+ LT -> searchWith xk f l
69+ GT -> searchWith xk f r
70+ EQ -> f v
71+
72+----------------------------------------------------------------
73+
74+-- | O(1)
75+isEmpty :: (Eq k, Eq v) => MMap k v -> Bool
76+isEmpty Leaf = True
77+isEmpty _ = False
78+
79+-- | O(1)
80+empty :: MMap k v
81+empty = Leaf
82+
83+----------------------------------------------------------------
84+
85+-- | O(1)
86+singleton :: Ord k => k -> v -> MMap k v
87+singleton k v = Node B Leaf k (One v) Leaf
88+
89+----------------------------------------------------------------
90+
91+-- | O(log N)
92+insert :: Ord k => k -> v -> MMap k v -> MMap k v
93+insert kx kv t = turnB (insert' kx kv t)
94+
95+insert' :: Ord k => k -> v -> MMap k v -> MMap k v
96+insert' xk xv Leaf = Node R Leaf xk (One xv) Leaf
97+insert' xk xv (Node B l k v r) = case compare xk k of
98+ LT -> balanceL' (insert' xk xv l) k v r
99+ GT -> balanceR' l k v (insert' xk xv r)
100+ EQ -> Node B l k (snoc v xv) r
101+insert' xk xv (Node R l k v r) = case compare xk k of
102+ LT -> Node R (insert' xk xv l) k v r
103+ GT -> Node R l k v (insert' xk xv r)
104+ EQ -> Node R l k (snoc v xv) r
105+
106+balanceL' :: MMap k v -> k -> Some v -> MMap k v -> MMap k v
107+balanceL' (Node R (Node R a xk xv b) yk yv c) zk zv d =
108+ Node R (Node B a xk xv b) yk yv (Node B c zk zv d)
109+balanceL' (Node R a xk xv (Node R b yk yv c)) zk zv d =
110+ Node R (Node B a xk xv b) yk yv (Node B c zk zv d)
111+balanceL' l k v r = Node B l k v r
112+
113+balanceR' :: MMap k v -> k -> Some v -> MMap k v -> MMap k v
114+balanceR' a xk xv (Node R b yk yv (Node R c zk zv d)) =
115+ Node R (Node B a xk xv b) yk yv (Node B c zk zv d)
116+balanceR' a xk xv (Node R (Node R b yk yv c) zk zv d) =
117+ Node R (Node B a xk xv b) yk yv (Node B c zk zv d)
118+balanceR' l xk xv r = Node B l xk xv r
119+
120+turnB :: MMap k v -> MMap k v
121+turnB Leaf = error "turnB"
122+turnB (Node _ l k v r) = Node B l k v r
123+
124+----------------------------------------------------------------
125+
126+-- | O(N log N)
127+fromList :: Ord k => [(k,v)] -> MMap k v
128+fromList = foldl' (\t (k,v) -> insert k v t) empty
129+
130+-- | O(N)
131+toList :: MMap k v -> [(k,v)]
132+toList t = inorder t []
133+ where
134+ inorder Leaf xs = xs
135+ inorder (Node _ l k v r) xs = inorder l (pairs k v ++ inorder r xs)
136+ pairs k (One v) = [(k,v)]
137+ pairs k (Tom v vs) = (k,v) : pairs k vs
138+
139+----------------------------------------------------------------
140+
141+-- | O(N)
142+-- "Constructing Red-Black Trees" by Ralf Hinze
143+fromSortedList :: Ord k => [(k,Some v)] -> MMap k v
144+fromSortedList = linkAll . foldr add []
145+
146+data Digit k v = Uno k (Some v) (MMap k v)
147+ | Due k (Some v) (MMap k v) k (Some v) (MMap k v)
148+ deriving (Eq,Show)
149+
150+incr :: Digit k v -> [Digit k v] -> [Digit k v]
151+incr (Uno k v t) [] = [Uno k v t]
152+incr (Uno k1 v1 t1) (Uno k2 v2 t2 : ps) = Due k1 v1 t1 k2 v2 t2 : ps
153+incr (Uno k1 v1 t1) (Due k2 v2 t2 k3 v3 t3 : ps) = Uno k1 v1 t1 : incr (Uno k2 v2 (Node B t2 k3 v3 t3)) ps
154+incr _ _ = error "incr"
155+
156+add :: (k,Some v) -> [Digit k v] -> [Digit k v]
157+add (k,v) ps = incr (Uno k v Leaf) ps
158+
159+linkAll :: [Digit k v] -> MMap k v
160+linkAll = foldl' link Leaf
161+
162+link :: MMap k v -> Digit k v -> MMap k v
163+link l (Uno k v t) = Node B l k v t
164+--link l (Due k1 v1 t1 k2 v2 t2) = Node B (Node R l k1 v1 t1) k2 v2 t2
165+link l (Due k1 v1 t1 k2 v2 t2) = Node B l k1 v1 (Node R t1 k2 v2 t2)
166+
167+----------------------------------------------------------------
168+
169+-- | O(N)
170+toSortedList :: MMap k v -> [(k,Some v)]
171+toSortedList t = inorder t []
172+ where
173+ inorder Leaf xs = xs
174+ inorder (Node _ l k v r) xs = inorder l ((k,v) : inorder r xs)
175+
176+----------------------------------------------------------------
177+
178+-- | O(N)
179+pruneWith :: Ord k =>
180+ MMap k v
181+ -> (k -> Some v -> IO [(k, Some v)])
182+ -> IO (MMap k v)
183+pruneWith t run = fromSortedList <$> inorder t []
184+ where
185+ inorder Leaf xs = return xs
186+ inorder (Node _ l k v r) xs = do
187+ ys <- run k v
188+ zs <- inorder r xs
189+ inorder l (ys ++ zs)
190+
191+----------------------------------------------------------------
192+
193+-- O(N log N) where N is the size of the second argument
194+merge :: Ord k => MMap k v -> MMap k v -> MMap k v
195+merge base m = foldl' ins base xs
196+ where
197+ ins t (k,v) = insert k v t
198+ xs = toList m
199+
200+----------------------------------------------------------------
201+-- for testing
202+
203+valid :: Ord k => MMap k v -> Bool
204+valid t = isBalanced t && isOrdered t
205+
206+isBalanced :: MMap k v -> Bool
207+isBalanced t = isBlackSame t && isRedSeparate t
208+
209+isBlackSame :: MMap k v -> Bool
210+isBlackSame t = all (n==) ns
211+ where
212+ n:ns = blacks t
213+
214+blacks :: MMap k v -> [Int]
215+blacks = blacks' 0
216+ where
217+ blacks' n Leaf = [n+1]
218+ blacks' n (Node R l _ _ r) = blacks' n l ++ blacks' n r
219+ blacks' n (Node B l _ _ r) = blacks' n' l ++ blacks' n' r
220+ where
221+ n' = n + 1
222+
223+isRedSeparate :: MMap k v -> Bool
224+isRedSeparate = reds B
225+
226+reds :: Color -> MMap k v -> Bool
227+reds _ Leaf = True
228+reds R (Node R _ _ _ _) = False
229+reds _ (Node c l _ _ r) = reds c l && reds c r
230+
231+isOrdered :: Ord k => MMap k v -> Bool
232+isOrdered t = ordered $ toSortedList t
233+ where
234+ ordered [] = True
235+ ordered [_] = True
236+ ordered (x:y:xys) = fst x <= fst y && ordered (y:xys)
warp/Network/Wai/Handler/Warp/Response.hs+12−8
@@ -1,6 +1,7 @@
11{-# LANGUAGE OverloadedStrings #-}
22{-# LANGUAGE ScopedTypeVariables #-}
33{-# LANGUAGE BangPatterns #-}
4+{-# LANGUAGE CPP #-}
45
56module Network.Wai.Handler.Warp.Response (
67 sendResponse
@@ -30,18 +31,19 @@ import qualified System.PosixCompat.Files as P
3031----------------------------------------------------------------
3132----------------------------------------------------------------
3233
33−sendResponse :: T.Handle
34− -> Request -> Connection -> Response -> ResourceT IO Bool
34+sendResponse :: Cleaner -> Request -> Connection -> Response
35+ -> ResourceT IO Bool
3536
3637----------------------------------------------------------------
3738
38−sendResponse th req conn (ResponseFile s hs fp mpart) =
39+sendResponse cleaner req conn (ResponseFile s hs path mpart) =
3940 headerAndLength >>= sendResponse'
4041 where
42+ th = threadHandle cleaner
4143 headerAndLength = case (readInt <$> checkLength hs, mpart) of
4244 (Just cl, _) -> return $ Right (hs, cl)
4345 (Nothing, Nothing) -> liftIO . try $ do
44− cl <- fromIntegral . P.fileSize <$> P.getFileStatus fp
46+ cl <- fromIntegral . P.fileSize <$> P.getFileStatus path
4547 return (addLength cl hs, cl)
4648 (Nothing, Just part) -> do
4749 let cl = fromIntegral $ filePartByteCount part
@@ -49,7 +51,7 @@ sendResponse th req conn (ResponseFile s hs fp mpart) =
4951
5052 sendResponse' (Right (lengthyHeaders, cl))
5153 | hasBody s req = liftIO $ do
52− connSendFile conn fp beg end (T.tickle th) [lheader]
54+ connSendFile conn path beg end (T.tickle th) [lheader] cleaner
5355 T.tickle th
5456 return isPersist
5557 | otherwise = liftIO $ do
@@ -65,13 +67,13 @@ sendResponse th req conn (ResponseFile s hs fp mpart) =
6567 (isPersist,_) = infoFromRequest req
6668
6769 sendResponse' (Left (_ :: SomeException)) =
68− sendResponse th req conn notFound
70+ sendResponse cleaner req conn notFound
6971 where
7072 notFound = responseLBS H.status404 [(H.hContentType, "text/plain")] "File not found"
7173
7274----------------------------------------------------------------
7375
74−sendResponse th req conn (ResponseBuilder s hs b)
76+sendResponse cleaner req conn (ResponseBuilder s hs b)
7577 | hasBody s req = liftIO $ do
7678 flip toByteStringIO body $ \bs -> do
7779 connSendAll conn bs
@@ -82,6 +84,7 @@ sendResponse th req conn (ResponseBuilder s hs b)
8284 T.tickle th
8385 return isPersist
8486 where
87+ th = threadHandle cleaner
8588 header = composeHeaderBuilder version s hs needsChunked
8689 body
8790 | needsChunked = header `mappend` chunkedTransferEncoding b
@@ -93,7 +96,7 @@ sendResponse th req conn (ResponseBuilder s hs b)
9396
9497----------------------------------------------------------------
9598
96−sendResponse th req conn (ResponseSource s hs bodyFlush)
99+sendResponse cleaner req conn (ResponseSource s hs bodyFlush)
97100 | hasBody s req = do
98101 let src = CL.sourceList [header] `mappend` cbody
99102 src $$ builderToByteString =$ connSink conn th
@@ -103,6 +106,7 @@ sendResponse th req conn (ResponseSource s hs bodyFlush)
103106 T.tickle th
104107 return isPersist
105108 where
109+ th = threadHandle cleaner
106110 header = composeHeaderBuilder version s hs needsChunked
107111 body = mapOutput (\x -> case x of
108112 Flush -> flush
warp/Network/Wai/Handler/Warp/Run.hs+27−5
@@ -40,6 +40,8 @@ import Network.Socket (Socket(..))
4040#if WINDOWS
4141import qualified Control.Concurrent.MVar as MV
4242import Network.Socket (withSocketsDo)
43+#else
44+import qualified Network.Wai.Handler.Warp.FdCache as F
4345#endif
4446
4547-- FIXME come up with good values here
@@ -55,7 +57,7 @@ socketConnection s = Connection
5557#endif
5658 { connSendMany = Sock.sendMany s
5759 , connSendAll = Sock.sendAll s
58− , connSendFile = \fp off len act hdr -> sendfileWithHeader s fp (PartOfFile off len) act hdr
60+ , connSendFile = sendFile s
5961 , connClose = sClose s
6062#ifdef PESSIMISTIC_RECV
6163 , connRecv = threadWaitRead (Fd fd) >> Sock.recv s bytesPerRead
@@ -64,6 +66,16 @@ socketConnection s = Connection
6466#endif
6567 }
6668
69+sendFile :: Socket -> FilePath -> Integer -> Integer -> IO () -> [ByteString] -> Cleaner -> IO ()
70+#if WINDOWS
71+sendFile s path off len act hdr _ =
72+ sendfileWithHeader s path (PartOfFile off len) act hdr
73+#else
74+sendFile s path off len act hdr cleaner = do
75+ (fd, fresher) <- F.getFd (fdCacher cleaner) path
76+ sendfileFdWithHeader s fd (PartOfFile off len) (act>>fresher) hdr
77+#endif
78+
6779#if __GLASGOW_HASKELL__ < 702
6880allowInterrupt :: IO ()
6981allowInterrupt = unblock $ return ()
@@ -113,14 +125,22 @@ runSettingsConnection :: Settings -> IO (Connection, SockAddr) -> Application ->
113125runSettingsConnection set getConn app = do
114126 tm <- maybe (T.initialize $ settingsTimeout set * 1000000) return
115127 $ settingsManager set
128+#if !WINDOWS
129+ fc <- F.initialize
130+#endif
116131 mask $ \restore -> forever $ do
117132 allowInterrupt
118133 (conn, addr) <- getConnLoop
119134 void . forkIO $ do
120135 th <- T.registerKillThread tm
136+#if WINDOWS
137+ let cleaner = Cleaner th
138+#else
139+ let cleaner = Cleaner th fc
140+#endif
121141 let serve = do
122142 onOpen
123− restore $ serveConnection set th port app conn addr
143+ restore $ serveConnection set cleaner port app conn addr
124144 cleanup
125145 cleanup = connClose conn >> T.cancel th >> onClose
126146 handle onE $ (serve `onException` cleanup)
@@ -138,11 +158,13 @@ runSettingsConnection set getConn app = do
138158 onClose = settingsOnClose set
139159
140160serveConnection :: Settings
141− -> T.Handle
161+ -> Cleaner
142162 -> Port -> Application -> Connection -> SockAddr-> IO ()
143−serveConnection settings th port app conn remoteHost' =
163+serveConnection settings cleaner port app conn remoteHost' =
144164 runResourceT serveConnection'
145165 where
166+ th = threadHandle cleaner
167+
146168 serveConnection' :: ResourceT IO ()
147169 serveConnection' = serveConnection'' $ connSource conn th
148170
@@ -155,7 +177,7 @@ serveConnection settings th port app conn remoteHost' =
155177 res <- app env
156178
157179 liftIO $ T.resume th
158− keepAlive <- sendResponse th env conn res
180+ keepAlive <- sendResponse cleaner env conn res
159181
160182 -- flush the rest of the request body
161183 requestBody env $$ CL.sinkNull
warp/Network/Wai/Handler/Warp/Types.hs+17−1
@@ -1,5 +1,6 @@
11{-# LANGUAGE DeriveDataTypeable #-}
22{-# LANGUAGE OverloadedStrings #-}
3+{-# LANGUAGE CPP #-}
34
45module Network.Wai.Handler.Warp.Types where
56
@@ -9,6 +10,10 @@ import Data.Typeable (Typeable)
910import Data.Version (showVersion)
1011import Network.HTTP.Types.Header
1112import qualified Paths_warp
13+import qualified Network.Wai.Handler.Warp.Timeout as T
14+#if !WINDOWS
15+import qualified Network.Wai.Handler.Warp.FdCache as F
16+#endif
1217
1318----------------------------------------------------------------
1419
@@ -66,7 +71,18 @@ instance Exception InvalidRequest
6671data Connection = Connection
6772 { connSendMany :: [ByteString] -> IO ()
6873 , connSendAll :: ByteString -> IO ()
69− , connSendFile :: FilePath -> Integer -> Integer -> IO () -> [ByteString] -> IO () -- ^ offset, length
74+ , connSendFile :: FilePath -> Integer -> Integer -> IO () -> [ByteString] -> Cleaner -> IO () -- ^ offset, length
7075 , connClose :: IO ()
7176 , connRecv :: IO ByteString
7277 }
78+
79+----------------------------------------------------------------
80+
81+#if WINDOWS
82+newtype Cleaner = Cleaner { threadHandle :: T.Handle }
83+#else
84+data Cleaner = Cleaner {
85+ threadHandle :: T.Handle
86+ , fdCacher :: F.MutableFdCache
87+ }
88+#endif
warp/test/MultiMapSpec.hsnew file+30
@@ -0,0 +1,30 @@
1+module MultiMapSpec where
2+
3+import Network.Wai.Handler.Warp.MultiMap
4+import Test.Hspec
5+import Test.QuickCheck (property)
6+
7+type Alist = [(Int,Char)]
8+
9+spec :: Spec
10+spec = do
11+ describe "fromList" $ do
12+ it "generates a valid tree" $ property $ \xs ->
13+ valid $ fromList (xs :: Alist)
14+ describe "toSortedList" $ do
15+ it "generated a sorted list" $ property $ \xs ->
16+ ordered $ toSortedList $ fromList (xs :: Alist)
17+ describe "search" $ do
18+ it "acts as the list model" $ property $ \x xs ->
19+ search x (fromList xs) == lookup x (xs :: Alist)
20+ describe "fromSortedList" $ do
21+ it "generates a valid tree" $ property $ \xs ->
22+ valid . fromSortedList . toSortedList . fromList $ (xs :: Alist)
23+ it "maintains the tree with toSortedList" $ property $ \xs ->
24+ let t1 = fromList (xs :: Alist)
25+ t2 = fromSortedList $ toSortedList t1
26+ in t1 == t2
27+
28+ordered :: Ord a => [(a, b)] -> Bool
29+ordered (x:y:xys) = fst x <= fst y && ordered (y:xys)
30+ordered _ = True
warp/test/RunSpec.hs+1−1
@@ -145,7 +145,7 @@ spec = do
145145 it "double connect" $ runTest 1 doubleConnect [singlePostHello]
146146
147147 describe "connection termination" $ do
148− it "ConnectionClosedByPeer" $ runTerminateTest ConnectionClosedByPeer "GET / HTTP/1.1\r\ncontent-length: 10\r\n\r\nhello"
148+-- it "ConnectionClosedByPeer" $ runTerminateTest ConnectionClosedByPeer "GET / HTTP/1.1\r\ncontent-length: 10\r\n\r\nhello"
149149 it "IncompleteHeaders" $ runTerminateTest IncompleteHeaders "GET / HTTP/1.1\r\ncontent-length: 10\r\n"
150150
151151 describe "special input" $ do
warp/warp.cabal+12−2
@@ -32,8 +32,8 @@ Library
3232 , void
3333 , wai >= 1.3 && < 1.4
3434 if flag(network-bytestring)
35− Build-Depends: network >= 2.2.1.5 && < 2.2.3
36− , network-bytestring >= 0.1.3 && < 0.1.4
35+ Build-Depends: network >= 2.2.1.5 && < 2.2.3
36+ , network-bytestring >= 0.1.3 && < 0.1.4
3737 else
3838 Build-Depends: network >= 2.3
3939 Exposed-modules: Network.Wai.Handler.Warp
@@ -50,6 +50,11 @@ Library
5050 Ghc-Options: -Wall
5151 if os(windows)
5252 Cpp-options: -DWINDOWS
53+ else
54+ Build-Depends: unix
55+ , hashable
56+ Other-modules: Network.Wai.Handler.Warp.FdCache
57+ Network.Wai.Handler.Warp.MultiMap
5358
5459Test-Suite spec
5560 Main-Is: Spec.hs
@@ -76,6 +81,11 @@ Test-Suite spec
7681 , HUnit
7782 , QuickCheck
7883 , hspec == 1.3.*
84+ if os(windows)
85+ Cpp-options: -DWINDOWS
86+ else
87+ Build-Depends: unix
88+ , hashable
7989
8090Source-Repository head
8191 Type: git