{-# LANGUAGE CPP #-} {-# LANGUAGE NoImplicitPrelude #-} -- | -- Module: $HEADER$ -- Description: Connection pools for various transport protocols. -- Copyright: (c) 2014-2016 Peter Trško -- License: BSD3 -- -- Maintainer: peter.trsko@gmail.com -- Stability: unstable -- Portability: GHC specific language extensions. -- -- Connection pools for TCP clients and UNIX Socket clients, later is not -- supported on Windows. -- -- This package is built on top of -- and -- -- packages. The later allows us to use -- package for -- implementing TCP and UNIX Sockets clients. Package /conduit-extra/ defines -- @appSource@ and @appSink@ based on abstractions from /streaming-commons/ -- package and they can be therefore reused. Difference between using -- /conduit-extra/ or /streaming-commons/ is that instead of using -- @runTCPClient@ (or its lifted variant @runGeneralTCPClient@ from -- /conduit-extra/) one would use 'withTcpClientConnection', and instead of -- @runUnixClient@ it would be 'withUnixClientConnection'. There is also more -- generic function named 'withConnection', which takes either 'ConnectionPool' -- instance. module Data.ConnectionPool ( -- * TCP Client Example -- -- $tcpClientExample #ifndef WINDOWS -- Windows doesn't support UNIX Sockets. -- * Unix Client Example -- -- $unixClientExample #endif -- !WINDOWS -- * Connection Pool -- -- $connectionPool ConnectionPool -- * Constructing Connection Pool -- -- $constructingConnectionPool , ResourcePoolParams -- ** Lenses -- -- $resourcePoolParamsLenses , numberOfResourcesPerStripe , numberOfStripes , resourceIdleTimeout -- ** Validation -- -- $resourcePoolParamsValidation , validateResourcePoolParams -- * TCP Client Connection Pool , TcpClient , ClientSettings , AppData , createTcpClientPool , withTcpClientConnection , tryWithTcpClientConnection , destroyAllTcpClientConnections #ifndef WINDOWS -- Windows doesn't support UNIX Sockets. -- * UNIX Client Connection Pool , UnixClient , ClientSettingsUnix , AppDataUnix , createUnixClientPool , withUnixClientConnection , tryWithUnixClientConnection , destroyAllUnixClientConnections #endif -- !WINDOWS -- * Polymorphic Interface -- -- | /Since version 0.2./ , ConnectionPoolFor(..) ) where import Data.Streaming.Network ( AppData , ClientSettings #ifndef WINDOWS -- Windows doesn't support UNIX Sockets. , ClientSettingsUnix , AppDataUnix #endif -- !WINDOWS ) import Data.ConnectionPool.Class ( ConnectionPoolFor ( HandlerData , destroyAllConnections , tryWithConnection , withConnection ) ) import Data.ConnectionPool.Family (ConnectionPool) import Data.ConnectionPool.Internal.ResourcePoolParams ( ResourcePoolParams , numberOfResourcesPerStripe , numberOfStripes , resourceIdleTimeout , validateResourcePoolParams ) import Data.ConnectionPool.Internal.TCP ( TcpClient , createTcpClientPool , destroyAllTcpClientConnections , tryWithTcpClientConnection , withTcpClientConnection ) #ifndef WINDOWS -- Windows doesn't support UNIX Sockets. import Data.ConnectionPool.Internal.Unix ( UnixClient , createUnixClientPool , destroyAllUnixClientConnections , tryWithUnixClientConnection , withUnixClientConnection ) #endif -- !WINDOWS -- $tcpClientExample -- -- Here is a simple example that demonstrates how TCP client can be created and -- how connection pool behaves. -- -- @ -- {-\# LANGUAGE OverloadedStrings \#-} -- module Main (main) -- where -- -- import Control.Concurrent -- ( forkIO -- , newEmptyMVar -- , putMVar -- , readMVar -- , threadDelay -- ) -- import Control.Monad (void, mapM_) -- import System.Environment (getArgs) -- -- import Control.Lens ((.~), (&)) -- import Data.ConnectionPool -- ( 'createTcpClientPool' -- , 'numberOfResourcesPerStripe' -- , 'numberOfStripes' -- , 'withTcpClientConnection' -- ) -- import Data.Default.Class (Default(def)) -- import Data.Streaming.Network -- ( 'Data.Streaming.Network.appWrite' -- , 'Data.Streaming.Network.clientSettingsTCP' -- ) -- -- -- main :: IO () -- main = do -- [port, numStripes, numPerStripe] <- getArgs -- pool <- 'createTcpClientPool' -- (poolParams numStripes numPerStripe) -- ('Data.Streaming.Network.clientSettingsTCP' (read port) \"127.0.0.1\") -- thread1 <- newEmptyMVar -- thread2 <- newEmptyMVar -- void . forkIO . 'withTcpClientConnection' pool $ \\appData -> do -- threadDelay 1000 -- 'Data.Streaming.Network.appWrite' appData \"1: I'm alive!\\n\" -- putMVar thread1 () -- void . forkIO . 'withTcpClientConnection' pool $ \\appData -> do -- 'Data.Streaming.Network.appWrite' appData \"2: I'm alive!\\n\" -- putMVar thread2 () -- mapM_ readMVar [thread1, thread2] -- where -- poolParams m n = -- 'Data.Default.Class.def' & 'numberOfStripes' .~ read m -- & 'numberOfResourcesPerStripe' .~ read n -- @ -- -- To test it we can use @socat@ or some @netcat@ like application. Our test -- will require two terminals, in one we will execute @socat@ as a server -- listenting on UNIX socket and in the other one we execute above example. -- -- Simple TCP server listening on port @8001@ that prints what it receives to -- stdout: -- -- > $ socat TCP4-LISTEN:8001,bind=127.0.0.1,fork - -- -- The @fork@ parameter in the above example is important, otherwise @socat@ -- would terminate when client closes its connection. -- -- If we run above example as: -- -- > $ runghc tcp-example.hs 8001 1 1 -- -- We can see that @socat@ received following text: -- -- > 1: I'm alive! -- > 2: I'm alive! -- -- But if we increment number of stripes or number of connections (resources) -- per stripe, then we will get: -- -- > 2: I'm alive! -- > 1: I'm alive! -- -- The reason for this is that we use @threadDelay 100@ in the first executed -- thread. So when we have only one stripe and one connection per stripe, then -- we have only one connection in the pool. Therefore when the first thread -- executes and acquires a connection, then all the other threads (the other -- one in above example) will block. If we have more then one connection -- available in our pool, then the first thread acquires connection, blocks on -- @threadDelay@ call, but the other thread also acquires connection and prints -- its output while the first thread is still blocked on @threadDelay@. This -- example demonstrates how connection pool behaves if it reached its capacity -- and when it has enough free resources. -- $unixClientExample -- -- Here is a simple example that demonstrates how UNIX Sockets client can be -- created and how connection pool behaves. -- -- @ -- {-\# LANGUAGE OverloadedStrings \#-} -- module Main (main) -- where -- -- import Control.Concurrent -- ( forkIO -- , newEmptyMVar -- , putMVar -- , readMVar -- , threadDelay -- ) -- import Control.Monad (void, mapM_) -- import System.Environment (getArgs) -- -- import Control.Lens ((.~), (&)) -- import Data.ConnectionPool -- ( 'createUnixClientPool' -- , 'numberOfResourcesPerStripe' -- , 'numberOfStripes' -- , 'withUnixClientConnection' -- ) -- import Data.Default.Class (Default(def)) -- import Data.Streaming.Network -- ( 'Data.Streaming.Network.appWrite' -- , 'Data.Streaming.Network.clientSettingsUnix' -- ) -- -- -- main :: IO () -- main = do -- [socket, numStripes, numPerStripe] <- getArgs -- pool <- 'createUnixClientPool' -- (poolParams numStripes numPerStripe) -- ('Data.Streaming.Network.clientSettingsUnix' socket) -- thread1 <- newEmptyMVar -- thread2 <- newEmptyMVar -- void . forkIO . 'withUnixClientConnection' pool $ \\appData -> do -- threadDelay 100 -- 'Data.Streaming.Network.appWrite' appData \"1: I'm alive!\\n\" -- putMVar thread1 () -- void . forkIO . 'withUnixClientConnection' pool $ \\appData -> do -- 'Data.Streaming.Network.appWrite' appData \"2: I'm alive!\\n\" -- putMVar thread2 () -- mapM_ readMVar [thread1, thread2] -- where -- poolParams m n = -- 'Data.Default.Class.def' & 'numberOfStripes' .~ read m -- & 'numberOfResourcesPerStripe' .~ read n -- @ -- -- Above example is very similar to our TCP Client Example and most notably the -- implementation of two client threads is the same. Testing it is very similar -- to testing TCP Client Example, but we would use different command for -- @socat@ and for executing the example. -- -- Simple UNIX socket server that prints what it receives to stdout: -- -- > $ socat UNIX-LISTEN:test.sock,fork - -- -- Parameter @fork@ has the same importance as when we used it in the command -- for running TCP server. -- -- We can execute UNIX Sockets Example using: -- -- > $ runghc unix-sockets-example.hs test.sock 1 1 -- -- Result of the test will be the same in case of using one stripe and one -- connection per stripe, and when we increase total number connections, to -- what we had with the TCP Client Example. -- $connectionPool -- -- For each supported protocol we have a 'ConnectionPool' data family instance -- that is tagged with supported protocol. Currently it can be either -- 'TcpClient' or 'UnixClient'. This way we are able to use same core -- implementation for both and only need to deviate from common code where -- necessary. -- -- Under the hood we use 'Network.Socket.Socket' to represent connections and -- that limits possible implementations of 'ConnectionPool' instances to -- protocols supported by -- package. -- -- Those interested in details should look in to -- "Data.ConnectionPool.Internal.ConnectionPool" and -- "Data.ConnectionPool.Internal.ConnectionPoolFamily" modules. -- $constructingConnectionPool -- -- This section describes basic principles that are shared among provided -- connection pools. It can also provide basic information in case of creating -- new connection pool while using API and types from this package. -- -- For each protocol we provide separate function that creates 'ConnectionPool' -- instance. For TCP clients it's 'createTcpClientPool' and for UNIX Socket -- clients it's 'createUnixClientPool' (not available on Windows). -- -- In each case two kinds of values need to be provided as parameters to such -- functions: -- -- 1. Parameters of underlying resource pool like how to organize stripes and -- parameters for algorithm that handles resource releasing, etc. -- -- 2. Transport protocol parameters like IP address, port, UNIX Socket file, -- and similar. -- -- To simplify things we provide 'ResourcePoolParams' data type that is -- accepted by concrete constructors of 'ConnectionPool' instances and it wraps -- all common connection pool parameters. And for protocol specific settings -- this package reuses data types from /streaming-commons/ library. -- -- As a result, of the above, type signature of function that creates -- connection pool for some protocol named @MyProtocol@ could look like: -- -- @ -- createMyProtocolPool -- :: 'ResourcePoolParams' -- -> MyProtocolParams -- -> 'IO' ('ConnectionPool' MyProtocol) -- @ -- -- To further simplify things this package defines default value for -- 'ResourcePoolParams' using 'Data.Default.Class.Default' type class that has -- only one method named 'Data.Default.Class.def'. Instance of this class is -- declared using minimal possible values of each parameter required by -- underlying resource pool. In example, to specify connection pool with 2 -- stripes with 8 connections in each stripe, but keeping connection idle -- timeout on its default value, we can simply use: -- -- @ -- 'Data.Default.Class.def' & 'numberOfStripes' .~ 2 -- & 'numberOfResourcesPerStripe' .~ 8 -- @ -- -- Where functions @&@ and @.~@ are defined by -- package. Function -- 'Data.Function.&' is also available in base >= 4.8. -- $resourcePoolParamsLenses -- -- For details on how to use leses as these see -- package where you might find -- a good starting point. -- $resourcePoolParamsValidation -- -- Sometimes one needs to validate parameters as early as possible, e.g. while -- parsing command line options. -- -- Usage example: -- -- @ -- 'validateResourcePoolParams' $ someParams -- & 'resourceIdleTimeout' .~ 1 -- & 'numberOfResourcesPerStripe' .~ 16 -- @ -- -- Most usually one would use 'Data.Default.def' instead of @someParams@. -- Functions @&@ and @.~@ are defined in -- package. Function -- 'Data.Function.&' is also available in base >= 4.8.