diff --git a/Changelog.md b/Changelog.md
--- a/Changelog.md
+++ b/Changelog.md
@@ -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.
diff --git a/configure b/configure
--- a/configure
+++ b/configure
@@ -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\\"
 
diff --git a/configure.ac b/configure.ac
--- a/configure.ac
+++ b/configure.ac
@@ -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])
diff --git a/docs/Changelog.md b/docs/Changelog.md
--- a/docs/Changelog.md
+++ b/docs/Changelog.md
@@ -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.
diff --git a/src/Streamly/Internal/Data/Array/Type.hs b/src/Streamly/Internal/Data/Array/Type.hs
--- a/src/Streamly/Internal/Data/Array/Type.hs
+++ b/src/Streamly/Internal/Data/Array/Type.hs
@@ -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
diff --git a/src/Streamly/Internal/Data/Fold/Combinators.hs b/src/Streamly/Internal/Data/Fold/Combinators.hs
--- a/src/Streamly/Internal/Data/Fold/Combinators.hs
+++ b/src/Streamly/Internal/Data/Fold/Combinators.hs
@@ -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
diff --git a/src/Streamly/Internal/Data/Fold/Container.hs b/src/Streamly/Internal/Data/Fold/Container.hs
--- a/src/Streamly/Internal/Data/Fold/Container.hs
+++ b/src/Streamly/Internal/Data/Fold/Container.hs
@@ -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
diff --git a/src/Streamly/Internal/Data/Fold/Exception.hs b/src/Streamly/Internal/Data/Fold/Exception.hs
--- a/src/Streamly/Internal/Data/Fold/Exception.hs
+++ b/src/Streamly/Internal/Data/Fold/Exception.hs
@@ -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)
diff --git a/src/Streamly/Internal/Data/Fold/Type.hs b/src/Streamly/Internal/Data/Fold/Type.hs
--- a/src/Streamly/Internal/Data/Fold/Type.hs
+++ b/src/Streamly/Internal/Data/Fold/Type.hs
@@ -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
diff --git a/src/Streamly/Internal/Data/IORef.hs b/src/Streamly/Internal/Data/IORef.hs
--- a/src/Streamly/Internal/Data/IORef.hs
+++ b/src/Streamly/Internal/Data/IORef.hs
@@ -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'.
 --
diff --git a/src/Streamly/Internal/Data/MutArray/Generic.hs b/src/Streamly/Internal/Data/MutArray/Generic.hs
--- a/src/Streamly/Internal/Data/MutArray/Generic.hs
+++ b/src/Streamly/Internal/Data/MutArray/Generic.hs
@@ -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
diff --git a/src/Streamly/Internal/Data/MutArray/Type.hs b/src/Streamly/Internal/Data/MutArray/Type.hs
--- a/src/Streamly/Internal/Data/MutArray/Type.hs
+++ b/src/Streamly/Internal/Data/MutArray/Type.hs
@@ -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
diff --git a/src/Streamly/Internal/Data/MutByteArray/Type.hs b/src/Streamly/Internal/Data/MutByteArray/Type.hs
--- a/src/Streamly/Internal/Data/MutByteArray/Type.hs
+++ b/src/Streamly/Internal/Data/MutByteArray/Type.hs
@@ -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
diff --git a/src/Streamly/Internal/Data/RingArray.hs b/src/Streamly/Internal/Data/RingArray.hs
--- a/src/Streamly/Internal/Data/RingArray.hs
+++ b/src/Streamly/Internal/Data/RingArray.hs
@@ -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)
diff --git a/src/Streamly/Internal/Data/RingArray/Generic.hs b/src/Streamly/Internal/Data/RingArray/Generic.hs
--- a/src/Streamly/Internal/Data/RingArray/Generic.hs
+++ b/src/Streamly/Internal/Data/RingArray/Generic.hs
@@ -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
diff --git a/src/Streamly/Internal/Data/Scanl/Combinators.hs b/src/Streamly/Internal/Data/Scanl/Combinators.hs
--- a/src/Streamly/Internal/Data/Scanl/Combinators.hs
+++ b/src/Streamly/Internal/Data/Scanl/Combinators.hs
@@ -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
diff --git a/src/Streamly/Internal/Data/Scanl/Container.hs b/src/Streamly/Internal/Data/Scanl/Container.hs
--- a/src/Streamly/Internal/Data/Scanl/Container.hs
+++ b/src/Streamly/Internal/Data/Scanl/Container.hs
@@ -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
diff --git a/src/Streamly/Internal/Data/Serialize/TH.hs b/src/Streamly/Internal/Data/Serialize/TH.hs
--- a/src/Streamly/Internal/Data/Serialize/TH.hs
+++ b/src/Streamly/Internal/Data/Serialize/TH.hs
@@ -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
diff --git a/src/Streamly/Internal/Data/Serialize/TH/RecHeader.hs b/src/Streamly/Internal/Data/Serialize/TH/RecHeader.hs
--- a/src/Streamly/Internal/Data/Serialize/TH/RecHeader.hs
+++ b/src/Streamly/Internal/Data/Serialize/TH/RecHeader.hs
@@ -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) +
diff --git a/src/Streamly/Internal/Data/Serialize/Type.hs b/src/Streamly/Internal/Data/Serialize/Type.hs
--- a/src/Streamly/Internal/Data/Serialize/Type.hs
+++ b/src/Streamly/Internal/Data/Serialize/Type.hs
@@ -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
diff --git a/src/Streamly/Internal/Data/Stream/Eliminate.hs b/src/Streamly/Internal/Data/Stream/Eliminate.hs
--- a/src/Streamly/Internal/Data/Stream/Eliminate.hs
+++ b/src/Streamly/Internal/Data/Stream/Eliminate.hs
@@ -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
diff --git a/src/Streamly/Internal/Data/Stream/Generate.hs b/src/Streamly/Internal/Data/Stream/Generate.hs
--- a/src/Streamly/Internal/Data/Stream/Generate.hs
+++ b/src/Streamly/Internal/Data/Stream/Generate.hs
@@ -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
diff --git a/src/Streamly/Internal/Data/Stream/Nesting.hs b/src/Streamly/Internal/Data/Stream/Nesting.hs
--- a/src/Streamly/Internal/Data/Stream/Nesting.hs
+++ b/src/Streamly/Internal/Data/Stream/Nesting.hs
@@ -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
diff --git a/src/Streamly/Internal/Data/Stream/Transform.hs b/src/Streamly/Internal/Data/Stream/Transform.hs
--- a/src/Streamly/Internal/Data/Stream/Transform.hs
+++ b/src/Streamly/Internal/Data/Stream/Transform.hs
@@ -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
diff --git a/src/Streamly/Internal/Data/Stream/Type.hs b/src/Streamly/Internal/Data/Stream/Type.hs
--- a/src/Streamly/Internal/Data/Stream/Type.hs
+++ b/src/Streamly/Internal/Data/Stream/Type.hs
@@ -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
 
diff --git a/src/Streamly/Internal/Data/StreamK/Type.hs b/src/Streamly/Internal/Data/StreamK/Type.hs
--- a/src/Streamly/Internal/Data/StreamK/Type.hs
+++ b/src/Streamly/Internal/Data/StreamK/Type.hs
@@ -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
 
diff --git a/src/Streamly/Internal/Data/Time/Clock.hs b/src/Streamly/Internal/Data/Time/Clock.hs
--- a/src/Streamly/Internal/Data/Time/Clock.hs
+++ b/src/Streamly/Internal/Data/Time/Clock.hs
@@ -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.
 --
diff --git a/src/Streamly/Internal/Data/Unfold.hs b/src/Streamly/Internal/Data/Unfold.hs
--- a/src/Streamly/Internal/Data/Unfold.hs
+++ b/src/Streamly/Internal/Data/Unfold.hs
@@ -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
diff --git a/src/Streamly/Internal/Data/Unfold/Enumeration.hs b/src/Streamly/Internal/Data/Unfold/Enumeration.hs
--- a/src/Streamly/Internal/Data/Unfold/Enumeration.hs
+++ b/src/Streamly/Internal/Data/Unfold/Enumeration.hs
@@ -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 $
diff --git a/src/Streamly/Internal/FileSystem/Posix/File.hsc b/src/Streamly/Internal/FileSystem/Posix/File.hsc
--- a/src/Streamly/Internal/FileSystem/Posix/File.hsc
+++ b/src/Streamly/Internal/FileSystem/Posix/File.hsc
@@ -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.
diff --git a/streamly-core.cabal b/streamly-core.cabal
--- a/streamly-core.cabal
+++ b/streamly-core.cabal
@@ -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
