pipes-safe 2.2.9 → 2.3.5
raw patch · 6 files changed
Files
- LICENSE +2/−2
- README.md +4/−4
- changelog.md +35/−0
- pipes-safe.cabal +16/−15
- src/Pipes/Safe.hs +56/−48
- src/Pipes/Safe/Prelude.hs +74/−7
LICENSE view
@@ -1,4 +1,4 @@-Copyright (c) 2013, 2014 Gabriel Gonzalez+Copyright (c) 2013, 2014 Gabriella Gonzalez All rights reserved. Redistribution and use in source and binary forms, with or without modification,@@ -8,7 +8,7 @@ * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.- * Neither the name of Gabriel Gonzalez nor the names of other contributors+ * Neither the name of Gabriella Gonzalez nor the names of other contributors may be used to endorse or promote products derived from this software without specific prior written permission.
README.md view
@@ -1,7 +1,7 @@-# Pipes-Safe v2.2.9+# Pipes-Safe `pipes-safe` builds upon-[the `pipes` library](https://github.com/Gabriel439/Haskell-Pipes-Library) to+[the `pipes` library](https://github.com/Gabriella439/Haskell-Pipes-Library) to provide exception safety and resource management. ## Quick start@@ -55,7 +55,7 @@ ## License (BSD 3-clause) -Copyright (c) 2013 Gabriel Gonzalez+Copyright (c) 2013 Gabriella Gonzalez All rights reserved. Redistribution and use in source and binary forms, with or without modification,@@ -68,7 +68,7 @@ list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -* Neither the name of Gabriel Gonzalez nor the names of other contributors may+* Neither the name of Gabriella Gonzalez nor the names of other contributors may be used to endorse or promote products derived from this software without specific prior written permission.
changelog.md view
@@ -1,3 +1,38 @@+# Version++* Export `SafeT` constructor++# Version 2.3.1++* Remove `MonadFail` constraints introduced in version 2.3.0+* Implement `MonadFail` for `SafeT`++# Version 2.3.0++* BREAKING CHANGE: Support GHC 8.6.1+ * This requires adding a `MonadFail` constraints to certain utilities++# Version 2.2.9++* Fix build against older versions of `exceptions`++# Version 2.2.8++* Increase upper bound on `exceptions`++# Version 2.2.7++* Increase upper bound on `exceptions`++# Version 2.2.6++* Add `PrimMonad` instance for `SafeT`++# Version 2.2.5++* Add `tryP` and `catchP`+* `MonadThrow` and `MonadCatch` instances for `Proxy` upstreamed to `pipes`+ # Version 2.2.4 * Increase upper bound on `pipes`
pipes-safe.cabal view
@@ -1,15 +1,15 @@ Name: pipes-safe-Version: 2.2.9-Cabal-Version: >=1.8.0.2+Version: 2.3.5+Cabal-Version: >=1.10 Build-Type: Simple License: BSD3 License-File: LICENSE Extra-Source-Files: README.md changelog.md-Copyright: 2013, 2014 Gabriel Gonzalez-Author: Gabriel Gonzalez-Maintainer: Gabriel439@gmail.com-Tested-With: GHC == 7.4.2, GHC == 7.6.3, GHC == 7.8.4, GHC == 7.10.2, GHC == 8.0.1-Bug-Reports: https://github.com/Gabriel439/Haskell-Pipes-Safe-Library/issues+Copyright: 2013, 2014 Gabriella Gonzalez+Author: Gabriella Gonzalez+Maintainer: GenuineGabriella@gmail.com+Tested-With: GHC == 8.10.7, GHC == 9.0.2, GHC == 9.2.1+Bug-Reports: https://github.com/Gabriella439/Haskell-Pipes-Safe-Library/issues Synopsis: Safety for the pipes ecosystem Description: This package adds resource management and exception handling to the @pipes@@@ -32,21 +32,22 @@ Category: Control, Pipes, Error Handling Source-Repository head Type: git- Location: https://github.com/Gabriel439/Haskell-Pipes-Safe-Library+ Location: https://github.com/Gabriella439/Haskell-Pipes-Safe-Library Library Build-Depends:- base >= 4 && < 5 ,- containers >= 0.3.0.0 && < 0.6 ,- exceptions >= 0.6 && < 0.11,- mtl >= 2.1 && < 2.3 ,- transformers >= 0.2.0.0 && < 0.6 ,+ base >= 4.14 && < 4.19,+ containers >= 0.6.2.1 && < 0.7 ,+ exceptions >= 0.10.4 && < 0.11,+ mtl >= 2.2.2 && < 2.4 ,+ transformers >= 0.5.6.2 && < 0.7 , transformers-base >= 0.4.4 && < 0.5 , monad-control >= 1.0.0.4 && < 1.1 ,- primitive >= 0.6.2.0 && < 0.7 ,- pipes >= 4.3.0 && < 4.4+ primitive >= 0.7.0.0 && < 0.9 ,+ pipes >= 4.3.10 && < 4.4 Exposed-Modules: Pipes.Safe, Pipes.Safe.Prelude HS-Source-Dirs: src GHC-Options: -O2 -Wall+ Default-Language: Haskell2010
src/Pipes/Safe.hs view
@@ -1,15 +1,14 @@ {-# LANGUAGE RankNTypes, TypeFamilies, FlexibleContexts, FlexibleInstances, MultiParamTypeClasses, UndecidableInstances, ScopedTypeVariables,- GeneralizedNewtypeDeriving, CPP, Trustworthy #-}+ GeneralizedNewtypeDeriving, Trustworthy #-} {-# OPTIONS_GHC -fno-warn-orphans #-} -{-| This module provides an orphan 'MonadCatch' instance for 'Proxy' of the+{-| This module provides an orphan 'MonadMask' instance for 'Proxy' of the form: -> instance (MonadCatch m, MonadIO m) => MonadCatch (Proxy a' a b' b m) where+> instance (MonadMask m, MonadIO m) => MonadMask (Proxy a' a b' b m) where - ... so you can throw and catch exceptions within pipes using all- 'MonadCatch' operations.+ Which is needed to implement the instance for MonadSafe for Proxy. This module also provides generalized versions of some 'MonadCatch' operations so that you can also protect against premature termination of@@ -63,7 +62,7 @@ module Pipes.Safe ( -- * SafeT- SafeT+ SafeT(SafeT) , runSafeT , runSafeP @@ -81,22 +80,23 @@ , bracket_ , bracketOnError + -- * Internals+ , Env+ -- * Re-exports -- $reexports , module Control.Monad.Catch , module Control.Exception ) where -import Control.Applicative (Applicative, Alternative)+import Control.Applicative (Alternative) import Control.Exception(Exception(..), SomeException(..)) import qualified Control.Monad.Catch as C import Control.Monad.Catch ( MonadCatch(..) , MonadThrow(..) , MonadMask(..)-#if MIN_VERSION_exceptions(0,10,0) , ExitCase(..)-#endif , mask_ , uninterruptibleMask_ , catchAll@@ -134,13 +134,9 @@ import qualified Control.Monad.Trans.Writer.Lazy as W import qualified Control.Monad.Trans.Writer.Strict as W' import qualified Control.Monad.Writer.Class as WC-#if MIN_VERSION_base(4,6,0) import Data.IORef (IORef, newIORef, readIORef, writeIORef, atomicModifyIORef')-#else-import Data.IORef (IORef, newIORef, readIORef, writeIORef, atomicModifyIORef)-#endif+import Data.Kind (Type) import qualified Data.Map as M-import Data.Monoid (Monoid) import Pipes (Proxy, Effect, Effect', runEffect) import Pipes.Internal (Proxy(..)) @@ -171,7 +167,9 @@ unmask (Respond b fb') = Respond b (unmask . fb') unmask (M m) = M $ do -- retrieve base's unmask and apply to merged action- Masked unmaskVariant <- liftIO $ readIORef ioref+ unmaskVariant <- liftIO $ do+ Masked unmaskVariant <- readIORef ioref+ return unmaskVariant unmaskVariant (m >>= chunk >>= return . unmask) unmask (Pure q) = Pure q @@ -187,7 +185,6 @@ uninterruptibleMask = liftMask uninterruptibleMask -#if MIN_VERSION_exceptions(0,10,0) generalBracket acquire release_ use = mask $ \unmasked -> do a <- acquire let action = do@@ -202,41 +199,64 @@ -- | This is to avoid an unnecessary partial pattern match in `generalBracket` data ExitCase_ a = ExitCaseSuccess_ a | ExitCaseException_ SomeException-#endif data Finalizers m = Finalizers { _nextKey :: !Integer , _finalizers :: !(M.Map Integer (m ())) } +-- | Internal 'SafeT' read-write environment. Exported only so that it can be+-- passed around unmodified by users of the v'SafeT' constructor.+--+-- Warning: Using the 'Env' outside the corresponding 'SafeT' scope will+-- result in undefined behavior.+newtype Env m = Env (IORef (Maybe (Finalizers m)))+ {-| 'SafeT' is a monad transformer that extends the base monad with the ability to 'register' and 'release' finalizers. All unreleased finalizers are called at the end of the 'SafeT' block, even in the event of exceptions. -}-newtype SafeT m r = SafeT { unSafeT :: R.ReaderT (IORef (Maybe (Finalizers m))) m r }- deriving (Functor, Applicative, Alternative, Monad, MonadPlus, MonadFix,- EC.MonadError e, SC.MonadState s, WC.MonadWriter w, CC.MonadCont,- MonadThrow, MonadCatch, MonadMask, MonadIO, B.MonadBase b)+newtype SafeT m r+ = -- | Constructor exported in case it's necessary for integrating 'SafeT'+ -- with other libraries. For example, implementing @mtl@-like+ -- Monad/Something/ instances will often require access to the 'SafeT'+ -- constructor.+ --+ -- Warning: Using the 'Env' outside the corresponding 'SafeT' scope will+ -- result in undefined behavior.+ SafeT (R.ReaderT (Env m) m r)+ deriving+ ( Functor+ , Applicative+ , Alternative+ , Monad+-- The derived instance for `MonadFail` requires a `MonadFail` instance for+-- `ReaderT` which is first available in `transformers-0.5.0.0`+ , MonadFail+ , MonadPlus+ , MonadFix+ , EC.MonadError e+ , SC.MonadState s+ , WC.MonadWriter w+ , CC.MonadCont+ , MonadThrow+ , MonadCatch+ , MonadMask+ , MonadIO+ , B.MonadBase b+ ) instance MonadTrans SafeT where lift m = SafeT (lift m) instance MonadBaseControl b m => MonadBaseControl b (SafeT m) where-#if MIN_VERSION_monad_control(1,0,0) type StM (SafeT m) a = StM m a liftBaseWith f = SafeT $ R.ReaderT $ \reader' -> liftBaseWith $ \runInBase -> f $ runInBase . (\(SafeT r) -> R.runReaderT r reader' ) restoreM = SafeT . R.ReaderT . const . restoreM-#else- newtype StM (SafeT m) a = StMT (StM m a)- liftBaseWith f = SafeT $ R.ReaderT $ \reader' ->- liftBaseWith $ \runInBase ->- f $ liftM StMT . runInBase . \(SafeT r) -> R.runReaderT r reader'- restoreM (StMT base) = SafeT $ R.ReaderT $ const $ restoreM base-#endif instance Prim.PrimMonad m => Prim.PrimMonad (SafeT m) where type PrimState (SafeT m) = Prim.PrimState m@@ -247,19 +267,15 @@ the end of the computation -} runSafeT :: (MonadMask m, MonadIO m) => SafeT m r -> m r-runSafeT m = C.bracket+runSafeT (SafeT m) = C.bracket (liftIO $ newIORef $! Just $! Finalizers 0 M.empty) (\ioref -> do-#if MIN_VERSION_base(4,6,0) mres <- liftIO $ atomicModifyIORef' ioref $ \val ->-#else- mres <- liftIO $ atomicModifyIORef ioref $ \val ->-#endif (Nothing, val) case mres of Nothing -> error "runSafeT's resources were freed by another" Just (Finalizers _ fs) -> mapM snd (M.toDescList fs) )- (R.runReaderT (unSafeT m))+ (R.runReaderT m . Env) {-# INLINABLE runSafeT #-} {-| Run 'SafeT' in the base monad, executing all unreleased finalizers at the@@ -269,7 +285,7 @@ finalization without exiting the 'Proxy' monad. -} runSafeP :: (MonadMask m, MonadIO m) => Effect (SafeT m) r -> Effect' m r-runSafeP = lift . runSafeT . runEffect+runSafeP e = lift . runSafeT . runEffect $ e {-# INLINABLE runSafeP #-} -- | Token used to 'release' a previously 'register'ed finalizer@@ -282,7 +298,7 @@ {-| The monad used to run resource management actions, corresponding to the monad directly beneath 'SafeT' -}- type Base (m :: * -> *) :: * -> *+ type Base (m :: Type -> Type) :: Type -> Type -- | Lift an action from the 'Base' monad liftBase :: Base m r -> m r@@ -306,13 +322,9 @@ liftBase = lift register io = do- ioref <- SafeT R.ask+ Env ioref <- SafeT R.ask liftIO $ do-#if MIN_VERSION_base(4,6,0) n <- atomicModifyIORef' ioref $ \val ->-#else- n <- atomicModifyIORef ioref $ \val ->-#endif case val of Nothing -> error "register: SafeT block is closed" Just (Finalizers n fs) ->@@ -320,18 +332,14 @@ return (ReleaseKey n) release key = do- ioref <- SafeT R.ask-#if MIN_VERSION_base(4,6,0)+ Env ioref <- SafeT R.ask liftIO $ atomicModifyIORef' ioref $ \val ->-#else- liftIO $ atomicModifyIORef ioref $ \val ->-#endif case val of Nothing -> error "release: SafeT block is closed" Just (Finalizers n fs) -> (Just $! Finalizers n (M.delete (unlock key) fs), ()) -instance (MonadSafe m) => MonadSafe (Proxy a' a b' b m) where+instance MonadSafe m => MonadSafe (Proxy a' a b' b m) where type Base (Proxy a' a b' b m) = Base m liftBase = lift . liftBase register = lift . register
src/Pipes/Safe/Prelude.hs view
@@ -5,25 +5,64 @@ module Pipes.Safe.Prelude ( -- * Handle management withFile,+ withBinaryFile,+ openFile,+ openBinaryFile, -- * String I/O -- $strings readFile,- writeFile+ writeFile,++ -- * Registering/releasing+ allocate,+ allocate_ ) where +import Control.Monad.Catch (mask_) import Control.Monad.IO.Class (MonadIO(liftIO)) import Pipes (Producer', Consumer')-import Pipes.Safe (bracket, MonadSafe)+import Pipes.Safe (bracket, liftBase, register, Base, MonadSafe, ReleaseKey) import qualified Pipes.Prelude as P import qualified System.IO as IO import Prelude hiding (readFile, writeFile) --- | Acquire a 'IO.Handle' within 'MonadSafe'-withFile :: (MonadSafe m) => FilePath -> IO.IOMode -> (IO.Handle -> m r) -> m r+{- | Acquire a 'IO.Handle' within 'MonadSafe'++ The file is opened in text mode. See also: 'withBinaryFile'+-}+withFile :: MonadSafe m => FilePath -> IO.IOMode -> (IO.Handle -> m r) -> m r withFile file ioMode = bracket (liftIO $ IO.openFile file ioMode) (liftIO . IO.hClose) {-# INLINABLE withFile #-} +{- | Like 'withFile', but open the file in binary mode++ See 'System.IO.hSetBinaryMode' for the differences between binary and text mode.+-}+withBinaryFile :: MonadSafe m => FilePath -> IO.IOMode -> (IO.Handle -> m r) -> m r+withBinaryFile file ioMode = bracket (liftIO $ IO.openBinaryFile file ioMode) (liftIO . IO.hClose)+{-# INLINABLE withBinaryFile #-}++{- | Acquire a 'IO.Handle' within 'MonadSafe'++ The 'ReleaseKey' can be used to close the handle with 'Pipes.Safe.release';+ otherwise the handle will be closed automatically at the conclusion of the+ 'MonadSafe' block.++ The file is opened in text mode. See also: 'openBinaryFile'+-}+openFile :: MonadSafe m => FilePath -> IO.IOMode -> m (ReleaseKey, IO.Handle)+openFile file ioMode = allocate (liftIO $ IO.openFile file ioMode) (liftIO . IO.hClose)+{-# INLINABLE openFile #-}++{- | Like 'openFile', but open the file in binary mode++ See 'System.IO.hSetBinaryMode' for the differences between binary and text mode.+-}+openBinaryFile :: MonadSafe m => FilePath -> IO.IOMode -> m (ReleaseKey, IO.Handle)+openBinaryFile file ioMode = allocate (liftIO $ IO.openBinaryFile file ioMode) (liftIO . IO.hClose)+{-# INLINABLE openBinaryFile #-}+ {- $strings Note that 'String's are very inefficient, and I will release future separate packages with 'Data.ByteString.ByteString' and 'Data.Text.Text' operations.@@ -34,13 +73,41 @@ {-| Read lines from a file, automatically opening and closing the file as necessary -}-readFile :: (MonadSafe m) => FilePath -> Producer' String m ()+readFile :: MonadSafe m => FilePath -> Producer' String m () readFile file = withFile file IO.ReadMode P.fromHandle {-# INLINABLE readFile #-} {-| Write lines to a file, automatically opening and closing the file as necessary -}-writeFile :: (MonadSafe m) => FilePath -> Consumer' String m r-writeFile file = withFile file IO.WriteMode P.toHandle+writeFile :: MonadSafe m => FilePath -> Consumer' String m r+writeFile file = withFile file IO.WriteMode $ \h -> P.toHandle h {-# INLINABLE writeFile #-}++{- | Acquire some resource with a guarantee that it will eventually be released++ The 'ReleaseKey' can be passed to 'Pipes.Safe.release' to+ release the resource manually. If this has not been done by the end+ of the 'MonadSafe' block, the resource will be released automatically.+-}+allocate :: MonadSafe m =>+ Base m a -- ^ Acquire+ -> (a -> Base m ()) -- ^ Release+ -> m (ReleaseKey, a)+allocate acq rel = mask_ $ do+ a <- liftBase acq+ key <- register (rel a)+ return (key, a)++{- | Like 'allocate', but for when the resource itself is not needed++ The acquire action runs immediately. The 'ReleaseKey' can be passed+ to 'Pipes.Safe.release' to run the release action. If this has not been+ done by the end of the 'MonadSafe' block, the release action will be+ run automatically.+-}+allocate_ :: MonadSafe m =>+ Base m a -- ^ Acquire+ -> (Base m ()) -- ^ Release+ -> m ReleaseKey+allocate_ acq rel = fmap fst (allocate acq (const rel))