streamly-core 0.3.1 → 0.3.2
raw patch · 31 files changed
+407/−156 lines, 31 filesPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
API changes (from Hackage documentation)
+ Streamly.Internal.Data.IORef: mkWeakIORef :: IORef a -> IO () -> IO (Weak (IORef a))
+ Streamly.Internal.Data.IORef: touchIORef :: IORef a -> IO ()
- Streamly.Internal.Data.Unfold: before :: (a -> m c) -> Unfold m a b -> Unfold m a b
+ Streamly.Internal.Data.Unfold: before :: Monad m => (a -> m c) -> Unfold m a b -> Unfold m a b
Files
- Changelog.md +36/−0
- configure +9/−9
- configure.ac +1/−1
- docs/Changelog.md +36/−0
- src/Streamly/Internal/Data/Array/Type.hs +1/−1
- src/Streamly/Internal/Data/Fold/Combinators.hs +7/−3
- src/Streamly/Internal/Data/Fold/Container.hs +4/−4
- src/Streamly/Internal/Data/Fold/Exception.hs +22/−11
- src/Streamly/Internal/Data/Fold/Type.hs +3/−2
- src/Streamly/Internal/Data/IORef.hs +25/−1
- src/Streamly/Internal/Data/MutArray/Generic.hs +7/−3
- src/Streamly/Internal/Data/MutArray/Type.hs +13/−6
- src/Streamly/Internal/Data/MutByteArray/Type.hs +5/−9
- src/Streamly/Internal/Data/RingArray.hs +19/−14
- src/Streamly/Internal/Data/RingArray/Generic.hs +6/−2
- src/Streamly/Internal/Data/Scanl/Combinators.hs +8/−6
- src/Streamly/Internal/Data/Scanl/Container.hs +2/−2
- src/Streamly/Internal/Data/Serialize/TH.hs +9/−1
- src/Streamly/Internal/Data/Serialize/TH/RecHeader.hs +5/−1
- src/Streamly/Internal/Data/Serialize/Type.hs +2/−0
- src/Streamly/Internal/Data/Stream/Eliminate.hs +16/−8
- src/Streamly/Internal/Data/Stream/Generate.hs +1/−1
- src/Streamly/Internal/Data/Stream/Nesting.hs +2/−5
- src/Streamly/Internal/Data/Stream/Transform.hs +16/−4
- src/Streamly/Internal/Data/Stream/Type.hs +20/−8
- src/Streamly/Internal/Data/StreamK/Type.hs +5/−0
- src/Streamly/Internal/Data/Time/Clock.hs +63/−26
- src/Streamly/Internal/Data/Unfold.hs +2/−2
- src/Streamly/Internal/Data/Unfold/Enumeration.hs +20/−5
- src/Streamly/Internal/FileSystem/Posix/File.hsc +41/−20
- streamly-core.cabal +1/−1
Changelog.md view
@@ -1,5 +1,41 @@ # Changelog +## 0.3.2 (Oct 2026)++### Bug Fixes++* On POSIX platforms, the `Path` based APIs that open an existing file in+ `WriteMode`, e.g. `write` and `withFile` in `Streamly.FileSystem.FileIO`,+ did not truncate it, the old contents beyond the newly written data+ remained. The file descriptor leaked when the file could not be opened as+ a Handle, e.g. if it was a directory.+* Fix `timer`, `timesWith`, `absTimesWith` and `relTimesWith` stopping after a+ major GC.+* Fix `enumerateFrom` and `enumerateFromTo` in Stream and Unfold not+ terminating for bounded integral types when the limit is `maxBound`.+* Fix `Unfold.enumerateFromTo` for `Identity` calling `enumerateFromThen`.+* Fix `Stream.splitEndBySeq` and `splitEndBySeq_` dropping the last segment+ for separators longer than a machine word.+* `chunksOf` in the array modules no longer emits a trailing empty array+ when the stream length is a multiple of the chunk size, or when the stream+ is empty.+* Fix `classify` and `classifyIO` in Fold and Scanl, and `Fold.classifyScan`,+ restarting the fold for a key that terminated on its first input.+* Fix `Scanl.uniqBy` failing before the first input.+* Fix `Stream.postscanl` emitting an output for a scan that terminates+ without consuming any input.+* Fix `Stream.isInfixOf` returning `False` for an empty infix.+* Fix `RingArray.getIndex`, `cast`, `fold`, `eqArray` and `eqArrayN` for+ out-of-range indices, misaligned heads, empty rings and array slices.+* Fix deserialization reading beyond the end of a corrupt or truncated buffer+ for lists and for derived `Serialize` instances.++### Internal API Bug Fixes++* Fix memory corruption in `MutArray.appendMax`, `MutArray.createMinOf` and+ `RingArray.Generic.copyToMutArray`, and resource release in+ `Fold.bracketIO` and `Fold.finallyIO`.+ ## 0.3.1 (May 2026) * Fixed `Path.fromString` truncation when unicode chars are present.
configure view
@@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles.-# Generated by GNU Autoconf 2.72 for streamly-core 0.3.1.+# Generated by GNU Autoconf 2.72 for streamly-core 0.3.2. # # Report bugs to <streamly@composewell.com>. #@@ -603,8 +603,8 @@ # Identity of this package. PACKAGE_NAME='streamly-core' PACKAGE_TARNAME='streamly-core'-PACKAGE_VERSION='0.3.1'-PACKAGE_STRING='streamly-core 0.3.1'+PACKAGE_VERSION='0.3.2'+PACKAGE_STRING='streamly-core 0.3.2' PACKAGE_BUGREPORT='streamly@composewell.com' PACKAGE_URL='https://streamly.composewell.com' @@ -1249,7 +1249,7 @@ # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF-'configure' configures streamly-core 0.3.1 to adapt to many kinds of systems.+'configure' configures streamly-core 0.3.2 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1311,7 +1311,7 @@ if test -n "$ac_init_help"; then case $ac_init_help in- short | recursive ) echo "Configuration of streamly-core 0.3.1:";;+ short | recursive ) echo "Configuration of streamly-core 0.3.2:";; esac cat <<\_ACEOF @@ -1397,7 +1397,7 @@ test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF-streamly-core configure 0.3.1+streamly-core configure 0.3.2 generated by GNU Autoconf 2.72 Copyright (C) 2023 Free Software Foundation, Inc.@@ -1621,7 +1621,7 @@ This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by streamly-core $as_me 0.3.1, which was+It was created by streamly-core $as_me 0.3.2, which was generated by GNU Autoconf 2.72. Invocation command line was $ $0$ac_configure_args_raw@@ -3881,7 +3881,7 @@ # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log="-This file was extended by streamly-core $as_me 0.3.1, which was+This file was extended by streamly-core $as_me 0.3.2, which was generated by GNU Autoconf 2.72. Invocation command line was CONFIG_FILES = $CONFIG_FILES@@ -3937,7 +3937,7 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\-streamly-core config.status 0.3.1+streamly-core config.status 0.3.2 configured by $0, generated by GNU Autoconf 2.72, with options \\"\$ac_cs_config\\"
configure.ac view
@@ -3,7 +3,7 @@ # See https://www.gnu.org/software/autoconf/manual/autoconf.html for help on # the macros used in this file. -AC_INIT([streamly-core], [0.3.1], [streamly@composewell.com], [streamly-core], [https://streamly.composewell.com])+AC_INIT([streamly-core], [0.3.2], [streamly@composewell.com], [streamly-core], [https://streamly.composewell.com]) # To suppress "WARNING: unrecognized options: --with-compiler" AC_ARG_WITH([compiler], [GHC])
docs/Changelog.md view
@@ -1,5 +1,41 @@ # Changelog +## 0.3.2 (Oct 2026)++### Bug Fixes++* On POSIX platforms, the `Path` based APIs that open an existing file in+ `WriteMode`, e.g. `write` and `withFile` in `Streamly.FileSystem.FileIO`,+ did not truncate it, the old contents beyond the newly written data+ remained. The file descriptor leaked when the file could not be opened as+ a Handle, e.g. if it was a directory.+* Fix `timer`, `timesWith`, `absTimesWith` and `relTimesWith` stopping after a+ major GC.+* Fix `enumerateFrom` and `enumerateFromTo` in Stream and Unfold not+ terminating for bounded integral types when the limit is `maxBound`.+* Fix `Unfold.enumerateFromTo` for `Identity` calling `enumerateFromThen`.+* Fix `Stream.splitEndBySeq` and `splitEndBySeq_` dropping the last segment+ for separators longer than a machine word.+* `chunksOf` in the array modules no longer emits a trailing empty array+ when the stream length is a multiple of the chunk size, or when the stream+ is empty.+* Fix `classify` and `classifyIO` in Fold and Scanl, and `Fold.classifyScan`,+ restarting the fold for a key that terminated on its first input.+* Fix `Scanl.uniqBy` failing before the first input.+* Fix `Stream.postscanl` emitting an output for a scan that terminates+ without consuming any input.+* Fix `Stream.isInfixOf` returning `False` for an empty infix.+* Fix `RingArray.getIndex`, `cast`, `fold`, `eqArray` and `eqArrayN` for+ out-of-range indices, misaligned heads, empty rings and array slices.+* Fix deserialization reading beyond the end of a corrupt or truncated buffer+ for lists and for derived `Serialize` instances.++### Internal API Bug Fixes++* Fix memory corruption in `MutArray.appendMax`, `MutArray.createMinOf` and+ `RingArray.Generic.copyToMutArray`, and resource release in+ `Fold.bracketIO` and `Fold.finallyIO`.+ ## 0.3.1 (May 2026) * Fixed `Path.fromString` truncation when unicode chars are present.
src/Streamly/Internal/Data/Array/Type.hs view
@@ -641,7 +641,7 @@ unsnoc arr = if null arr then Nothing- else Just (unsafeTail arr, unsafeGetIndexRev 0 arr)+ else Just (unsafeInit arr, unsafeGetIndexRev 0 arr) ------------------------------------------------------------------------------- -- Streams of arrays
src/Streamly/Internal/Data/Fold/Combinators.hs view
@@ -1780,7 +1780,7 @@ -- >>> ref <- newIORef [Fold.take 2 Fold.sum, Fold.take 2 Fold.length :: Fold IO Int Int] -- >>> gen = atomicModifyIORef ref (\xs -> ([], xs)) -- >>> Stream.toList $ Stream.scanl (Fold.distributeScan gen) (Stream.enumerateFromTo 1 10)--- [[],[],[],[2,3],[],[],[],[],[],[],[]]+-- [[],[],[3,2],[],[],[],[],[],[],[],[]] -- {-# INLINE distributeScan #-} distributeScan :: Monad m => m [Fold m a b] -> Scanl m a [b]@@ -1796,7 +1796,11 @@ case res of Partial fs -> do r <- step fs a- run (Tuple' (Fold step (return r) extr fin : ys) zs) xs a+ case r of+ Partial fs1 ->+ let fld = Fold step (return (Partial fs1)) extr fin+ in run (Tuple' (fld : ys) zs) xs a+ Done b -> run (Tuple' ys (b : zs)) xs a Done b -> do run (Tuple' ys (b : zs)) xs a @@ -2129,7 +2133,7 @@ {-# INLINE sampleFromthen #-} sampleFromthen :: Monad m => Int -> Int -> Fold m a b -> Fold m a b sampleFromthen offset size =- with indexed filter (\(i, _) -> (i + offset) `mod` size == 0)+ with indexed filter (\(i, _) -> i >= offset && (i - offset) `mod` size == 0) ------------------------------------------------------------------------------ -- Nesting
src/Streamly/Internal/Data/Fold/Container.hs view
@@ -929,7 +929,7 @@ Partial s1 -> Tuple3' (IsMap.mapInsert k s1 kv) set Nothing Done b ->- Tuple3' kv set (Just (k, b))+ Tuple3' kv (Set.insert k set) (Just (k, b)) Done b -> return (Tuple3' kv (Set.insert k set) (Just (k, b))) step (Tuple3' kv set _) a = do@@ -1034,7 +1034,7 @@ Partial s1 -> Tuple3' (IsMap.mapInsert k s1 kv) set Nothing Done b ->- Tuple3' kv set (Just (k, b))+ Tuple3' kv (Set.insert k set) (Just (k, b)) Done b -> return (Tuple3' kv (Set.insert k set) (Just (k, b))) step (Tuple3' kv set _) a = do@@ -1124,7 +1124,7 @@ ref <- liftIO $ newIORef s1 return $ Tuple3' (IsMap.mapInsert k ref kv) set Nothing Done b ->- return $ Tuple3' kv set (Just (k, b))+ return $ Tuple3' kv (Set.insert k set) (Just (k, b)) Done b -> return (Tuple3' kv (Set.insert k set) (Just (k, b))) step (Tuple3' kv set _) a = do@@ -1240,7 +1240,7 @@ ref <- liftIO $ newIORef s1 return $ Tuple3' (IsMap.mapInsert k ref kv) set Nothing Done b ->- return $ Tuple3' kv set (Just (k, b))+ return $ Tuple3' kv (Set.insert k set) (Just (k, b)) Done b -> return (Tuple3' kv (Set.insert k set) (Just (k, b))) step (Tuple3' kv set _) a = do
src/Streamly/Internal/Data/Fold/Exception.hs view
@@ -138,9 +138,14 @@ res <- initial1 case res of Partial s -> do- s1 <- step1 s a `MC.onException` liftIO (runIOFinalizer ref)- let fld1 = Fold step1 (pure s1) extract1 final1- pure $ Partial $ Tuple' ref fld1+ r <- step1 s a `MC.onException` liftIO (runIOFinalizer ref)+ case r of+ Partial s1 -> do+ let fld1 = Fold step1 (pure (Partial s1)) extract1 final1+ pure $ Partial $ Tuple' ref fld1+ Done b -> do+ liftIO $ runIOFinalizer ref+ pure $ Done b Done b -> do liftIO $ runIOFinalizer ref pure $ Done b@@ -156,9 +161,11 @@ case res of Partial s -> do val <- final1 s `MC.onException` liftIO (runIOFinalizer ref)- runIOFinalizer ref+ liftIO $ runIOFinalizer ref pure val- Done b -> pure b+ Done b -> do+ liftIO $ runIOFinalizer ref+ pure b -- | Run a side effect whenever the fold stops normally, aborts due to an -- exception or is garbage collected.@@ -173,15 +180,19 @@ initial = do ref <- liftIO $ newIOFinalizer aft res <- initial1 `MC.onException` liftIO (runIOFinalizer ref)- pure $ case res of- Done b -> Done b- Partial s -> Partial $ Tuple' ref s+ case res of+ Done b -> do+ liftIO $ runIOFinalizer ref+ pure $ Done b+ Partial s -> pure $ Partial $ Tuple' ref s step (Tuple' ref s) a = do res <- step1 s a `MC.onException` liftIO (runIOFinalizer ref)- pure $ case res of- Done b -> Done b- Partial s1 -> Partial $ Tuple' ref s1+ case res of+ Done b -> do+ liftIO $ runIOFinalizer ref+ pure $ Done b+ Partial s1 -> pure $ Partial $ Tuple' ref s1 extract (Tuple' ref s) = extract1 s `MC.onException` liftIO (runIOFinalizer ref)
src/Streamly/Internal/Data/Fold/Type.hs view
@@ -2435,7 +2435,8 @@ Partial s -> cextract s Done b -> return b --- | Extract the output of a fold and refold it using a 'Refold'.+-- | Finalize a fold without any input and use its output as the initial+-- value of a 'Refold'. -- -- A fusible alternative to 'concatMap'. --@@ -2443,7 +2444,7 @@ {-# INLINE refold #-} refold :: Monad m => Refold m b a c -> Fold m a b -> Fold m a c refold (Refold step inject extract) f =- Fold step (extractM f >>= inject) extract extract+ Fold step (finalM f >>= inject) extract extract ------------------------------------------------------------------------------ -- morphInner
src/Streamly/Internal/Data/IORef.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE UnboxedTuples #-}+ -- | -- Module : Streamly.Internal.Data.IORef -- Copyright : (c) 2019 Composewell Technologies@@ -25,6 +27,8 @@ -- Construction , newIORef+ , mkWeakIORef+ , touchIORef -- Write , writeIORef@@ -45,7 +49,10 @@ import Data.Kind (Type) #endif import Data.Proxy (Proxy(..))-import Streamly.Internal.Data.MutByteArray.Type (MutByteArray)+import GHC.Exts (mkWeak#)+import GHC.IO (IO(..))+import GHC.Weak (Weak(..))+import Streamly.Internal.Data.MutByteArray.Type (MutByteArray(..)) import Streamly.Internal.Data.Unbox (Unbox(..), sizeOf) import qualified Streamly.Internal.Data.MutByteArray.Type as MBA@@ -66,6 +73,23 @@ var <- MBA.new (sizeOf (Proxy :: Proxy a)) pokeAt 0 var x return $ IORef var++-- | Make a 'Weak' pointer to an 'IORef', with a finalizer that runs when the+-- 'IORef' is no longer reachable. The key is the underlying mutable byte+-- array, not the 'IORef' box, which the compiler may unbox and discard.+--+-- /Pre-release/+mkWeakIORef :: IORef a -> IO () -> IO (Weak (IORef a))+mkWeakIORef ref@(IORef (MutByteArray arr#)) (IO finalizer) = IO $ \s ->+ case mkWeak# arr# ref finalizer s of+ (# s1, w #) -> (# s1, Weak w #)++-- | Keep the 'IORef' reachable at least up to this point, see 'mkWeakIORef'.+--+-- /Pre-release/+{-# INLINE touchIORef #-}+touchIORef :: IORef a -> IO ()+touchIORef (IORef var) = MBA.touch var -- | Write a value to an 'IORef'. --
src/Streamly/Internal/Data/MutArray/Generic.hs view
@@ -802,7 +802,10 @@ return $ D.Skip (GroupBuffer s contents start end bound) D.Stop -> return- $ D.Skip (GroupYield contents start end bound GroupFinish)+ $ if end == start+ then D.Stop+ else D.Skip+ (GroupYield contents start end bound GroupFinish) step' _ (GroupYield contents start end bound next) = return $ D.Yield (MutArray contents start end bound) next@@ -873,8 +876,9 @@ clone src = do let len = length src dst <- new len- unsafePutSlice src 0 dst 0 len- return dst+ let dst1 = dst { arrEnd = arrStart dst + len }+ unsafePutSlice src 0 dst1 0 len+ return dst1 ------------------------------------------------------------------------------- -- Size
src/Streamly/Internal/Data/MutArray/Type.hs view
@@ -1567,7 +1567,9 @@ r <- stepi defState st case r of D.Yield i s -> do- x <- liftio $ getIndex i (MutArray contents start end undefined)+ -- We do not need arrBound but we cannot leave it undefined+ -- because it is a strict field.+ x <- liftio $ getIndex i (MutArray contents start end end) case x of Just v -> return $ D.Yield v (GetIndicesState contents start end s) Nothing -> error "Invalid Index"@@ -1916,7 +1918,10 @@ return $ D.Skip (GroupBuffer s contents start end bound) D.Stop -> return- $ D.Skip (GroupYield contents start end bound GroupFinish)+ $ if end == start+ then D.Stop+ else D.Skip+ (GroupYield contents start end bound GroupFinish) step' _ (GroupYield contents start end bound next) = return $ D.Yield (MutArray contents start end bound) next@@ -2392,8 +2397,9 @@ where free_ = arrBound - arrEnd- needed = n * SIZE_OF(a)- bound = arrBound + needed - free_+ -- Keep "needed" to be at least free_ otherwise when recompute the bound in+ -- "final" we may lose some reserve space.+ needed = max (n * SIZE_OF(a)) free_ initial = do assert (n >= 0) (return ())@@ -2408,8 +2414,9 @@ liftIO $ pokeAt end contents x return $ ArrayUnsafe contents start (INDEX_NEXT(end,a)) + -- If the array gets reallocated the original bound does not remain valid. final (ArrayUnsafe contents start end) =- MutArray contents start end bound+ MutArray contents start end (start + arrEnd - arrStart + needed) {-# DEPRECATED writeAppendNUnsafe "Please use unsafeAppendN instead." #-} {-# INLINE writeAppendNUnsafe #-}@@ -2779,7 +2786,7 @@ step arr@(MutArray _ start end bound) x | INDEX_NEXT(end,a) > bound = do let oldSize = end - start- newSize = max (oldSize * 2) 1+ newSize = max (oldSize * 2) (SIZE_OF(a)) arr1 <- liftIO $ reallocExplicitAs ps (SIZE_OF(a)) newSize arr unsafeSnoc arr1 x step arr x = unsafeSnoc arr x
src/Streamly/Internal/Data/MutByteArray/Type.hs view
@@ -360,16 +360,12 @@ :: MutByteArray -> Int -> MutByteArray -> Int -> Int -> IO Int unsafeByteCmp (MutByteArray marr1) (I# st1#) (MutByteArray marr2) (I# st2#) (I# len#) =+ -- compareByteArrays# must be evaluated here, a lazy result would read the+ -- arrays after later mutations. IO $ \s# ->- let res =- I#- (compareByteArrays#- (unsafeCoerce# marr1)- st1#- (unsafeCoerce# marr2)- st2#- len#)- in (# s#, res #)+ case compareByteArrays#+ (unsafeCoerce# marr1) st1# (unsafeCoerce# marr2) st2# len# of+ res# -> (# s#, I# res# #) ------------------------------------------------------------------------------- -- Pinning & Unpinning
src/Streamly/Internal/Data/RingArray.hs view
@@ -416,13 +416,14 @@ -- | /O(1)/ Lookup the element at the given index relative to the ring head. -- Index starts from 0, could be positive or negative. Returns Nothing if the--- index is more than or equal to the size of the ring.+-- index is more than or equal to the length of the ring, or less than the+-- negated length. -- {-# INLINE getIndex #-} getIndex :: forall m a. (MonadIO m, Unbox a) => Int -> RingArray a -> m (Maybe a) getIndex i ring =- let rs = ringSize ring- in if i < rs && i > -rs+ let len = length ring+ in if i < len && i >= -len then Just <$> unsafeGetIndex i ring else return Nothing @@ -623,15 +624,17 @@ asBytes = unsafeCast -- | Cast a ring having elements of type @a@ into a ring having elements of--- type @b@. The length of the ring should be a multiple of the size of the--- target element otherwise 'Nothing' is returned.+-- type @b@. The byte length of the ring and the byte offset of the ring head+-- must be multiples of the size of the target element otherwise 'Nothing' is+-- returned. -- {-# INLINE cast #-} cast :: forall a b. (Unbox b) => RingArray a -> Maybe (RingArray b) cast ring = let len = byteLength ring r = len `mod` SIZE_OF(b)- in if r /= 0+ rh = ringHead ring `mod` SIZE_OF(b)+ in if r /= 0 || rh /= 0 then Nothing else Just $ unsafeCast ring @@ -653,14 +656,15 @@ | nBytes <= p1Len = do part1 <- MutByteArray.unsafeByteCmp- arrContents 0 ringContents ringHead nBytes+ arrContents arrStart ringContents ringHead nBytes pure $ part1 == 0 | otherwise = do part1 <- MutByteArray.unsafeByteCmp- arrContents 0 ringContents ringHead p1Len+ arrContents arrStart ringContents ringHead p1Len part2 <-- MutByteArray.unsafeByteCmp arrContents p1Len ringContents 0 p2Len+ MutByteArray.unsafeByteCmp+ arrContents (arrStart + p1Len) ringContents 0 p2Len pure $ part1 == 0 && part2 == 0 where p1Len = ringSize - ringHead@@ -680,10 +684,10 @@ | otherwise = do part1 <- MutByteArray.unsafeByteCmp- arrContents 0 ringContents ringHead p1Len+ arrContents arrStart ringContents ringHead p1Len part2 <- MutByteArray.unsafeByteCmp- arrContents p1Len ringContents 0 p2Len+ arrContents (arrStart + p1Len) ringContents 0 p2Len pure $ part1 == 0 && part2 == 0 where p1Len = ringSize - ringHead@@ -707,7 +711,10 @@ fold (Fold step initial _ final) rb = do res <- initial case res of- Fold.Partial fs -> go SPEC rh fs+ Fold.Partial fs ->+ if ringSize rb == 0+ then final fs+ else go SPEC rh fs Fold.Done b -> return b where@@ -767,8 +774,6 @@ go acc1 (index + SIZE_OF(a)) -- | Fold the entire length of a ring buffer starting at the current ring head.------ Note, this will crash on ring of 0 size. -- {-# INLINE foldlM' #-} foldlM' :: forall m a b. (MonadIO m, Unbox a)
src/Streamly/Internal/Data/RingArray/Generic.hs view
@@ -165,8 +165,12 @@ end = idx + len arr <- MutArray.emptyOf len arr1 <- MutArray.uninit arr len- MutArray.unsafePutSlice ringArr idx arr1 0 (ringMax - idx)- MutArray.unsafePutSlice ringArr 0 arr1 (ringMax - idx) (end - ringMax)+ if end <= ringMax+ then MutArray.unsafePutSlice ringArr idx arr1 0 len+ else do+ MutArray.unsafePutSlice ringArr idx arr1 0 (ringMax - idx)+ MutArray.unsafePutSlice+ ringArr 0 arr1 (ringMax - idx) (end - ringMax) return arr1 -- This would be theoretically slower than toMutArray because of a branch
src/Streamly/Internal/Data/Scanl/Combinators.hs view
@@ -640,14 +640,16 @@ -- {-# INLINE uniqBy #-} uniqBy :: Monad m => (a -> a -> Bool) -> Scanl m a (Maybe a)-uniqBy eq = rollingMap f+uniqBy eq = fmap (\(Tuple' _ x) -> x) $ mkScanl step (Tuple' Nothing Nothing) where - f pre curr =- case pre of- Nothing -> Just curr- Just x -> if x `eq` curr then Nothing else Just curr+ step (Tuple' pre _) curr =+ let out =+ case pre of+ Just x | x `eq` curr -> Nothing+ _ -> Just curr+ in Tuple' (Just curr) out -- | See 'uniqBy'. --@@ -2110,7 +2112,7 @@ {-# INLINE sampleFromthen #-} sampleFromthen :: Monad m => Int -> Int -> Scanl m a b -> Scanl m a b sampleFromthen offset size =- with indexed filter (\(i, _) -> (i + offset) `mod` size == 0)+ with indexed filter (\(i, _) -> i >= offset && (i - offset) `mod` size == 0) ------------------------------------------------------------------------------ -- Nesting
src/Streamly/Internal/Data/Scanl/Container.hs view
@@ -600,7 +600,7 @@ return $ Tuple3' (IsMap.mapInsert k s1 kv) set (Just (k, b)) Done b ->- return $ Tuple3' kv set (Just (k, b))+ return $ Tuple3' kv (Set.insert k set) (Just (k, b)) Done b -> return (Tuple3' kv (Set.insert k set) (Just (k, b))) step (Tuple3' kv set _) a = do@@ -685,7 +685,7 @@ $ Tuple3' (IsMap.mapInsert k ref kv) set (Just (k, b)) Done b ->- return $ Tuple3' kv set (Just (k, b))+ return $ Tuple3' kv (Set.insert k set) (Just (k, b)) Done b -> return (Tuple3' kv (Set.insert k set) (Just (k, b))) step (Tuple3' kv set _) a = do
src/Streamly/Internal/Data/Serialize/TH.hs view
@@ -201,7 +201,15 @@ $(varE _initialOffset) $(varE _arr) $(varE _endOffset)- $(multiIfE (map (guardCon conLen off1) cons ++ [catchAll]))|]+ -- xorCmp reads the constructor name without bounds checks, so+ -- check that the name fits in the buffer before calling it.+ if $(varE off1) + w8_int $(varE conLen) > $(varE _endOffset)+ then error+ ("deserializeAt: constructor name at offset = "+ ++ show $(varE off1)+ ++ " exceeds the buffer length = "+ ++ show $(varE _endOffset))+ else $(multiIfE (map (guardCon conLen off1) cons ++ [catchAll]))|] catchAll = normalGE
src/Streamly/Internal/Data/Serialize/TH/RecHeader.hs view
@@ -395,7 +395,11 @@ deserializeAt $(varE initialOff) $(varE _arr) $(varE endOff) ($(varP hOff), hlen1 :: Word32) <- deserializeAt hlenOff $(varE _arr) $(varE endOff)- if (hlen1 == $(litIntegral hlen)) && $(xorCmp hval hOff _arr)+ -- xorCmp reads the header without bounds checks, so check that the+ -- header fits in the buffer before calling it.+ if (hlen1 == $(litIntegral hlen))+ && $(varE hOff) + $(litIntegral hlen) <= $(varE endOff)+ && $(xorCmp hval hOff _arr) then do let $(varP (makeI 0)) = $(varE initialOff) +
src/Streamly/Internal/Data/Serialize/Type.hs view
@@ -251,6 +251,8 @@ {-# INLINABLE deserializeAt #-} deserializeAt off arr sz = do (off1, len64) <- deserializeAt off arr sz :: IO (Int, Int64)+ when (len64 < 0) $+ error $ "deserializeAt: negative list length " ++ show len64 let len = (fromIntegral :: Int64 -> Int) len64 peekList f o i | i >= 3 = do -- Unfold the loop three times
src/Streamly/Internal/Data/Stream/Eliminate.hs view
@@ -100,12 +100,13 @@ {-# INLINE_NORMAL foldr1 #-} foldr1 :: Monad m => (a -> a -> a) -> Stream m a -> m (Maybe a)-foldr1 f m = do- r <- uncons m- case r of- Nothing -> return Nothing- Just (h, t) -> fmap Just (foldr f h t)+foldr1 f = foldr step Nothing + where++ step x Nothing = Just x+ step x (Just acc) = Just (f x acc)+ ------------------------------------------------------------------------------ -- Parsers ------------------------------------------------------------------------------@@ -583,9 +584,16 @@ => Stream m a -> Stream m a -> m Bool isInfixOf infx stream = do arr <- fold Array.create infx- -- XXX can use breakOnSeq instead (when available)- r <- null $ StreamD.drop 1 $ Nesting.splitSepBySeq_ arr Fold.drain stream- return (not r)+ -- An empty infix occurs in every stream. The split below cannot detect+ -- it in a stream of fewer than two elements, which has fewer than two+ -- segments.+ if Array.length arr == 0+ then return True+ else do+ -- XXX can use breakOnSeq instead (when available)+ r <- null+ $ StreamD.drop 1 $ Nesting.splitSepBySeq_ arr Fold.drain stream+ return (not r) -- Note: isPrefixOf uses the prefix stream only once. In contrast, isSuffixOf -- may use the suffix stream many times. To run in optimal memory we do not
src/Streamly/Internal/Data/Stream/Generate.hs view
@@ -500,7 +500,7 @@ {-# INLINE enumerateFromToIntegral #-} enumerateFromToIntegral :: (Monad m, Integral a) => a -> a -> Stream m a enumerateFromToIntegral from to =- takeWhile (<= to) $ enumerateFromStepIntegral from 1+ takeWhile (<= to) $ takeEndBy (== to) $ enumerateFromStepIntegral from 1 -- | Enumerate an 'Integral' type. @enumerateFromIntegral from@ generates a -- stream whose first element is @from@ and the successive elements are in
src/Streamly/Internal/Data/Stream/Nesting.hs view
@@ -3542,11 +3542,8 @@ let jump c = SplitOnSuffixSeqKRInit c s mba yieldReinit jump b Skip s -> go SPEC offset s fs- Stop -> do- -- do not issue a blank segment when we end at pattern- if offset == maxOffset && A.byteEq arr patArr- then final fs >> return Stop- else if withSep+ Stop ->+ if withSep then do r <- final fs skip $ SplitOnSuffixSeqYield r SplitOnSuffixSeqDone
src/Streamly/Internal/Data/Stream/Transform.hs view
@@ -502,17 +502,24 @@ -- Unfortunately, we cannot define lazy scans because the Partial constructor -- itself is strict. --- | Postscan a stream using the given fold. A postscan omits the initial--- (default) value of the accumulator and includes the final value.+-- | Postscan a stream using the given scan. A postscan emits one output per+-- input element, omitting the initial value of the accumulator. Equivalently,+-- @postscanl s = 'Data.List.drop' 1 . 'scanl' s@. -- -- >>> Stream.toList $ Stream.postscanl Scanl.latest (Stream.fromList []) -- [] ----- Compare with 'scan' which includes the initial value as well:+-- Compare with 'scanl' which additionally emits the initial value: -- -- >>> Stream.toList $ Stream.scanl Scanl.latest (Stream.fromList []) -- [Nothing] --+-- A scan that terminates at the initial step (e.g. @Scanl.take 0@) consumes no+-- input, so the postscan is empty:+--+-- >>> Stream.toList $ Stream.postscanl (Scanl.take 0 Scanl.toList) (Stream.fromList [1,2,3::Int])+-- []+-- -- The following example extracts the input stream up to a point where the -- running average of elements is no more than 10: --@@ -540,7 +547,12 @@ return $ case res of FL.Partial fs -> Skip $ ScanDo st fs- FL.Done b -> Yield b ScanDone+ -- A scan that is Done at the initial step has consumed no+ -- input, so a postscan (one output per consumed input) emits+ -- nothing. This keeps the invariant+ -- @postscanl s = drop 1 . scanl s@: the initial value is+ -- always dropped, whether it comes from a Partial or a Done.+ FL.Done _ -> Stop step gst (ScanDo st fs) = do res <- sstep (adaptState gst) st case res of
src/Streamly/Internal/Data/Stream/Type.hs view
@@ -1015,6 +1015,8 @@ showsPrec p dl = showParen (p > 10) $ showString "fromList " . shows (GHC.Exts.toList dl) + show dl = "fromList " ++ show (GHC.Exts.toList dl)+ instance Read a => Read (Stream Identity a) where readPrec = parens $ prec 10 $ do Ident "fromList" <- lexP@@ -2422,7 +2424,7 @@ {-# ANN type CIterState Fuse #-} data CIterState s f fs b = CIterInit s f- | CIterConsume s fs+ | CIterConsume s fs Bool | CIterYield b (CIterState s f fs b) | CIterStop @@ -2444,20 +2446,27 @@ return $ Skip $ case res of- FL.Partial fs -> CIterConsume st fs+ FL.Partial fs -> CIterConsume st fs False FL.Done fb -> CIterYield fb $ CIterInit st (return fb) {-# INLINE_LATE stepOuter #-} stepOuter _ (CIterInit st action) = do iterStep st (action >>= finject)- stepOuter gst (CIterConsume st fs) = do+ stepOuter gst (CIterConsume st fs consumed) = do r <- step (adaptState gst) st case r of- Yield x s -> iterStep s (fstep fs x)- Skip s -> return $ Skip $ CIterConsume s fs- Stop -> do- b <- fextract fs- return $ Skip $ CIterYield b CIterStop+ Yield x s -> do+ res <- fstep fs x+ return $ Skip $ case res of+ FL.Partial fs1 -> CIterConsume s fs1 True+ FL.Done fb -> CIterYield fb $ CIterInit s (return fb)+ Skip s -> return $ Skip $ CIterConsume s fs consumed+ Stop ->+ if consumed+ then do+ b <- fextract fs+ return $ Skip $ CIterYield b CIterStop+ else return Stop stepOuter _ (CIterYield a next) = return $ Yield a next stepOuter _ CIterStop = return Stop @@ -2598,6 +2607,9 @@ -- Do not use automatic derivation for this to show as "fromList" rather than -- "fromList Identity". instance Show a => Show (Nested Identity a) where+ {-# INLINE showsPrec #-}+ showsPrec p (Nested xs) = showsPrec p xs+ {-# INLINE show #-} show (Nested xs) = show xs
src/Streamly/Internal/Data/StreamK/Type.hs view
@@ -1193,6 +1193,8 @@ showsPrec p dl = showParen (p > 10) $ showString "fromList " . shows (toList dl) + show dl = "fromList " ++ show (toList dl)+ instance Read a => Read (StreamK Identity a) where readPrec = parens $ prec 10 $ do Ident "fromList" <- lexP@@ -2802,6 +2804,9 @@ -- Do not use automatic derivation for this to show as "fromList" rather than -- "fromList Identity". instance Show a => Show (Nested Identity a) where+ {-# INLINE showsPrec #-}+ showsPrec p (Nested xs) = showsPrec p xs+ {-# INLINE show #-} show (Nested xs) = show xs
src/Streamly/Internal/Data/Time/Clock.hs view
@@ -26,12 +26,12 @@ ) where -import Control.Concurrent (threadDelay, ThreadId)+import Control.Concurrent (forkIO, threadDelay, ThreadId) import Control.Concurrent.MVar (MVar, newEmptyMVar, takeMVar, tryPutMVar)-import Control.Monad (forever, when, void)+import Control.Monad (when, void) import Streamly.Internal.Data.Time.Units (MicroSecond64(..), fromAbsTime, addToAbsTime, toRelTime)-import Streamly.Internal.Control.ForkIO (forkIOManaged)+import System.Mem.Weak (Weak, deRefWeak) import qualified Streamly.Internal.Data.IORef as Unboxed @@ -47,12 +47,16 @@ t <- fromAbsTime <$> getTime clock Unboxed.modifyIORef' timeVar (const t) +-- Returns False if the IORef is no longer reachable. {-# INLINE updateWithDelay #-} updateWithDelay :: RealFrac a =>- Clock -> a -> Unboxed.IORef MicroSecond64 -> IO ()-updateWithDelay clock precision timeVar = do+ Clock -> a -> Weak (Unboxed.IORef MicroSecond64) -> IO Bool+updateWithDelay clock precision weakVar = do threadDelay (delayTime precision)- updateTimeVar clock timeVar+ r <- deRefWeak weakVar+ case r of+ Nothing -> return False+ Just timeVar -> updateTimeVar clock timeVar >> return True where @@ -69,8 +73,8 @@ -- | @asyncClock g@ starts a clock thread that updates an IORef with current -- time as a 64-bit value in microseconds, every 'g' seconds. The IORef can be--- read asynchronously. The thread exits automatically when the reference to--- the returned 'ThreadId' is lost.+-- read asynchronously. The thread exits automatically when the IORef is no+-- longer reachable. -- -- Minimum granularity of clock update is 1 ms. Higher is better for -- performance.@@ -82,9 +86,19 @@ asyncClock clock g = do timeVar <- Unboxed.newIORef 0 updateTimeVar clock timeVar- tid <- forkIOManaged $ forever (updateWithDelay clock g timeVar)+ -- The thread holds only a weak reference to timeVar. The consumers may+ -- drop the returned ThreadId, so a finalizer on the ThreadId may kill the+ -- thread at the next GC while timeVar is still being read.+ weakVar <- Unboxed.mkWeakIORef timeVar (return ())+ tid <- forkIO $ loop weakVar return (tid, timeVar) + where++ loop weakVar = do+ continue <- updateWithDelay clock g weakVar+ when continue $ loop weakVar+ {-# INLINE readClock #-} readClock :: (ThreadId, Unboxed.IORef MicroSecond64) -> IO MicroSecond64 readClock (_, timeVar) = Unboxed.readIORef timeVar@@ -94,7 +108,7 @@ ------------------------------------------------------------------------------ -- | Adjustable periodic timer.-data Timer = Timer ThreadId (MVar ()) (IO ())+data Timer = Timer ThreadId (MVar ()) (Unboxed.IORef MicroSecond64) (IO ()) -- Set the expiry to current time + timer period {-# INLINE resetTimerExpiry #-}@@ -104,18 +118,29 @@ let t1 = addToAbsTime t (toRelTime period) Unboxed.modifyIORef' timeVar (const (fromAbsTime t1)) +-- Returns False if the IORef is no longer reachable. {-# INLINE processTimerTick #-} processTimerTick :: RealFrac a =>- Clock -> a -> Unboxed.IORef MicroSecond64 -> MVar () -> IO () -> IO ()-processTimerTick clock precision timeVar mvar reset = do+ Clock+ -> a+ -> MicroSecond64+ -> Weak (Unboxed.IORef MicroSecond64)+ -> MVar ()+ -> IO Bool+processTimerTick clock precision period weakVar mvar = do threadDelay (delayTime precision)- t <- fromAbsTime <$> getTime clock- expiry <- Unboxed.readIORef timeVar- when (t >= expiry) $ do- -- non-blocking put so that we can process multiple timers in a- -- non-blocking manner in future.- void $ tryPutMVar mvar ()- reset+ r <- deRefWeak weakVar+ case r of+ Nothing -> return False+ Just timeVar -> do+ t <- fromAbsTime <$> getTime clock+ expiry <- Unboxed.readIORef timeVar+ when (t >= expiry) $ do+ -- non-blocking put so that we can process multiple timers in+ -- a non-blocking manner in future.+ void $ tryPutMVar mvar ()+ resetTimerExpiry clock period timeVar+ return True where @@ -142,21 +167,33 @@ timeVar <- Unboxed.newIORef 0 let p = round (period * 1e6) :: Int p1 = fromIntegral p :: MicroSecond64- reset = resetTimerExpiry clock p1 timeVar- process = processTimerTick clock g timeVar mvar reset- reset- tid <- forkIOManaged $ forever process- return $ Timer tid mvar reset+ resetTimerExpiry clock p1 timeVar+ -- The thread holds only a weak reference to timeVar, and exits when the+ -- Timer is no longer reachable. It holds mvar strongly, otherwise a thread+ -- blocked in waitTimer would be unreachable and get+ -- BlockedIndefinitelyOnMVar.+ weakVar <- Unboxed.mkWeakIORef timeVar (return ())+ tid <- forkIO $ loop p1 weakVar mvar+ return $ Timer tid mvar timeVar (resetTimerExpiry clock p1 timeVar) + where++ loop p1 weakVar mvar = do+ continue <- processTimerTick clock g p1 weakVar mvar+ when continue $ loop p1 weakVar mvar+ -- | Blocking wait for a timer tick. {-# INLINE waitTimer #-} waitTimer :: Timer -> IO ()-waitTimer (Timer _ mvar _) = takeMVar mvar+waitTimer (Timer _ mvar timeVar _) = do+ takeMVar mvar+ -- Keep timeVar reachable, so that the timer thread keeps running+ Unboxed.touchIORef timeVar -- | Resets the current period. {-# INLINE resetTimer #-} resetTimer :: Timer -> IO ()-resetTimer (Timer _ _ reset) = reset+resetTimer (Timer _ _ _ reset) = reset -- | Elongates the current period by specified amount. --
src/Streamly/Internal/Data/Unfold.hs view
@@ -853,8 +853,8 @@ -- -- /Pre-release/ {-# INLINE_NORMAL before #-}-before :: (a -> m c) -> Unfold m a b -> Unfold m a b-before action (Unfold step inject) = Unfold step (action >> inject)+before :: Monad m => (a -> m c) -> Unfold m a b -> Unfold m a b+before action (Unfold step inject) = Unfold step (\a -> action a >> inject a) -- The custom implementation of "after_" is slightly faster (5-7%) than -- "_after". This is just to document and make sure that we can always use
src/Streamly/Internal/Data/Unfold/Enumeration.hs view
@@ -201,12 +201,27 @@ enumerateFromThenIntegral = lmap (\(from, next) -> (from, next - from)) enumerateFromStepIntegral -{-# INLINE enumerateFromToIntegral #-}+data EnumToState a = EnumToYield !a !a | EnumToStop++-- Stops after yielding "to" instead of computing "to + 1", which overflows+-- when "to" is maxBound.+{-# INLINE_NORMAL enumerateFromToIntegral #-} enumerateFromToIntegral :: (Monad m, Integral a) => Unfold m (a, a) a-enumerateFromToIntegral =- takeWhileMWithInput (\(_, to) b -> return $ b <= to)- $ lmap (\(from, _) -> (from, 1)) enumerateFromStepIntegral+enumerateFromToIntegral = Unfold step inject + where++ inject (from, to) =+ return $ if from > to then EnumToStop else EnumToYield from to++ {-# INLINE_LATE step #-}+ step (EnumToYield x to) =+ return+ $ if x == to+ then Yield x EnumToStop+ else Yield x (EnumToYield (x + 1) to)+ step EnumToStop = return Stop+ {-# INLINE enumerateFromThenToIntegral #-} enumerateFromThenToIntegral :: (Monad m, Integral a) => Unfold m (a, a, a) a enumerateFromThenToIntegral =@@ -533,7 +548,7 @@ map Identity $ lmap (bimap runIdentity runIdentity) enumerateFromThen {-# INLINE enumerateFromTo #-} enumerateFromTo =- map Identity $ lmap (bimap runIdentity runIdentity) enumerateFromThen+ map Identity $ lmap (bimap runIdentity runIdentity) enumerateFromTo {-# INLINE enumerateFromThenTo #-} enumerateFromThenTo = map Identity $
src/Streamly/Internal/FileSystem/Posix/File.hsc view
@@ -80,18 +80,24 @@ -- Imports ------------------------------------------------------------------------------- +import Control.Exception (onException)+import Control.Monad (when) import Data.Bits ((.|.), (.&.), complement) import Foreign.C.Error (throwErrnoIfMinus1_) import Foreign.C.String (CString) import Foreign.C.Types (CInt(..))-import GHC.IO.Handle.FD (fdToHandle)+import GHC.IO.Device (IODeviceType(..))+import GHC.IO.Handle.FD (mkHandleFromFD) import Streamly.Internal.FileSystem.Posix.Errno (throwErrnoPathIfMinus1Retry) import Streamly.Internal.FileSystem.PosixPath (PosixPath) import System.IO (IOMode(..), Handle) import System.Posix.Types (Fd(..), CMode(..)) +import qualified GHC.IO.Device as Device+import qualified GHC.IO.FD as FD import qualified Streamly.Internal.FileSystem.File.Common as File import qualified Streamly.Internal.FileSystem.PosixPath as Path+import qualified System.Posix.Internals as Posix -- We want to remain close to the Posix C API. A function based API to set and -- clear the modes is simple, type safe and directly mirrors the C API. It does@@ -253,33 +259,26 @@ $ openAtCString fdMay cstr flags cmode --- | Open a regular file, return an Fd.+-- | The open flags and the create mode for opening a regular file in the given+-- 'IOMode'. -- -- Sets O_NOCTTY, O_NONBLOCK flags to be compatible with the base openFile -- behavior. O_NOCTTY affects opening of terminal special files and O_NONBLOCK -- affects fifo special files, and mandatory locking. ---openFileFdWith :: OpenFlags -> PosixPath -> IOMode -> IO Fd-openFileFdWith oflags path iomode = do+openFileFlags :: OpenFlags -> IOMode -> (OpenFlags, Maybe FileMode)+openFileFlags oflags iomode = case iomode of- ReadMode -> open1 (setReadOnly oflags1) Nothing- WriteMode ->- open1 (setWriteOnly oflags1) (Just defaultCreateMode)+ ReadMode -> (setReadOnly oflags1, Nothing)+ WriteMode -> (setWriteOnly oflags1, Just defaultCreateMode) AppendMode ->- open1- ((setAppend True . setWriteOnly) oflags1)- (Just defaultCreateMode)- ReadWriteMode ->- open1 (setReadWrite oflags) (Just defaultCreateMode)+ ((setAppend True . setWriteOnly) oflags1, Just defaultCreateMode)+ ReadWriteMode -> (setReadWrite oflags1, Just defaultCreateMode) where oflags1 = setNoCtty True $ setNonBlock True oflags- open1 = openAt Nothing path -openFileFd :: PosixPath -> IOMode -> IO Fd-openFileFd = openFileFdWith defaultOpenFlags- foreign import ccall unsafe "unistd.h close" c_close :: CInt -> IO CInt @@ -290,11 +289,33 @@ -- base openFile compatible, Handle returning, APIs ------------------------------------------------------------------------------- --- | Open a regular file, return a Handle. The file is locked, the Handle is--- NOT set up to close the file on garbage collection.-{-# INLINE openFileHandle #-}+-- | Like openFile in base. open() can be slow, e.g. on NFS or FUSE file+-- systems, or block, so use an interruptible foreign call for it.+interruptibleOpen :: CString -> CInt -> CMode -> IO CInt+##if MIN_VERSION_base(4,16,0)+interruptibleOpen = Posix.c_interruptible_open+##else+interruptibleOpen = Posix.c_safe_open+##endif++-- | Open a file and return a Handle in binary mode. Like openFile in base,+-- the file is locked and a file opened in 'WriteMode' is truncated. openFileHandle :: PosixPath -> IOMode -> IO Handle-openFileHandle p x = openFileFd p x >>= fdToHandle . fromIntegral+openFileHandle path iomode = do+ let (flags, cmode) = openFileFlags defaultOpenFlags iomode+ OpenFlags flags1 = maybe flags (\_ -> setCreat True flags) cmode+ FileMode mode = maybe defaultCreateMode id cmode+ fd <- Path.asCString path $ \cstr ->+ throwErrnoPathIfMinus1Retry "openFile" path+ $ interruptibleOpen cstr flags1 mode+ (fD, fdType) <-+ FD.mkFD fd iomode Nothing False True `onException` c_close fd+ -- Like base, truncate after locking the file, ftruncate fails on special+ -- files like /dev/null.+ when (iomode == WriteMode && fdType == RegularFile)+ $ Device.setSize fD 0 `onException` Device.close fD+ mkHandleFromFD fD fdType (Path.toString path) iomode False Nothing+ `onException` Device.close fD -- | Like openFile in base package but using Path instead of FilePath. -- Use hSetBinaryMode on the handle if you want to use binary mode.
streamly-core.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.4 name: streamly-core-version: 0.3.1+version: 0.3.2 synopsis: Streaming, parsers, arrays, serialization and more description: For upgrading to streamly-0.9.0+ please read the