on master help sign in
Non-resumable enumerators
commit556030c5712691cfa31b134a06b549b11de13e51 authorMichael Snoyman <michael@snoyman.com> dateWed 3 Feb 2010, 17:52 (17 years ago) parent1205634
2 files changed+38−7
Network/Wai.hs+5 Network/Wai/Enumerator.hs+33−7
Network/Wai.hs+5
@@ -336,6 +336,11 @@ statusMessage (Status _ m) = m
336336-- 'Enumerator's are used twice in the WAI: once in the 'Request' datatype to
337337-- access the request body, and once in the 'Response' datatype for the
338338-- application to return the body of the response.
339+--
340+-- 'Enumerator's are not required to be resumable. That is to say, the
341+-- 'Enumerator' may only be called once. While this requirement puts a bit of a
342+-- strain on the caller in some situations, it saves a large amount of
343+-- complication- and thus performance- on the producer.
339344type Enumerator a = (a -> B.ByteString -> IO (Either a a))
340345 -> a
341346 -> IO (Either a a)
Network/Wai/Enumerator.hs+33−7
@@ -23,6 +23,8 @@ import qualified Data.ByteString as B
2323import System.IO.Unsafe (unsafeInterleaveIO)
2424import System.IO (withBinaryFile, IOMode (ReadMode), Handle, hIsEOF)
2525import Data.ByteString.Lazy.Internal (defaultChunkSize)
26+import Control.Concurrent (forkIO)
27+import Control.Concurrent.MVar
2628
2729-- | Performs a specified conversion on each 'B.ByteString' output by an
2830-- enumerator.
@@ -32,14 +34,17 @@ mapE f e iter = e iter' where
3234
3335-- | This uses 'unsafeInterleaveIO' to lazily read from an enumerator. All
3436-- normal lazy I/O warnings apply.
35−toLBS :: Enumerator (Maybe B.ByteString) -> IO L.ByteString
36−toLBS e = L.fromChunks `fmap` helper where
37− helper = unsafeInterleaveIO $ do
38− x <- toSource e
37+toLBS :: Enumerator () -> IO L.ByteString
38+toLBS e = do
39+ source <- toSource e
40+ L.fromChunks `fmap` helper source
41+ where
42+ helper source = unsafeInterleaveIO $ do
43+ x <- source
3944 case x of
4045 Nothing -> return []
4146 Just x' -> do
42− xs <- helper
47+ xs <- helper source
4348 return $ x' : xs
4449
4550-- | This function safely converts a lazy bytestring into an enumerator.
@@ -61,8 +66,29 @@ fromLBS' lbs' iter a0 = lbs' >>= \lbs -> fromLBS lbs iter a0
6166-- | A source is a more standard way of accessing data from an 'Enumerator'.
6267-- Each time you call it, it returns the next chunk of data if available, or
6368-- 'Nothing' if the data has been completely consumed.
64−toSource :: Enumerator (Maybe B.ByteString) -> IO (Maybe B.ByteString)
65−toSource e = fmap (either id id) $ e (const $ return . Left . Just) Nothing
69+toSource :: Enumerator () -> IO (IO (Maybe B.ByteString))
70+toSource e = do
71+ buffer <- newEmptyMVar
72+ forkIO $ e (helper buffer) () >> putMVar buffer Nothing
73+ return $ source buffer
74+ where
75+ helper :: MVar (Maybe B.ByteString)
76+ -> ()
77+ -> B.ByteString
78+ -> IO (Either () ())
79+ helper buffer _ bs = do
80+ putMVar buffer $ Just bs
81+ return $ Right ()
82+ source :: MVar (Maybe B.ByteString) -> IO (Maybe B.ByteString)
83+ source mmbs = do
84+ mbs <- takeMVar mmbs
85+ case mbs of
86+ Nothing -> do
87+ -- By putting Nothing back in, the source can be called
88+ -- again without causing a deadlock.
89+ putMVar mmbs Nothing
90+ return Nothing
91+ Just bs -> return $ Just bs
6692
6793-- | Read a chunk of data from the given 'Handle' at a time. We use
6894-- 'defaultChunkSize' from the bytestring package to determine the largest