diff --git a/.gitmodules b/.gitmodules index 91468aacc..c5f3f1e17 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "scripts"] path = scripts url = git://github.com/yesodweb/scripts.git +[submodule "pool"] + path = pool + url = https://github.com/bos/pool.git diff --git a/package-list.sh b/package-list.sh index a2bbdd4d7..468e5a5d1 100644 --- a/package-list.sh +++ b/package-list.sh @@ -1,6 +1,7 @@ #!/bin/bash pkgs=( ./pool + ./pool-conduit ./persistent ./persistent-template ./persistent-sqlite diff --git a/persistent-test/persistent-test.cabal b/persistent-test/persistent-test.cabal index 157db15b7..51c16ee0d 100644 --- a/persistent-test/persistent-test.cabal +++ b/persistent-test/persistent-test.cabal @@ -63,7 +63,7 @@ library , random == 1.* , QuickCheck == 2.4.* , blaze-html - , pool + , pool-conduit , transformers-base -- postgresql dependencies diff --git a/persistent/Database/Persist/GenericSql.hs b/persistent/Database/Persist/GenericSql.hs index d47cbe43f..eb0e21b26 100644 --- a/persistent/Database/Persist/GenericSql.hs +++ b/persistent/Database/Persist/GenericSql.hs @@ -46,7 +46,7 @@ import Control.Arrow ((&&&)) import Database.Persist.Store import Control.Monad.IO.Class import Control.Monad.Trans.Reader -import Data.Pool +import Data.Conduit.Pool import Database.Persist.GenericSql.Internal import Database.Persist.GenericSql.Migration import qualified Database.Persist.GenericSql.Raw as R @@ -84,8 +84,8 @@ instance PathPiece (Key SqlPersist entity) where execute' :: MonadIO m => Text -> [PersistValue] -> SqlPersist m () execute' = R.execute -runSqlPool :: (MBCIO m, MonadIO m) => SqlPersist m a -> Pool Connection -> m a -runSqlPool r pconn = withPool' pconn $ runSqlConn r +runSqlPool :: C.ResourceIO m => SqlPersist m a -> Pool Connection -> m a +runSqlPool r pconn = withResource pconn $ runSqlConn r runSqlConn :: (MBCIO m, MonadIO m) => SqlPersist m a -> Connection -> m a runSqlConn (SqlPersist r) conn = do diff --git a/persistent/Database/Persist/GenericSql/Internal.hs b/persistent/Database/Persist/GenericSql/Internal.hs index 124739c1d..cd49c106c 100644 --- a/persistent/Database/Persist/GenericSql/Internal.hs +++ b/persistent/Database/Persist/GenericSql/Internal.hs @@ -20,7 +20,7 @@ import Prelude hiding ((++)) import qualified Data.Map as Map import Data.IORef import Control.Monad.IO.Class -import Data.Pool +import Data.Conduit.Pool import Database.Persist.Store import Database.Persist.Query import Control.Exception.Lifted (bracket) @@ -58,8 +58,13 @@ data Statement = Statement } withSqlPool :: C.ResourceIO m - => IO Connection -> Int -> (Pool Connection -> m a) -> m a -withSqlPool mkConn = createPool mkConn close' + => IO Connection -- ^ create a new connection + -> Int -- ^ connection count + -> (Pool Connection -> m a) + -> m a +withSqlPool mkConn connCount f = do + pool <- liftIO $ createPool mkConn close' 1 20 connCount + f pool withSqlConn :: C.ResourceIO m => IO Connection -> (Connection -> m a) -> m a diff --git a/persistent/Database/Persist/Store.hs b/persistent/Database/Persist/Store.hs index 276326b89..fa007cb23 100644 --- a/persistent/Database/Persist/Store.hs +++ b/persistent/Database/Persist/Store.hs @@ -72,16 +72,8 @@ import qualified Data.Text.Encoding as T import qualified Data.Text.Encoding.Error as T import Web.PathPieces (PathPiece (..)) import qualified Data.Text.Read -import Control.Monad.IO.Class (MonadIO) import qualified Data.Conduit as C -#if MIN_VERSION_monad_control(0, 3, 0) -import Control.Monad.Trans.Control (MonadBaseControl) -#define MBCIO MonadBaseControl IO -#else -import Control.Monad.IO.Control (MonadControlIO) -#define MBCIO MonadControlIO -#endif import Data.Aeson (Value) import Data.Aeson.Types (Parser) @@ -557,7 +549,7 @@ class PersistConfig c where loadConfig :: Value -> Parser c -- | I really don't want Applicative here, but it's necessary for Mongo. withPool :: (Applicative m, C.ResourceIO m) => c -> (PersistConfigPool c -> m a) -> m a - runPool :: (MBCIO m, MonadIO m) => c -> PersistConfigBackend c m a + runPool :: C.ResourceIO m => c -> PersistConfigBackend c m a -> PersistConfigPool c -> m a diff --git a/persistent/persistent.cabal b/persistent/persistent.cabal index 8b32e9627..3ecae4c09 100644 --- a/persistent/persistent.cabal +++ b/persistent/persistent.cabal @@ -29,7 +29,7 @@ library , conduit >= 0.0 && < 0.1 , monad-control >= 0.3 && < 0.4 , lifted-base >= 0.1 && < 0.2 - , pool >= 0.1 && < 0.2 + , pool-conduit >= 0.0 && < 0.1 , blaze-html >= 0.4 && < 0.5 , path-pieces >= 0.1 && < 0.2 , mtl >= 2.0 && < 2.1 diff --git a/pool b/pool new file mode 160000 index 000000000..ebf08e333 --- /dev/null +++ b/pool @@ -0,0 +1 @@ +Subproject commit ebf08e3338df1b0291bc597d5375a8850491b920 diff --git a/pool-conduit/Data/Conduit/Pool.hs b/pool-conduit/Data/Conduit/Pool.hs new file mode 100644 index 000000000..1f0b16bd4 --- /dev/null +++ b/pool-conduit/Data/Conduit/Pool.hs @@ -0,0 +1,59 @@ +-- | Allocate resources from a pool, guaranteeing resource handling via the +-- ResourceT transformer. +module Data.Conduit.Pool + ( ManagedResource (..) + , takeResource + , takeResourceCheck + , P.Pool + , P.createPool + , P.withResource + ) where + +import qualified Data.Pool as P +import Control.Monad.Trans.Resource +import Control.Monad.IO.Class (liftIO) +import qualified Data.IORef as I + +-- | The result of taking a resource. +data ManagedResource m a = ManagedResource + { mrValue :: a -- ^ The actual resource. + , mrReuse :: Bool -> ResourceT m () + -- ^ Let's you specify whether the resource should be returned to the pool + -- (via 'P.putResource') or destroyed (via 'P.destroyResource') on release. + -- This defaults to destruction, in case of exceptions. + , mrRelease :: ResourceT m () + -- ^ Release this resource, either destroying it or returning it to the + -- pool. + } + +-- | Take a resource from the pool and register a release action. +takeResource :: ResourceIO m => P.Pool a -> ResourceT m (ManagedResource m a) +takeResource pool = do + onRelRef <- liftIO $ I.newIORef False + (relKey, (a, _)) <- withIO + (P.takeResource pool) + (\(a, local) -> do + onRel <- I.readIORef onRelRef + if onRel + then P.putResource local a + else P.destroyResource pool local a) + return ManagedResource + { mrValue = a + , mrReuse = liftIO . I.writeIORef onRelRef + , mrRelease = release relKey + } + +-- | Same as 'takeResource', but apply some action to check if a resource is +-- still valid. +takeResourceCheck :: ResourceIO m + => P.Pool a + -> (a -> ResourceT m Bool) + -> ResourceT m (ManagedResource m a) +takeResourceCheck pool check = do + mr <- takeResource pool + isValid <- check $ mrValue mr + if isValid + then return mr + else do + mrRelease mr + takeResourceCheck pool check diff --git a/pool/LICENSE b/pool-conduit/LICENSE similarity index 100% rename from pool/LICENSE rename to pool-conduit/LICENSE diff --git a/pool/Setup.lhs b/pool-conduit/Setup.lhs similarity index 100% rename from pool/Setup.lhs rename to pool-conduit/Setup.lhs diff --git a/pool-conduit/pool-conduit.cabal b/pool-conduit/pool-conduit.cabal new file mode 100644 index 000000000..31ac18e3c --- /dev/null +++ b/pool-conduit/pool-conduit.cabal @@ -0,0 +1,28 @@ +name: pool-conduit +version: 0.0.0 +license: BSD3 +license-file: LICENSE +author: Michael Snoyman +maintainer: Michael Snoyman +synopsis: Resource pool allocations via ResourceT. +description: Allocate resources from a pool, guaranteeing resource handling via the ResourceT transformer. +category: Database, Yesod, Conduit +stability: Stable +cabal-version: >= 1.8 +build-type: Simple +homepage: http://www.yesodweb.com/book/persistent + +library + + build-depends: base >= 4 && < 5 + , resource-pool >= 0.2.0.4 && < 0.3 + , transformers >= 0.2.1 && < 0.3 + , conduit >= 0.0.2 && < 0.1 + + exposed-modules: Data.Conduit.Pool + + ghc-options: -Wall + +source-repository head + type: git + location: git://github.com/yesodweb/persistent.git diff --git a/pool/Data/Pool.hs b/pool/Data/Pool.hs deleted file mode 100644 index 65d8525a2..000000000 --- a/pool/Data/Pool.hs +++ /dev/null @@ -1,177 +0,0 @@ -{-# LANGUAGE DeriveDataTypeable #-} -{-# LANGUAGE PackageImports #-} -{-# LANGUAGE CPP #-} -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE FlexibleContexts #-} -module Data.Pool - ( -- * Creation - Pool - , createPool - , createPoolCheckAlive - -- * Usage - , withPool - , withPool' - , withPoolAllocate - -- * Diagnostics - , PoolStats (..) - , poolStats - ) where - -import Data.IORef (IORef, newIORef, atomicModifyIORef, readIORef) -import Control.Exception (throwIO, Exception, bracket, finally) -import qualified Control.Exception as E -import Data.Typeable -#if MIN_VERSION_monad_control(0, 3, 0) -import qualified Control.Monad.Trans.Control as I -#else -import qualified Control.Monad.IO.Control as I -import qualified Control.Exception.Control as I -#endif -import Control.Monad.IO.Class -import Control.Monad - -data PoolData a = PoolData - { poolAvail :: ![a] - , poolCreated :: !Int - } - -data Pool a = Pool - { poolMax :: Int - , poolData :: IORef (PoolData a) - , poolMake :: IO a - , poolFree :: a -> IO () - , poolCheckAlive :: a -> IO Bool - } - -data PoolStats = PoolStats - { poolStatsMax :: Int - , poolStatsAvailable :: Int - , poolStatsCreated :: Int - } - -poolStats :: Pool a -> IO PoolStats -poolStats p = do - d <- readIORef $ poolData p - return $ PoolStats (poolMax p) (length $ poolAvail d) (poolCreated d) - -#if MIN_VERSION_monad_control(0, 3, 0) -#define MBCIO I.MonadBaseControl IO -#define LOO I.liftBaseOp -#define CIO I.control -#define TRY try' - -sequenceEither :: I.MonadBaseControl IO m => Either e (I.StM m a) -> m (Either e a) -sequenceEither = either (return . Left) (liftM Right . I.restoreM) -{-# INLINE sequenceEither #-} - --- |Generalized version of 'E.try'. -try' :: (I.MonadBaseControl IO m, Exception e) => m a -> m (Either e a) -try' m = I.liftBaseWith (\runInIO -> E.try (runInIO m)) >>= sequenceEither -#else -#define MBCIO I.MonadControlIO -#define LOO I.liftIOOp -#define CIO I.controlIO -#define TRY I.try -#endif - --- | Create a new pool without any resource alive checking. -createPool :: (MBCIO m, MonadIO m) - => IO a -- ^ new resource creator - -> (a -> IO ()) -- ^ resource deallocator - -> Int -- ^ maximum number of resources to allow in pool - -> (Pool a -> m b) -- ^ inner function to run with the pool - -> m b -createPool mk fr mx f = createPoolCheckAlive mk fr mx f $ const $ return True - --- | Create a new pool, including a function to check if a resource is still --- alive. Stale resources will automatically be removed from the pool. -createPoolCheckAlive - :: (MBCIO m, MonadIO m) - => IO a -- ^ new resource creator - -> (a -> IO ()) -- ^ resource deallocator - -> Int -- ^ maximum number of resource to allow in pool - -> (Pool a -> m b) -- ^ inner function to run with the pool - -> (a -> IO Bool) -- ^ is the resource alive? - -> m b -createPoolCheckAlive mk fr mx f ca = do - pd <- liftIO $ newIORef $ PoolData [] 0 - finallyIO (f $ Pool mx pd mk fr ca) $ do - PoolData ress _ <- readIORef pd - mapM_ fr ress - -finallyIO :: MBCIO m => m a -> IO b -> m a -finallyIO a io = CIO $ \runInIO -> finally (runInIO a) io - -data PoolExhaustedException = PoolExhaustedException - deriving (Show, Typeable) -instance Exception PoolExhaustedException - --- | This function throws a 'PoolExhaustedException' when no resources are --- available. See 'withPoolAllocate' to avoid this. -#if MIN_VERSION_monad_control(0, 3, 0) -withPool' :: (I.MonadBaseControl IO m, MonadIO m) -#else -withPool' :: I.MonadControlIO m -#endif - => Pool a -> (a -> m b) -> m b -withPool' p f = do - x <- withPool p f - case x of - Nothing -> liftIO $ throwIO PoolExhaustedException - Just x' -> return x' - --- | Same as @withPool'@, but instead of throwing a 'PoolExhaustedException' --- when there the maximum number of resources are created and allocated, it --- allocates a new resource, passes it to the subprocess and then frees it. -withPoolAllocate :: (MonadIO m, MBCIO m) => Pool a -> (a -> m b) -> m b -withPoolAllocate p f = do - x <- withPool p f - case x of - Just x' -> return x' - Nothing -> LOO (bracket (poolMake p) (poolFree p)) f - -mask :: MBCIO m => ((forall a. m a -> m a) -> m b) -> m b -#if MIN_VERSION_base(4,3,0) -#if MIN_VERSION_monad_control(0, 3, 0) -mask = I.liftBaseOp E.mask . liftRestore - -liftRestore :: I.MonadBaseControl IO m - => ((forall a. m a -> m a) -> b) - -> ((forall a. IO a -> IO a) -> b) -liftRestore f r = f $ I.liftBaseOp_ r -#else -mask = I.mask -#endif -#else -mask f = I.block $ f I.unblock -#endif - --- | Attempt to run the given action with a resource from the given 'Pool'. --- Returns 'Nothing' if no resource was available. -withPool :: (MonadIO m, MBCIO m) => Pool a -> (a -> m b) -> m (Maybe b) -withPool p f = mask $ \unmask -> do - eres <- liftIO $ atomicModifyIORef (poolData p) $ \pd -> - case poolAvail pd of - x:xs -> (pd { poolAvail = xs }, Right x) - [] -> (pd, Left $ poolCreated pd) - case eres of - Left pc -> - if pc >= poolMax p - then return Nothing - else LOO (bracket (poolMake p) (insertResource 1)) - (liftM Just . unmask . f) - Right res -> do - isAlive <- TRY $ unmask $ liftIO $ poolCheckAlive p res - case isAlive :: Either E.SomeException Bool of - Right True -> finallyIO (liftM Just $ unmask $ f res) - (insertResource 0 res) - _ -> do - -- decrement the poolCreated count and then start over - liftIO $ atomicModifyIORef (poolData p) $ \pd -> - (pd { poolCreated = poolCreated pd - 1}, ()) - unmask $ withPool p f - where - insertResource i x = atomicModifyIORef (poolData p) $ \pd -> - (pd { poolAvail = x : poolAvail pd - , poolCreated = i + poolCreated pd - }, ()) diff --git a/pool/README b/pool/README deleted file mode 100644 index e69de29bb..000000000 diff --git a/pool/leak.hs b/pool/leak.hs deleted file mode 100644 index d6a792b89..000000000 --- a/pool/leak.hs +++ /dev/null @@ -1,7 +0,0 @@ -import Data.Pool -import System.Environment - -main = do - [x] <- getArgs - createPool (return ()) (const $ return ()) 1000 $ \p -> do - sequence_ $ replicate (read x) $ withPool' p return diff --git a/pool/pool.cabal b/pool/pool.cabal deleted file mode 100644 index ba00b3063..000000000 --- a/pool/pool.cabal +++ /dev/null @@ -1,24 +0,0 @@ -name: pool -version: 0.1.2 -license: BSD3 -license-file: LICENSE -author: Michael Snoyman -maintainer: Michael Snoyman -synopsis: Thread-safe resource pools. -description: Useful for stuff like database connection pools. -category: Data, Database, Yesod -stability: Stable -cabal-version: >= 1.6 -build-type: Simple -homepage: http://www.yesodweb.com/book/persistent - -library - build-depends: base >= 4 && < 5 - , transformers >= 0.2.1 && < 0.3 - , monad-control >= 0.2 && < 0.4 - exposed-modules: Data.Pool - ghc-options: -Wall - -source-repository head - type: git - location: git://github.com/yesodweb/persistent.git