hasql-pool 0.5.2.1 → 0.5.2.2
raw patch · 4 files changed
+66/−271 lines, 4 filesdep +resource-pooldep −basedep −stmdep −transformersdep ~base-preludePVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies added: resource-pool
Dependencies removed: base, stm, transformers
Dependency ranges changed: base-prelude
API changes (from Hackage documentation)
- Hasql.Pool: ConnectionUsageError :: ConnectionError -> UsageError
- Hasql.Pool: PoolIsReleasedUsageError :: UsageError
- Hasql.Pool: SessionUsageError :: QueryError -> UsageError
+ Hasql.Pool: ConnectionError :: ConnectionError -> UsageError
+ Hasql.Pool: SessionError :: QueryError -> UsageError
+ Hasql.Pool: instance GHC.Show.Show Hasql.Pool.Pool
- Hasql.Pool: type Settings = (Int, Int, Settings)
+ Hasql.Pool: type Settings = (Int, NominalDiffTime, Settings)
Files
- hasql-pool.cabal +13/−10
- library/Hasql/Pool.hs +29/−161
- library/Hasql/Pool/Prelude.hs +3/−100
- library/Hasql/Pool/ResourcePool.hs +21/−0
hasql-pool.cabal view
@@ -1,7 +1,7 @@ name: hasql-pool version:- 0.5.2.1+ 0.5.2.2 category: Hasql, Database, PostgreSQL synopsis:@@ -41,16 +41,20 @@ Arrows, BangPatterns, ConstraintKinds, DataKinds, DefaultSignatures, DeriveDataTypeable, DeriveFoldable, DeriveFunctor, DeriveGeneric, DeriveTraversable, EmptyDataDecls, FlexibleContexts, FlexibleInstances, FunctionalDependencies, GADTs, GeneralizedNewtypeDeriving, LambdaCase, LiberalTypeSynonyms, MagicHash, MultiParamTypeClasses, MultiWayIf, NoImplicitPrelude, NoMonomorphismRestriction, OverloadedStrings, PatternGuards, ParallelListComp, QuasiQuotes, RankNTypes, RecordWildCards, ScopedTypeVariables, StandaloneDeriving, TemplateHaskell, TupleSections, TypeFamilies, TypeOperators, UnboxedTuples default-language: Haskell2010- exposed-modules:- Hasql.Pool other-modules: Hasql.Pool.Prelude+ Hasql.Pool.ResourcePool+ exposed-modules:+ Hasql.Pool build-depends:- base >=4.11 && <5,- hasql >=1.3 && <1.6,- stm >=2.5 && <3,- time >=1.5 && <2,- transformers >=0.5 && <0.7+ -- resources:+ resource-pool >= 0.2 && < 0.3,+ -- database:+ hasql >= 1.3 && < 1.6,+ -- data:+ time >= 1.5 && < 2,+ -- general:+ base-prelude >= 1 && < 2 test-suite test@@ -68,5 +72,4 @@ base-prelude, hasql, hasql-pool,- stm >=2.5 && <3,- hspec >=2.6 && <3+ hspec >= 2.6 && < 3
library/Hasql/Pool.hs view
@@ -10,98 +10,17 @@ where import Hasql.Pool.Prelude-import Hasql.Connection (Connection)-import Hasql.Session (Session)-import qualified Hasql.Connection as Connection-import qualified Hasql.Session as Session+import qualified Hasql.Connection+import qualified Hasql.Session+import qualified Data.Pool as ResourcePool+import qualified Hasql.Pool.ResourcePool as ResourcePool -- | -- A pool of connections to DB.-data Pool =- Pool- Connection.Settings- {-^ Connection settings. -}- (TQueue ActiveConnection)- {-^ Queue of established connections. -}- (TVar Int)- {-^ Slots available for establishing new connections. -}- (TVar Bool)- {-^ Flag signaling whether pool's alive. -}--data ActiveConnection =- ActiveConnection {- activeConnectionLastUseTimestamp :: Int,- activeConnectionConnection :: Connection- }--loopCollectingGarbage :: Int -> TQueue ActiveConnection -> TVar Int -> TVar Bool -> IO ()-loopCollectingGarbage timeout establishedQueue slotsAvailVar aliveVar =- decide- where- decide =- do- ts <- getMillisecondsSinceEpoch- join $ atomically $ do- alive <- readTVar aliveVar- if alive- then let- tryToRelease =- tryReadTQueue establishedQueue >>= \ case- -- The queue is empty. Just wait for changes in the state.- Nothing ->- retry- Just entry@(ActiveConnection lastUseTs connection) ->- let- outdatingTs =- lastUseTs + timeout- in- -- Check whether it's outdated.- if outdatingTs < ts- -- Fetch the current value of available slots and- -- release this one and other connections.- then do- slotsAvail <- readTVar slotsAvailVar- collectAndRelease slotsAvail [connection] outdatingTs- -- Return it to the front of the queue and- -- wait until it's outdating time.- else do- unGetTQueue establishedQueue entry- return (sleep outdatingTs *> decide)- collectAndRelease !slotsAvail !outdatedList outdatingTs =- tryReadTQueue establishedQueue >>= \ case- Nothing ->- finalizeAndRelease slotsAvail outdatedList outdatingTs- Just entry@(ActiveConnection lastUseTs connection) ->- let- outdatingTs =- lastUseTs + timeout- in- if outdatingTs < ts- then do- unGetTQueue establishedQueue entry- finalizeAndRelease slotsAvail outdatedList outdatingTs- else- collectAndRelease (succ slotsAvail) (connection : outdatedList) outdatingTs- finalizeAndRelease slotsAvail outdatedList outdatingTs =- do- writeTVar slotsAvailVar slotsAvail- return (release outdatedList *> sleep outdatingTs *> decide)- in tryToRelease- else do- list <- flushTQueue establishedQueue- return (release (fmap activeConnectionConnection list))- sleep untilTs =- do- ts <- getMillisecondsSinceEpoch- let- diff =- untilTs - ts- in if diff > 0- then threadDelay (diff * 1000)- else return ()- release =- traverse_ Connection.release+newtype Pool =+ Pool (ResourcePool.Pool (Either Hasql.Connection.ConnectionError Hasql.Connection.Connection))+ deriving (Show) -- | -- Settings of the connection pool. Consist of:@@ -109,99 +28,48 @@ -- * Pool-size. -- -- * Timeout. --- An amount of time in milliseconds for which the unused connections are kept open.+-- An amount of time for which an unused resource is kept open.+-- The smallest acceptable value is 0.5 seconds. -- -- * Connection settings. -- type Settings =- (Int, Int, Connection.Settings)+ (Int, NominalDiffTime, Hasql.Connection.Settings) -- | -- Given the pool-size, timeout and connection settings -- create a connection-pool. acquire :: Settings -> IO Pool acquire (size, timeout, connectionSettings) =- do- establishedQueue <- newTQueueIO- slotsAvailVar <- newTVarIO size- aliveVar <- newTVarIO (size > 0)- forkIO $ loopCollectingGarbage timeout establishedQueue slotsAvailVar aliveVar- return (Pool connectionSettings establishedQueue slotsAvailVar aliveVar)+ fmap Pool $+ ResourcePool.createPool acquire release stripes timeout size+ where+ acquire =+ Hasql.Connection.acquire connectionSettings+ release =+ either (const (pure ())) Hasql.Connection.release+ stripes =+ 1 -- | -- Release the connection-pool. release :: Pool -> IO ()-release (Pool _ _ _ aliveVar) =- atomically (writeTVar aliveVar False)+release (Pool pool) =+ ResourcePool.destroyAllResources pool -- | -- A union over the connection establishment error and the session error. data UsageError =- ConnectionUsageError Connection.ConnectionError |- SessionUsageError Session.QueryError |- PoolIsReleasedUsageError+ ConnectionError Hasql.Connection.ConnectionError |+ SessionError Hasql.Session.QueryError deriving (Show, Eq) -- | -- Use a connection from the pool to run a session and -- return the connection to the pool, when finished.-use :: Pool -> Session.Session a -> IO (Either UsageError a)-use (Pool connectionSettings establishedQueue slotsAvailVar aliveVar) session =- join $ atomically $ do- alive <- readTVar aliveVar- if alive- then- tryReadTQueue establishedQueue >>= \ case- -- No established connection avail at the moment.- Nothing -> do- slotsAvail <- readTVar slotsAvailVar- -- Do we have any slots left for establishing new connections?- if slotsAvail > 0- -- Reduce the available slots var and instruct to- -- establish and use a new connection.- then do- writeTVar slotsAvailVar $! pred slotsAvail- return acquireConnectionThenUseThenPutItToQueue- -- Wait until the state changes and retry.- else- retry- Just (ActiveConnection _ connection) ->- return (useConnectionThenPutItToQueue connection)- else- return (return (Left PoolIsReleasedUsageError))- where- acquireConnectionThenUseThenPutItToQueue =- do- res <- Connection.acquire connectionSettings- case res of- -- Failed to acquire, so release an availability slot,- -- returning the error details.- Left acquisitionError -> do- atomically $ modifyTVar' slotsAvailVar succ- return (Left (ConnectionUsageError acquisitionError))- Right connection ->- useConnectionThenPutItToQueue connection- useConnectionThenPutItToQueue connection =- do- res <- Session.run session connection- case res of- Left queryError -> do- -- Check whether the error is on client-side,- -- and in that case release the connection.- case queryError of- Session.QueryError _ _ (Session.ClientError _) ->- releaseConnection connection- _ ->- putConnectionToPool connection- return (Left (SessionUsageError queryError))- Right res -> do- putConnectionToPool connection- return (Right res)- putConnectionToPool connection =- do- ts <- getMillisecondsSinceEpoch- atomically $ writeTQueue establishedQueue (ActiveConnection ts connection)- releaseConnection connection =- do- atomically $ modifyTVar' slotsAvailVar succ- Connection.release connection+use :: Pool -> Hasql.Session.Session a -> IO (Either UsageError a)+use (Pool pool) session =+ fmap (either (Left . ConnectionError) (either (Left . SessionError) Right)) $+ ResourcePool.withResourceOnEither pool $+ traverse $+ Hasql.Session.run session
library/Hasql/Pool/Prelude.hs view
@@ -1,111 +1,14 @@ module Hasql.Pool.Prelude-(+( module Exports,- getMillisecondsSinceEpoch, ) where --- base---------------------------import Control.Applicative as Exports hiding (WrappedArrow(..))-import Control.Arrow as Exports hiding (first, second)-import Control.Category as Exports-import Control.Concurrent as Exports-import Control.Exception as Exports-import Control.Monad as Exports hiding (fail, mapM_, sequence_, forM_, msum, mapM, sequence, forM)-import Control.Monad.IO.Class as Exports-import Control.Monad.Fail as Exports-import Control.Monad.Fix as Exports hiding (fix)-import Control.Monad.ST as Exports-import Data.Bifunctor as Exports-import Data.Bits as Exports-import Data.Bool as Exports-import Data.Char as Exports-import Data.Coerce as Exports-import Data.Complex as Exports-import Data.Data as Exports-import Data.Dynamic as Exports-import Data.Either as Exports-import Data.Fixed as Exports-import Data.Foldable as Exports hiding (toList)-import Data.Function as Exports hiding (id, (.))-import Data.Functor as Exports-import Data.Functor.Compose as Exports-import Data.Int as Exports-import Data.IORef as Exports-import Data.Ix as Exports-import Data.List as Exports hiding (sortOn, isSubsequenceOf, uncons, concat, foldr, foldl1, maximum, minimum, product, sum, all, and, any, concatMap, elem, foldl, foldr1, notElem, or, find, maximumBy, minimumBy, mapAccumL, mapAccumR, foldl')-import Data.List.NonEmpty as Exports (NonEmpty(..))-import Data.Maybe as Exports-import Data.Monoid as Exports hiding (Alt)-import Data.Ord as Exports-import Data.Proxy as Exports-import Data.Ratio as Exports-import Data.STRef as Exports-import Data.String as Exports-import Data.Traversable as Exports-import Data.Tuple as Exports-import Data.Unique as Exports-import Data.Version as Exports-import Data.Void as Exports-import Data.Word as Exports-import Debug.Trace as Exports-import Foreign.ForeignPtr as Exports-import Foreign.Ptr as Exports-import Foreign.StablePtr as Exports-import Foreign.Storable as Exports-import GHC.Conc as Exports hiding (orElse, withMVar, threadWaitWriteSTM, threadWaitWrite, threadWaitReadSTM, threadWaitRead)-import GHC.Exts as Exports (IsList(..), lazy, inline, sortWith, groupWith)-import GHC.Generics as Exports (Generic)-import GHC.IO.Exception as Exports-import Numeric as Exports-import Prelude as Exports hiding (fail, concat, foldr, mapM_, sequence_, foldl1, maximum, minimum, product, sum, all, and, any, concatMap, elem, foldl, foldr1, notElem, or, mapM, sequence, id, (.))-import System.Environment as Exports-import System.Exit as Exports-import System.IO as Exports (Handle, hClose)-import System.IO.Error as Exports-import System.IO.Unsafe as Exports-import System.Mem as Exports-import System.Mem.StableName as Exports-import System.Timeout as Exports-import Text.ParserCombinators.ReadP as Exports (ReadP, ReadS, readP_to_S, readS_to_P)-import Text.ParserCombinators.ReadPrec as Exports (ReadPrec, readPrec_to_P, readP_to_Prec, readPrec_to_S, readS_to_Prec)-import Text.Printf as Exports (printf, hPrintf)-import Text.Read as Exports (Read(..), readMaybe, readEither)-import Unsafe.Coerce as Exports --- stm+-- base-prelude --------------------------import Control.Concurrent.STM as Exports hiding (orElse)---- -- text--- ---------------------------- import Data.Text as Exports (Text)---- -- bytestring--- ---------------------------- import Data.ByteString as Exports (ByteString)+import BasePrelude as Exports hiding (assert, left, right, isLeft, isRight, error) -- time ------------------------- import Data.Time as Exports-import Data.Time.Clock.POSIX as Exports-import Data.Time.Clock.System as Exports---- transformers---------------------------import Control.Monad.Trans.Class as Exports-import Control.Monad.Trans.Cont as Exports hiding (shift, callCC)-import Control.Monad.Trans.Except as Exports (ExceptT(ExceptT), Except, except, runExcept, runExceptT, mapExcept, mapExceptT, withExcept, withExceptT)-import Control.Monad.Trans.Maybe as Exports-import Control.Monad.Trans.Reader as Exports (Reader, runReader, mapReader, withReader, ReaderT(ReaderT), runReaderT, mapReaderT, withReaderT)-import Control.Monad.Trans.State.Strict as Exports (State, runState, evalState, execState, mapState, withState, StateT(StateT), runStateT, evalStateT, execStateT, mapStateT, withStateT)-import Control.Monad.Trans.Writer.Strict as Exports (Writer, runWriter, execWriter, mapWriter, WriterT(..), execWriterT, mapWriterT)---getMillisecondsSinceEpoch :: IO Int-getMillisecondsSinceEpoch =- fmap (fromIntegral . systemTimeToMicros) getSystemTime- where- systemTimeToMicros (MkSystemTime s ns) =- s * 1000 + fromIntegral (div ns 1000000)
+ library/Hasql/Pool/ResourcePool.hs view
@@ -0,0 +1,21 @@+{-|+Extras for the resource-pool library.+-}+module Hasql.Pool.ResourcePool+where++import Hasql.Pool.Prelude+import Data.Pool+++withResourceOnEither :: Pool resource -> (resource -> IO (Either failure success)) -> IO (Either failure success)+withResourceOnEither pool act = mask_ $ do+ (resource, localPool) <- takeResource pool+ failureOrSuccess <- act resource `onException` destroyResource pool localPool resource+ case failureOrSuccess of+ Right success -> do+ putResource localPool resource+ return (Right success)+ Left failure -> do+ destroyResource pool localPool resource+ return (Left failure)