def __init__(self, endpoints, client_cls, size=DEFAULT_SIZE, ttransport=TTransport.TBufferedTransport, tprotocol=TBinaryProtocol.TBinaryProtocol, use_ssl=False, ca_certs=None, cert=None, key=None): """ Initialize the thrift connection pool for a specific server and client """ self.endpoints = endpoints self.client_cls = client_cls self.ttransport = ttransport self.tprotocol = tprotocol self.use_ssl = use_ssl self.ca_certs = ca_certs self.cert = cert self.key = key self._closed = False self.size = size self._semaphore = threading.BoundedSemaphore(size) self._connection_queue = LifoQueueWithEvict(size)
class ThriftConnectionPool(object): """ Thrift Conncetion pool Attributes: endpoints: list of endpoints that thrift server is listening on client_cls: thrift client class to instantiate size: optional size of the pool, equivalent to max number of connections. Defaults to ThriftConnectionPool.DEFAULT_SIZE ttransport: optional thrift transport to use, defaults to TTransport.TBufferedTransport tprotocol: optional thrift protocol to use, defaults to TBinaryProtocol.TBinaryProtocol ca_certs, cert, key: files needed by SSL """ DEFAULT_SIZE = 5 def __init__(self, endpoints, client_cls, size=DEFAULT_SIZE, ttransport=TTransport.TBufferedTransport, tprotocol=TBinaryProtocol.TBinaryProtocol, use_ssl=False, ca_certs=None, cert=None, key=None): """ Initialize the thrift connection pool for a specific server and client """ self.endpoints = endpoints self.client_cls = client_cls self.ttransport = ttransport self.tprotocol = tprotocol self.use_ssl = use_ssl self.ca_certs = ca_certs self.cert = cert self.key = key self._closed = False self.size = size self._semaphore = threading.BoundedSemaphore(size) self._connection_queue = LifoQueueWithEvict(size) def evict_check(self, idle_threshold_millis): self._connection_queue.evict(idle_threshold_millis, self._close_connection) def close(self): """ Closes the connection pool. Outstanding connections will be closed on the next call to return_connection """ self._closed = True while not self._connection_queue.empty(): try: (conn, t) = self._connection_queue.get(block=False) self._connection_queue.task_done() try: self._close_connection(conn) except: # ignore thrift errors, nothing we can do here pass except Queue.Empty: # ignore empty queue - that's what we want anyway pass def _create_connection(self): """ Helper that creates a thrift client connection to the instance host and port. Returns: An instance of the thrift client class with an open transport """ endpoints = self.endpoints[:] shuffle(endpoints) for endpoint in endpoints: try: host, port = endpoint.split(':') logger.debug('connecting to %s:%s. ssl? %s', host, port, self.use_ssl) if self.use_ssl: socket = TSSLSocket(host=host, port=int(port), ca_certs=self.ca_certs, cert=self.cert, key=self.key) else: socket = TSocket.TSocket(host, int(port)) transport = self.ttransport(socket) protocol = self.tprotocol(transport) connection = self.client_cls(protocol) transport.open() except Exception as e: logger.info("connection to %s failed, try other endpoints: %s", endpoint, e) continue return connection raise Exception("Can't open a connection on any endpoints!") @staticmethod def _close_connection(conn): """ Helper that closes a thrift client input and output transports Args: conn: the thrift connection that should be closed """ try: conn._iprot.trans.close() except: logger.warn('failed to close iprot transport on %s', conn) try: conn._oprot.trans.close() except: logger.warn('failed to close oprot transport on %s', conn) def get_connection(self): """ Get a connection from the pool. If the pool is empty, it will create a new one. If the pool is full, it blocks until one is available. Returns: A connected thrift client """ self._semaphore.acquire() if self._closed: raise RuntimeError('connection pool already closed') try: (conn, t) = self._connection_queue.get(block=False) self._connection_queue.task_done() return conn except Queue.Empty: try: return self._create_connection() except: self._semaphore.release() raise def return_connection(self, conn): """ Return the given connection to the pool of available connections. If the pool is closed, just close the connection. Args: conn: the thrift connection to be returned to the connection pool """ if self._closed: self._close_connection(conn) else: self._connection_queue.put((conn, current_time_millis())) self._semaphore.release() def release_conn(self, conn): """ Release the connection without returning to the pool. Use when the connection is bad, i.e. catching a TTransportException. Args: conn: the thrift connection being released """ try: self._close_connection(conn) except: # Ignore, not returning to the pool anyway pass if not self._closed: self._semaphore.release()