Package: geventconnpool
geventconnpoolThis package implements a generic TCP connection pool for gevent-based
applications. It can be used every time your program needs to connnect to
an external service through a TCP-based protocol (including all HTTP protocols
like REST APIs), and you want your process to keep and manage a pool of
connections to the remote endpoint.A typical scenario might be a gunicorn-based web application with gevent backend,
that accesses remote service through HTTPS APIs. In this case, using the pool
shorten the request and minimize the latency because the pool keeps open
connections to the remote endpoints, and it is not necessary to do a full SSL
handshake every time we need to issue a command.QuickstartTo install the package, use pip:$ pip install geventconnpoolor easy_install:$ easy_install geventconnpoolYou need to derive fromConnectionPooland reimplement_new_connection,
to specify how to open a connection to the remove site. For instance:fromgeventconnpoolimportConnectionPoolfromgeventimportsocketclassMyPool(ConnectionPool):def_new_connection(self):returnsocket.create_connection(('test.example.org',2485))In this case, we’re simply opening a TCP connection to a specified peer.The pool can be istantiated by specifying how many connections we want to
keep open at the same time:pool=MyPool(20)# always keep 20 connections openTo access a connection within the pool:withpool.get()asc:c.send("PING\n")ifc.recv(5)!="PONG\n":raisesocket.error("something awful happened")If the context is quit through asocket.errorexception, the connection is
discarded and a new open is opened in background, to keep the pool always full
of valid connections. Any other exception does not have a special meaning, and
the connection will be reinserted into the pool to be reused later.If your application/library uses exceptions that do not derive fromsocket.errorto signify connection errors (imaplibis one example),
you can override which exceptions are treated as triggering discards:pool=MyPool(20,exc_classes=(socket.error,imaplib.IMAP4.error))Automatic retryingIf you want to be resilent to temporary network errors, you can use theretrydecorator that will re-execute the function if it is quit with asocket.errorexception:fromgeventconnpoolimportretry@retrydefsenddata(data):withpool.get()asc:c.send(data)ifc.recv(2)!="OK":raisesocket.error("something awful happened")Since the pool discards the connections when asocket.errorexception is
generated, the net effect ofretryis that a different connection will be
used for each attempt.Theretrydecorator has some extra features which are disabled by default.
If you pass it a logger, it will log each attempt. You can also specify a
maximum number of attempts, an interval to wait between successive attempts,
and which specific exception classes to trigger retries on. Log messages and
levels are customizable.importlogginglogging.basicConfig()log=logging.getLogger()fromgeventconnpoolimportretry@retry(exc_classes=(socket.error,imaplib.IMAP4.error),logger=log,max_failures=5,interval=2)defsenddata(data):withpool.get()asc:typ,data=c.select('INBOX')If you wish to codify a set of options toretryinto your code, consider
usingfunctools.partial.Advanced connection examplesWhen implement a connection pool, it is advisable to perform all the
initialization phases of the application protocol within the_new_connectioncallback. For instance, a protocol might allow to switch to TLS
(with a STARTTLS-like) and then require authentication:fromgeventconnpoolimportConnectionPoolfromgeventimportsocket,sslclassMyPool(ConnectionPool):def_new_connection(self):s=socket.create_connection(('test.example.org',2485))s.send("STARTTLS\n")res=s.recv(3)ifres=="OK\n":s=ssl.wrap_socket(s)elifres=="NO\n":passelse:raisesocket.error("invalid response to STARTTLS")s.send("LOGIN:%s\n"%MY_LOGIN_NAME);s.send("PASS:%s\n"%MY_PASS);res=s.recv(2)ifres!="OK":raisesocket.error("authentication failed")returnsAs you can see, it is possible to simply raisesocket.errorif something
went wrong. The pool is resistant to temporary connection errors and will retry
automatically to estabilish new connections later.Another common situation might involve the usage of third-party libraries like for
instance usingbototo connect to
Amazon AWS:fromgeventconnpoolimportConnectionPoolimportbotofromboto.exceptionimportNoAuthHanlderclassUsersPool(ConnectionPool):def_new_connection(self):try:c=boto.connect_dynamodb(MY_AWS_KEY_ID,MY_AWS_SECRET_KEY)returnc.get_table("users")except:raisesocket.error("error connecting to AWS")In this case, we don’t only connect to AWS and authenticate, but we also open
a specific table and return a reference to that table. In fact, it is not
necessary for the return value of_new_connection()to be a socket (or
socket-like):ConnectionPooltreats it as a black.box and return it whengetis called.Noteboto has an internal connection pool, but it is only used to be
fully-thread safe, and does not preemptively open the connections,
authenticate, and perform initialization. This means that it still makes
sense to useConnectionPoolto minimize the latency when communicating
to AWS.KeepaliveSome protocols or networks might require a keepalive mechanism to keep a
connection open if it is idle. For instance, the remote peer, a firewall or a
load-balancer might close a connection if it is idle for too long.Sometimes, it is sufficient to rely on the standard TCP-level keeaplive, that
can be turned on any TCP socket:fromgeventconnpoolimportConnectionPoolfromgeventimportsocketclassMyPool(ConnectionPool):def_new_connection(self):s=socket.create_connection(('test.example.org',2485))s._sock.setsockopt(socket.SOL_SOCKET,socket.SO_KEEPALIVE,1)returnsThe TCP keepalive uses ACK packets to continously communicating with the remote
peer. To tune the keepalive parameters (delay between ACKs, number of unanswered
ACKs to consider the connnection dropped, etc.), you need to tweak with the
proc filesystem (yes, it’s a global per-computer configuration).Alternatively, it is possible to implement an application-level keepalive
by implemening the_keepalivemethod and specifying the keepalive frequency
in the constructor:fromgeventconnpoolimportConnectionPoolfromgeventimportsocketclassMyPool(ConnectionPool):def_new_connection(self):returnsocket.create_connection(('test.example.org',2485))def_keepalive(self,c):c.send("PING\n")ifc.recv(5)!="PONG\n":raisesocket.errorpool=MyPool(20,keepalive=30)The above code uses a keepalive based on an application-level command (PING),
and specifies that it should be executed every 30 seconds (per each connection)._keepaliveshould raisesocket.errorto communicate that the connection
appears to be broken and should be discarded by the pool.