Skip to content

Commit 8f07cd7

Browse files
Use NoBuffering in File/FileIo APIs
1 parent 73b0a52 commit 8f07cd7

File tree

4 files changed

+74
-27
lines changed

4 files changed

+74
-27
lines changed

core/src/Streamly/FileSystem/FileIO.hs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@ module Streamly.FileSystem.FileIO
2727
-- documentation. One IO request may or may not read the full
2828
-- chunk. If the whole stream is not consumed, it is possible that we may
2929
-- read slightly more from the IO device than what the consumer needed.
30-
-- Unless specified otherwise in the API, writes are collected into chunks
31-
-- of @defaultChunkSize@ before they are written to the IO device.
30+
-- When writing, unless specified otherwise in the API, writes are
31+
-- collected into chunks of @defaultChunkSize@ before they are written to
32+
-- the IO device.
3233

3334
-- Streaming APIs work for all kind of devices, seekable or non-seekable;
3435
-- including disks, files, memory devices, terminals, pipes, sockets and

core/src/Streamly/FileSystem/Handle.hs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@
1414
-- Read and write byte streams and array streams to and from file handles
1515
-- ('Handle').
1616
--
17-
-- The 'TextEncoding', 'NewLineMode', and 'Buffering' options of the underlying
17+
-- Please set NoBuffering mode on the handle as buffering is explicitly
18+
-- controlled by the streaming API and double buffering can sometimes cause
19+
-- unexpected results.
20+
--
21+
-- Also note that the 'TextEncoding', 'NewLineMode' options of the underlying
1822
-- GHC 'Handle' are ignored by these APIs. Please use "Streamly.Unicode.Stream"
1923
-- module for encoding and decoding a byte stream, use stream splitting
2024
-- operations in "Streamly.Data.Stream" to create a stream of lines or to split

core/src/Streamly/Internal/FileSystem/File.hs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,8 @@ import Control.Monad.Catch (MonadCatch)
9898
import Control.Monad.IO.Class (MonadIO(..))
9999
import Data.Kind (Type)
100100
import Data.Word (Word8)
101-
import System.IO (Handle, openFile, IOMode(..), hClose)
101+
import System.IO
102+
(Handle, IOMode(..), openFile, hClose, hSetBuffering, BufferMode(..))
102103
import Prelude hiding (read)
103104

104105
import qualified Control.Monad.Catch as MC
@@ -150,7 +151,14 @@ import qualified Streamly.Internal.FileSystem.Handle as FH
150151
{-# INLINE withFile #-}
151152
withFile :: (MonadIO m, MonadCatch m)
152153
=> FilePath -> IOMode -> (Handle -> Stream m a) -> Stream m a
153-
withFile file mode = S.bracketIO (openFile file mode) hClose
154+
withFile file mode = S.bracketIO open hClose
155+
156+
where
157+
158+
open = do
159+
h <- openFile file mode
160+
hSetBuffering h NoBuffering
161+
return h
154162

155163
-- | Transform an 'Unfold' from a 'Handle' to an unfold from a 'FilePath'. The
156164
-- resulting unfold opens a handle in 'ReadMode', uses it using the supplied
@@ -163,7 +171,15 @@ withFile file mode = S.bracketIO (openFile file mode) hClose
163171
{-# INLINE usingFile #-}
164172
usingFile :: (MonadIO m, MonadCatch m)
165173
=> Unfold m Handle a -> Unfold m FilePath a
166-
usingFile = UF.bracketIO (`openFile` ReadMode) hClose
174+
usingFile = UF.bracketIO open hClose
175+
176+
where
177+
178+
open file = do
179+
h <- openFile file ReadMode
180+
hSetBuffering h NoBuffering
181+
return h
182+
167183

168184
{-# INLINE usingFile2 #-}
169185
usingFile2 :: (MonadIO m, MonadCatch m)
@@ -174,6 +190,7 @@ usingFile2 = UF.bracketIO before after
174190

175191
before (x, file) = do
176192
h <- openFile file ReadMode
193+
hSetBuffering h NoBuffering
177194
return (x, h)
178195

179196
after (_, h) = hClose h
@@ -187,6 +204,7 @@ usingFile3 = UF.bracketIO before after
187204

188205
before (x, y, z, file) = do
189206
h <- openFile file ReadMode
207+
hSetBuffering h NoBuffering
190208
return (x, y, z, h)
191209

192210
after (_, _, _, h) = hClose h
@@ -439,6 +457,7 @@ writeChunks path = Fold step initial extract final
439457
where
440458
initial = do
441459
h <- liftIO (openFile path WriteMode)
460+
liftIO $ hSetBuffering h NoBuffering
442461
fld <- FL.reduce (FH.writeChunks h)
443462
`MC.onException` liftIO (hClose h)
444463
return $ FL.Partial (fld, h)

core/src/Streamly/Internal/FileSystem/FileIO.hs

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,9 @@ module Streamly.Internal.FileSystem.FileIO
6868
, writeChunks
6969

7070
-- ** Writing Streams
71-
, fromBytes -- putBytes?
72-
, fromBytesWith
73-
, fromChunks
71+
, fromBytes -- XXX putBytes?
72+
, fromBytesWith -- putBytesWith
73+
, fromChunks -- putChunks?
7474

7575
-- ** Append To File
7676
, writeAppend
@@ -84,7 +84,7 @@ where
8484
import Control.Monad.Catch (MonadCatch)
8585
import Control.Monad.IO.Class (MonadIO(..))
8686
import Data.Word (Word8)
87-
import System.IO (Handle, IOMode(..), hClose)
87+
import System.IO (Handle, IOMode(..), hClose, hSetBuffering, BufferMode(..))
8888
import Prelude hiding (read)
8989

9090
import qualified Control.Monad.Catch as MC
@@ -130,32 +130,52 @@ import qualified Streamly.Internal.FileSystem.Windows.File as File
130130
-- Safe file reading
131131
-------------------------------------------------------------------------------
132132

133-
-- | @'withFile' name mode act@ opens a file using 'openFile' and passes
134-
-- the resulting handle to the computation @act@. The handle will be
135-
-- closed on exit from 'withFile', whether by normal termination or by
136-
-- raising an exception. If closing the handle raises an exception, then
137-
-- this exception will be raised by 'withFile' rather than any exception
138-
-- raised by 'act'.
133+
-- | @'withFile' name mode act@ opens a file with NoBuffering set on the handle
134+
-- and passes the resulting handle to the computation @act@. The handle will be
135+
-- closed on exit from 'withFile', whether by normal termination or by raising
136+
-- an exception. If closing the handle raises an exception, then that
137+
-- exception is raised by 'withFile' rather than any exception raised by 'act'.
138+
--
139+
-- The file is opened without buffering as buffering can be controlled by the
140+
-- streaming APIs.
139141
--
140142
-- /Pre-release/
141143
--
142144
{-# INLINE withFile #-}
143145
withFile :: (MonadIO m, MonadCatch m)
144146
=> Path -> IOMode -> (Handle -> Stream m a) -> Stream m a
145-
withFile file mode = S.bracketIO (File.openFile file mode) hClose
147+
withFile file mode = S.bracketIO open hClose
148+
149+
where
146150

147-
-- | Transform an 'Unfold' from a 'Handle' to an unfold from a 'Path'. The
148-
-- resulting unfold opens a handle in 'ReadMode', uses it using the supplied
149-
-- unfold and then makes sure that the handle is closed on normal termination
150-
-- or in case of an exception. If closing the handle raises an exception, then
151-
-- this exception will be raised by 'usingFile'.
151+
open = do
152+
h <- File.openFile file mode
153+
hSetBuffering h NoBuffering
154+
return h
155+
156+
-- | Transform an 'Unfold' that takes 'Handle' as input to an unfold that takes
157+
-- a 'Path' as input. The resulting unfold opens the file in 'ReadMode',
158+
-- passes it to the supplied unfold and then makes sure that the handle is
159+
-- closed on normal termination or in case of an exception. If closing the
160+
-- handle raises an exception, then this exception will be raised by
161+
-- 'usingFile'.
162+
--
163+
-- The file is opened without buffering as buffering can be controlled by the
164+
-- streaming APIs.
152165
--
153166
-- /Pre-release/
154167
--
155168
{-# INLINE usingFile #-}
156169
usingFile :: (MonadIO m, MonadCatch m)
157170
=> Unfold m Handle a -> Unfold m Path a
158-
usingFile = UF.bracketIO (`File.openFile` ReadMode) hClose
171+
usingFile = UF.bracketIO open hClose
172+
173+
where
174+
175+
open file = do
176+
h <- File.openFile file ReadMode
177+
hSetBuffering h NoBuffering
178+
return h
159179

160180
{-# INLINE usingFile2 #-}
161181
usingFile2 :: (MonadIO m, MonadCatch m)
@@ -166,6 +186,7 @@ usingFile2 = UF.bracketIO before after
166186

167187
before (x, file) = do
168188
h <- File.openFile file ReadMode
189+
hSetBuffering h NoBuffering
169190
return (x, h)
170191

171192
after (_, h) = hClose h
@@ -179,6 +200,7 @@ usingFile3 = UF.bracketIO before after
179200

180201
before (x, y, z, file) = do
181202
h <- File.openFile file ReadMode
203+
hSetBuffering h NoBuffering
182204
return (x, y, z, h)
183205

184206
after (_, _, _, h) = hClose h
@@ -201,7 +223,7 @@ usingFile3 = UF.bracketIO before after
201223
putChunk :: Path -> Array a -> IO ()
202224
putChunk file arr = File.withFile file WriteMode (`FH.putChunk` arr)
203225

204-
-- | append an array to a file.
226+
-- | Append an array to a file.
205227
--
206228
-- /Pre-release/
207229
--
@@ -378,17 +400,18 @@ write :: (MonadIO m, Storable a) => Handle -> Stream m a -> m ()
378400
write = toHandleWith A.defaultChunkSize
379401
-}
380402

381-
-- | Write a stream of chunks to a handle. Each chunk in the stream is written
382-
-- to the device as a separate IO request.
403+
-- | Write a stream of chunks to a file. Each chunk in the stream is written
404+
-- immediately to the device as a separate IO request, without coalescing or
405+
-- buffering.
383406
--
384-
-- /Pre-release/
385407
{-# INLINE writeChunks #-}
386408
writeChunks :: (MonadIO m, MonadCatch m)
387409
=> Path -> Fold m (Array a) ()
388410
writeChunks path = Fold step initial extract final
389411
where
390412
initial = do
391413
h <- liftIO (File.openFile path WriteMode)
414+
liftIO $ hSetBuffering h NoBuffering
392415
fld <- FL.reduce (FH.writeChunks h)
393416
`MC.onException` liftIO (hClose h)
394417
return $ FL.Partial (fld, h)

0 commit comments

Comments
 (0)