on master help sign in
Non-resumable enumerators
556030cMichael Snoyman
Network/Wai.hs+5
@@ -336,6 +336,11 @@ statusMessage (Status _ m) = m
336-- 'Enumerator's are used twice in the WAI: once in the 'Request' datatype to
336-- 'Enumerator's are used twice in the WAI: once in the 'Request' datatype to
337-- access the request body, and once in the 'Response' datatype for the
337-- access the request body, and once in the 'Response' datatype for the
338-- application to return the body of the response.
338-- 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.
339type Enumerator a = (a -> B.ByteString -> IO (Either a a))
344type Enumerator a = (a -> B.ByteString -> IO (Either a a))
340 -> a
345 -> a
341 -> IO (Either a a)
346 -> IO (Either a a)
Network/Wai/Enumerator.hs+33−7
@@ -23,6 +23,8 @@ import qualified Data.ByteString as B
23import System.IO.Unsafe (unsafeInterleaveIO)
23import System.IO.Unsafe (unsafeInterleaveIO)
24import System.IO (withBinaryFile, IOMode (ReadMode), Handle, hIsEOF)
24import System.IO (withBinaryFile, IOMode (ReadMode), Handle, hIsEOF)
25import Data.ByteString.Lazy.Internal (defaultChunkSize)
25import Data.ByteString.Lazy.Internal (defaultChunkSize)
26+import Control.Concurrent (forkIO)
27+import Control.Concurrent.MVar
26
28
27-- | Performs a specified conversion on each 'B.ByteString' output by an
29-- | Performs a specified conversion on each 'B.ByteString' output by an
28-- enumerator.
30-- enumerator.
@@ -32,14 +34,17 @@ mapE f e iter = e iter' where
32
34
33-- | This uses 'unsafeInterleaveIO' to lazily read from an enumerator. All
35-- | This uses 'unsafeInterleaveIO' to lazily read from an enumerator. All
34-- normal lazy I/O warnings apply.
36-- normal lazy I/O warnings apply.
35−toLBS :: Enumerator (Maybe B.ByteString) -> IO L.ByteString
37+toLBS :: Enumerator () -> IO L.ByteString
38+toLBS e = do
39+ source <- toSource e
36−toLBS e = L.fromChunks `fmap` helper where
40+ L.fromChunks `fmap` helper source
41+ where
37− helper = unsafeInterleaveIO $ do
42+ helper source = unsafeInterleaveIO $ do
38− x <- toSource e
43+ x <- source
39 case x of
44 case x of
40 Nothing -> return []
45 Nothing -> return []
41 Just x' -> do
46 Just x' -> do
42− xs <- helper
47+ xs <- helper source
43 return $ x' : xs
48 return $ x' : xs
44
49
45-- | This function safely converts a lazy bytestring into an enumerator.
50-- | This function safely converts a lazy bytestring into an enumerator.
@@ -61,8 +66,29 @@ fromLBS' lbs' iter a0 = lbs' >>= \lbs -> fromLBS lbs iter a0
61-- | A source is a more standard way of accessing data from an 'Enumerator'.
66-- | A source is a more standard way of accessing data from an 'Enumerator'.
62-- Each time you call it, it returns the next chunk of data if available, or
67-- Each time you call it, it returns the next chunk of data if available, or
63-- 'Nothing' if the data has been completely consumed.
68-- 'Nothing' if the data has been completely consumed.
64−toSource :: Enumerator (Maybe B.ByteString) -> IO (Maybe B.ByteString)
69+toSource :: Enumerator () -> IO (IO (Maybe B.ByteString))
65−toSource e = fmap (either id id) $ e (const $ return . Left . Just) Nothing
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
66
92
67-- | Read a chunk of data from the given 'Handle' at a time. We use
93-- | Read a chunk of data from the given 'Handle' at a time. We use
68-- 'defaultChunkSize' from the bytestring package to determine the largest
94-- 'defaultChunkSize' from the bytestring package to determine the largest