PageRenderTime 49ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 1ms

/pycassa/pool.py

http://github.com/pycassa/pycassa
Python | 870 lines | 823 code | 9 blank | 38 comment | 3 complexity | 1548208ce24050485a1f43677a6c5a00 MD5 | raw file
Possible License(s): Apache-2.0
  1. """ Connection pooling for Cassandra connections. """
  2. from __future__ import with_statement
  3. import time
  4. import threading
  5. import random
  6. import socket
  7. import sys
  8. if 'gevent.monkey' in sys.modules:
  9. from gevent import queue as Queue
  10. else:
  11. import Queue # noqa
  12. from thrift import Thrift
  13. from thrift.transport.TTransport import TTransportException
  14. from connection import (Connection, default_socket_factory,
  15. default_transport_factory)
  16. from logging.pool_logger import PoolLogger
  17. from util import as_interface
  18. from cassandra.ttypes import TimedOutException, UnavailableException
  19. _BASE_BACKOFF = 0.01
  20. __all__ = ['QueuePool', 'ConnectionPool', 'PoolListener',
  21. 'ConnectionWrapper', 'AllServersUnavailable',
  22. 'MaximumRetryException', 'NoConnectionAvailable',
  23. 'InvalidRequestError']
  24. class ConnectionWrapper(Connection):
  25. """
  26. Creates a wrapper for a :class:`~.pycassa.connection.Connection`
  27. object, adding pooling related functionality while still allowing
  28. access to the thrift API calls.
  29. These should not be created directly, only obtained through
  30. Pool's :meth:`~.ConnectionPool.get()` method.
  31. """
  32. # These mark the state of the connection so that we can
  33. # check to see that they are not returned, checked out,
  34. # or disposed twice (or from the wrong state).
  35. _IN_QUEUE = 0
  36. _CHECKED_OUT = 1
  37. _DISPOSED = 2
  38. def __init__(self, pool, max_retries, *args, **kwargs):
  39. self._pool = pool
  40. self._retry_count = 0
  41. self.max_retries = max_retries
  42. self.info = {}
  43. self.starttime = time.time()
  44. self.operation_count = 0
  45. self._state = ConnectionWrapper._CHECKED_OUT
  46. Connection.__init__(self, *args, **kwargs)
  47. self._pool._notify_on_connect(self)
  48. # For testing purposes only
  49. self._should_fail = False
  50. self._original_meth = self.send_batch_mutate
  51. def return_to_pool(self):
  52. """
  53. Returns this to the pool.
  54. This has the same effect as calling :meth:`ConnectionPool.put()`
  55. on the wrapper.
  56. """
  57. self._pool.put(self)
  58. def _checkin(self):
  59. if self._state == ConnectionWrapper._IN_QUEUE:
  60. raise InvalidRequestError("A connection has been returned to "
  61. "the connection pool twice.")
  62. elif self._state == ConnectionWrapper._DISPOSED:
  63. raise InvalidRequestError("A disposed connection has been returned "
  64. "to the connection pool.")
  65. self._state = ConnectionWrapper._IN_QUEUE
  66. def _checkout(self):
  67. if self._state != ConnectionWrapper._IN_QUEUE:
  68. raise InvalidRequestError("A connection has been checked "
  69. "out twice.")
  70. self._state = ConnectionWrapper._CHECKED_OUT
  71. def _is_in_queue_or_disposed(self):
  72. ret = self._state == ConnectionWrapper._IN_QUEUE or \
  73. self._state == ConnectionWrapper._DISPOSED
  74. return ret
  75. def _dispose_wrapper(self, reason=None):
  76. if self._state == ConnectionWrapper._DISPOSED:
  77. raise InvalidRequestError("A connection has been disposed twice.")
  78. self._state = ConnectionWrapper._DISPOSED
  79. self.close()
  80. self._pool._notify_on_dispose(self, msg=reason)
  81. def _replace(self, new_conn_wrapper):
  82. """
  83. Get another wrapper from the pool and replace our own contents
  84. with its contents.
  85. """
  86. self.server = new_conn_wrapper.server
  87. self.transport = new_conn_wrapper.transport
  88. self._iprot = new_conn_wrapper._iprot
  89. self._oprot = new_conn_wrapper._oprot
  90. self.info = new_conn_wrapper.info
  91. self.starttime = new_conn_wrapper.starttime
  92. self.operation_count = new_conn_wrapper.operation_count
  93. self._state = ConnectionWrapper._CHECKED_OUT
  94. self._should_fail = new_conn_wrapper._should_fail
  95. @classmethod
  96. def _retry(cls, f):
  97. def new_f(self, *args, **kwargs):
  98. self.operation_count += 1
  99. self.info['request'] = {'method': f.__name__, 'args': args, 'kwargs': kwargs}
  100. try:
  101. allow_retries = kwargs.pop('allow_retries', True)
  102. if kwargs.pop('reset', False):
  103. self._pool._replace_wrapper() # puts a new wrapper in the queue
  104. self._replace(self._pool.get()) # swaps out transport
  105. result = f(self, *args, **kwargs)
  106. self._retry_count = 0 # reset the count after a success
  107. return result
  108. except Thrift.TApplicationException:
  109. self.close()
  110. self._pool._decrement_overflow()
  111. self._pool._clear_current()
  112. raise
  113. except (TimedOutException, UnavailableException,
  114. TTransportException,
  115. socket.error, IOError, EOFError), exc:
  116. self._pool._notify_on_failure(exc, server=self.server, connection=self)
  117. self.close()
  118. self._pool._decrement_overflow()
  119. self._pool._clear_current()
  120. self._retry_count += 1
  121. if (not allow_retries or
  122. (self.max_retries != -1 and self._retry_count > self.max_retries)):
  123. raise MaximumRetryException('Retried %d times. Last failure was %s: %s' %
  124. (self._retry_count, exc.__class__.__name__, exc))
  125. # Exponential backoff
  126. time.sleep(_BASE_BACKOFF * (2 ** self._retry_count))
  127. kwargs['reset'] = True
  128. return new_f(self, *args, **kwargs)
  129. new_f.__name__ = f.__name__
  130. return new_f
  131. def _fail_once(self, *args, **kwargs):
  132. if self._should_fail:
  133. self._should_fail = False
  134. raise TimedOutException
  135. else:
  136. return self._original_meth(*args, **kwargs)
  137. def get_keyspace_description(self, keyspace=None, use_dict_for_col_metadata=False):
  138. """
  139. Describes the given keyspace.
  140. If `use_dict_for_col_metadata` is ``True``, the column metadata will be stored
  141. as a dictionary instead of a list
  142. A dictionary of the form ``{column_family_name: CfDef}`` is returned.
  143. """
  144. if keyspace is None:
  145. keyspace = self.keyspace
  146. ks_def = self.describe_keyspace(keyspace)
  147. cf_defs = dict()
  148. for cf_def in ks_def.cf_defs:
  149. cf_defs[cf_def.name] = cf_def
  150. if use_dict_for_col_metadata:
  151. old_metadata = cf_def.column_metadata
  152. new_metadata = dict()
  153. for datum in old_metadata:
  154. new_metadata[datum.name] = datum
  155. cf_def.column_metadata = new_metadata
  156. return cf_defs
  157. def __str__(self):
  158. return "<ConnectionWrapper %s@%s>" % (self.keyspace, self.server)
  159. retryable = ('get', 'get_slice', 'multiget_slice', 'get_count', 'multiget_count',
  160. 'get_range_slices', 'get_indexed_slices', 'batch_mutate', 'add',
  161. 'insert', 'remove', 'remove_counter', 'truncate', 'describe_keyspace',
  162. 'atomic_batch_mutate')
  163. for fname in retryable:
  164. new_f = ConnectionWrapper._retry(getattr(Connection, fname))
  165. setattr(ConnectionWrapper, fname, new_f)
  166. class ConnectionPool(object):
  167. """A pool that maintains a queue of open connections."""
  168. _max_overflow = 0
  169. def _get_max_overflow(self):
  170. return self._max_overflow
  171. def _set_max_overflow(self, max_overflow):
  172. with self._pool_lock:
  173. self._max_overflow = max_overflow
  174. self._overflow_enabled = max_overflow > 0 or max_overflow == -1
  175. if max_overflow == -1:
  176. self._max_conns = (2 ** 31) - 1
  177. else:
  178. self._max_conns = self._pool_size + max_overflow
  179. max_overflow = property(_get_max_overflow, _set_max_overflow)
  180. """ Whether or not a new connection may be opened when the
  181. pool is empty is controlled by `max_overflow`. This specifies how many
  182. additional connections may be opened after the pool has reached `pool_size`;
  183. keep in mind that these extra connections will be discarded upon checkin
  184. until the pool is below `pool_size`. This may be set to -1 to indicate no
  185. overflow limit. The default value is 0, which does not allow for overflow. """
  186. pool_timeout = 30
  187. """ If ``pool_size + max_overflow`` connections have already been checked
  188. out, an attempt to retrieve a new connection from the pool will wait
  189. up to `pool_timeout` seconds for a connection to be returned to the
  190. pool before giving up. Note that this setting is only meaningful when you
  191. are accessing the pool concurrently, such as with multiple threads.
  192. This may be set to 0 to fail immediately or -1 to wait forever.
  193. The default value is 30. """
  194. recycle = 10000
  195. """ After performing `recycle` number of operations, connections will
  196. be replaced when checked back in to the pool. This may be set to
  197. -1 to disable connection recycling. The default value is 10,000. """
  198. max_retries = 5
  199. """ When an operation on a connection fails due to an :exc:`~.TimedOutException`
  200. or :exc:`~.UnavailableException`, which tend to indicate single or
  201. multiple node failure, the operation will be retried on different nodes
  202. up to `max_retries` times before an :exc:`~.MaximumRetryException` is raised.
  203. Setting this to 0 disables retries and setting to -1 allows unlimited retries.
  204. The default value is 5. """
  205. logging_name = None
  206. """ By default, each pool identifies itself in the logs using ``id(self)``.
  207. If multiple pools are in use for different purposes, setting `logging_name` will
  208. help individual pools to be identified in the logs. """
  209. socket_factory = default_socket_factory
  210. """ A function that creates the socket for each connection in the pool.
  211. This function should take two arguments: `host`, the host the connection is
  212. being made to, and `port`, the destination port.
  213. By default, this is function is :func:`~connection.default_socket_factory`.
  214. """
  215. transport_factory = default_transport_factory
  216. """ A function that creates the transport for each connection in the pool.
  217. This function should take three arguments: `tsocket`, a TSocket object for the
  218. transport, `host`, the host the connection is being made to, and `port`,
  219. the destination port.
  220. By default, this is function is :func:`~connection.default_transport_factory`.
  221. """
  222. def __init__(self, keyspace,
  223. server_list=['localhost:9160'],
  224. credentials=None,
  225. timeout=0.5,
  226. use_threadlocal=True,
  227. pool_size=5,
  228. prefill=True,
  229. socket_factory=default_socket_factory,
  230. transport_factory=default_transport_factory,
  231. **kwargs):
  232. """
  233. All connections in the pool will be opened to `keyspace`.
  234. `server_list` is a sequence of servers in the form ``"host:port"`` that
  235. the pool will connect to. The port defaults to 9160 if excluded.
  236. The list will be randomly shuffled before being drawn from sequentially.
  237. `server_list` may also be a function that returns the sequence of servers.
  238. If authentication or authorization is required, `credentials` must
  239. be supplied. This should be a dictionary containing 'username' and
  240. 'password' keys with appropriate string values.
  241. `timeout` specifies in seconds how long individual connections will
  242. block before timing out. If set to ``None``, connections will never
  243. timeout.
  244. If `use_threadlocal` is set to ``True``, repeated calls to
  245. :meth:`get()` within the same application thread will
  246. return the same :class:`ConnectionWrapper` object if one is
  247. already checked out from the pool. Be careful when setting `use_threadlocal`
  248. to ``False`` in a multithreaded application, especially with retries enabled.
  249. Synchronization may be required to prevent the connection from changing while
  250. another thread is using it.
  251. The pool will keep up to `pool_size` open connections in the pool
  252. at any time. When a connection is returned to the pool, the
  253. connection will be discarded if the pool already contains `pool_size`
  254. connections. The total number of simultaneous connections the pool will
  255. allow is ``pool_size + max_overflow``,
  256. and the number of "sleeping" connections the pool will allow is ``pool_size``.
  257. A good choice for `pool_size` is a multiple of the number of servers
  258. passed to the Pool constructor. If a size less than this is chosen,
  259. the last ``(len(server_list) - pool_size)`` servers may not be used until
  260. either overflow occurs, a connection is recycled, or a connection
  261. fails. Similarly, if a multiple of ``len(server_list)`` is not chosen,
  262. those same servers would have a decreased load. By default, overflow
  263. is disabled.
  264. If `prefill` is set to ``True``, `pool_size` connections will be opened
  265. when the pool is created.
  266. Example Usage:
  267. .. code-block:: python
  268. >>> pool = pycassa.ConnectionPool(keyspace='Keyspace1', server_list=['10.0.0.4:9160', '10.0.0.5:9160'], prefill=False)
  269. >>> cf = pycassa.ColumnFamily(pool, 'Standard1')
  270. >>> cf.insert('key', {'col': 'val'})
  271. 1287785685530679
  272. """
  273. self._pool_threadlocal = use_threadlocal
  274. self.keyspace = keyspace
  275. self.credentials = credentials
  276. self.timeout = timeout
  277. self.socket_factory = socket_factory
  278. self.transport_factory = transport_factory
  279. if use_threadlocal:
  280. self._tlocal = threading.local()
  281. self._pool_size = pool_size
  282. self._q = Queue.Queue(pool_size)
  283. self._pool_lock = threading.Lock()
  284. self._current_conns = 0
  285. # Listener groups
  286. self.listeners = []
  287. self._on_connect = []
  288. self._on_checkout = []
  289. self._on_checkin = []
  290. self._on_dispose = []
  291. self._on_recycle = []
  292. self._on_failure = []
  293. self._on_server_list = []
  294. self._on_pool_dispose = []
  295. self._on_pool_max = []
  296. self.add_listener(PoolLogger())
  297. if "listeners" in kwargs:
  298. listeners = kwargs["listeners"]
  299. for l in listeners:
  300. self.add_listener(l)
  301. self.logging_name = kwargs.get("logging_name", None)
  302. if not self.logging_name:
  303. self.logging_name = id(self)
  304. if "max_overflow" not in kwargs:
  305. self._set_max_overflow(0)
  306. recognized_kwargs = ["pool_timeout", "recycle", "max_retries", "max_overflow"]
  307. for kw in recognized_kwargs:
  308. if kw in kwargs:
  309. setattr(self, kw, kwargs[kw])
  310. self.set_server_list(server_list)
  311. self._prefill = prefill
  312. if self._prefill:
  313. self.fill()
  314. def set_server_list(self, server_list):
  315. """
  316. Sets the server list that the pool will make connections to.
  317. `server_list` should be sequence of servers in the form ``"host:port"`` that
  318. the pool will connect to. The list will be randomly permuted before
  319. being used. `server_list` may also be a function that returns the
  320. sequence of servers.
  321. """
  322. if callable(server_list):
  323. self.server_list = list(server_list())
  324. else:
  325. self.server_list = list(server_list)
  326. random.shuffle(self.server_list)
  327. self._list_position = 0
  328. self._notify_on_server_list(self.server_list)
  329. def _get_next_server(self):
  330. """
  331. Gets the next 'localhost:port' combination from the list of
  332. servers and increments the position. This is not thread-safe,
  333. but client-side load-balancing isn't so important that this is
  334. a problem.
  335. """
  336. if self._list_position >= len(self.server_list):
  337. self._list_position = 0
  338. server = self.server_list[self._list_position]
  339. self._list_position += 1
  340. return server
  341. def _create_connection(self):
  342. """Creates a ConnectionWrapper, which opens a
  343. pycassa.connection.Connection."""
  344. if not self.server_list:
  345. raise AllServersUnavailable('Cannot connect to any servers as server list is empty!')
  346. failure_count = 0
  347. while failure_count < 2 * len(self.server_list):
  348. try:
  349. server = self._get_next_server()
  350. wrapper = self._get_new_wrapper(server)
  351. return wrapper
  352. except (TTransportException, socket.error, IOError, EOFError), exc:
  353. self._notify_on_failure(exc, server)
  354. failure_count += 1
  355. raise AllServersUnavailable('An attempt was made to connect to each of the servers ' +
  356. 'twice, but none of the attempts succeeded. The last failure was %s: %s' %
  357. (exc.__class__.__name__, exc))
  358. def fill(self):
  359. """
  360. Adds connections to the pool until at least ``pool_size`` connections
  361. exist, whether they are currently checked out from the pool or not.
  362. .. versionadded:: 1.2.0
  363. """
  364. with self._pool_lock:
  365. while self._current_conns < self._pool_size:
  366. conn = self._create_connection()
  367. conn._checkin()
  368. self._q.put(conn, False)
  369. self._current_conns += 1
  370. def _get_new_wrapper(self, server):
  371. return ConnectionWrapper(self, self.max_retries,
  372. self.keyspace, server,
  373. timeout=self.timeout,
  374. credentials=self.credentials,
  375. socket_factory=self.socket_factory,
  376. transport_factory=self.transport_factory)
  377. def _replace_wrapper(self):
  378. """Try to replace the connection."""
  379. if not self._q.full():
  380. conn = self._create_connection()
  381. conn._checkin()
  382. try:
  383. self._q.put(conn, False)
  384. except Queue.Full:
  385. conn._dispose_wrapper(reason="pool is already full")
  386. else:
  387. with self._pool_lock:
  388. self._current_conns += 1
  389. def _clear_current(self):
  390. """ If using threadlocal, clear our threadlocal current conn. """
  391. if self._pool_threadlocal:
  392. self._tlocal.current = None
  393. def put(self, conn):
  394. """ Returns a connection to the pool. """
  395. if not conn.transport.isOpen():
  396. return
  397. if self._pool_threadlocal:
  398. if hasattr(self._tlocal, 'current') and self._tlocal.current:
  399. conn = self._tlocal.current
  400. self._tlocal.current = None
  401. else:
  402. conn = None
  403. if conn:
  404. conn._retry_count = 0
  405. if conn._is_in_queue_or_disposed():
  406. raise InvalidRequestError("Connection was already checked in or disposed")
  407. if self.recycle > -1 and conn.operation_count > self.recycle:
  408. new_conn = self._create_connection()
  409. self._notify_on_recycle(conn, new_conn)
  410. conn._dispose_wrapper(reason="recyling connection")
  411. conn = new_conn
  412. conn._checkin()
  413. self._notify_on_checkin(conn)
  414. try:
  415. self._q.put_nowait(conn)
  416. except Queue.Full:
  417. conn._dispose_wrapper(reason="pool is already full")
  418. self._decrement_overflow()
  419. return_conn = put
  420. def _decrement_overflow(self):
  421. with self._pool_lock:
  422. self._current_conns -= 1
  423. def _new_if_required(self, max_conns, check_empty_queue=False):
  424. """ Creates new connection if there is room """
  425. with self._pool_lock:
  426. if (not check_empty_queue or self._q.empty()) and self._current_conns < max_conns:
  427. new_conn = True
  428. self._current_conns += 1
  429. else:
  430. new_conn = False
  431. if new_conn:
  432. try:
  433. return self._create_connection()
  434. except:
  435. with self._pool_lock:
  436. self._current_conns -= 1
  437. raise
  438. return None
  439. def get(self):
  440. """ Gets a connection from the pool. """
  441. conn = None
  442. if self._pool_threadlocal:
  443. try:
  444. if self._tlocal.current:
  445. conn = self._tlocal.current
  446. if conn:
  447. return conn
  448. except AttributeError:
  449. pass
  450. conn = self._new_if_required(self._pool_size)
  451. if not conn:
  452. # if queue is empty and max_overflow is not reached, create new conn
  453. conn = self._new_if_required(self._max_conns, check_empty_queue=True)
  454. if not conn:
  455. # We will have to fetch from the queue, and maybe block
  456. timeout = self.pool_timeout
  457. if timeout == -1:
  458. timeout = None
  459. try:
  460. conn = self._q.get(timeout=timeout)
  461. except Queue.Empty:
  462. self._notify_on_pool_max(pool_max=self._max_conns)
  463. size_msg = "size %d" % (self._pool_size, )
  464. if self._overflow_enabled:
  465. size_msg += "overflow %d" % (self._max_overflow)
  466. message = "ConnectionPool limit of %s reached, unable to obtain connection after %d seconds" \
  467. % (size_msg, self.pool_timeout)
  468. raise NoConnectionAvailable(message)
  469. else:
  470. conn._checkout()
  471. if self._pool_threadlocal:
  472. self._tlocal.current = conn
  473. self._notify_on_checkout(conn)
  474. return conn
  475. def execute(self, f, *args, **kwargs):
  476. """
  477. Get a connection from the pool, execute
  478. `f` on it with `*args` and `**kwargs`, return the
  479. connection to the pool, and return the result of `f`.
  480. """
  481. conn = None
  482. try:
  483. conn = self.get()
  484. return getattr(conn, f)(*args, **kwargs)
  485. finally:
  486. if conn:
  487. conn.return_to_pool()
  488. def dispose(self):
  489. """ Closes all checked in connections in the pool. """
  490. while True:
  491. try:
  492. conn = self._q.get(False)
  493. conn._dispose_wrapper(
  494. reason="Pool %s is being disposed" % id(self))
  495. self._decrement_overflow()
  496. except Queue.Empty:
  497. break
  498. self._notify_on_pool_dispose()
  499. def size(self):
  500. """ Returns the capacity of the pool. """
  501. return self._pool_size
  502. def checkedin(self):
  503. """ Returns the number of connections currently in the pool. """
  504. return self._q.qsize()
  505. def overflow(self):
  506. """ Returns the number of overflow connections that are currently open. """
  507. return max(self._current_conns - self._pool_size, 0)
  508. def checkedout(self):
  509. """ Returns the number of connections currently checked out from the pool. """
  510. return self._current_conns - self.checkedin()
  511. def add_listener(self, listener):
  512. """
  513. Add a :class:`PoolListener`-like object to this pool.
  514. `listener` may be an object that implements some or all of
  515. :class:`PoolListener`, or a dictionary of callables containing implementations
  516. of some or all of the named methods in :class:`PoolListener`.
  517. """
  518. listener = as_interface(listener,
  519. methods=('connection_created', 'connection_checked_out',
  520. 'connection_checked_in', 'connection_disposed',
  521. 'connection_recycled', 'connection_failed',
  522. 'obtained_server_list', 'pool_disposed',
  523. 'pool_at_max'))
  524. self.listeners.append(listener)
  525. if hasattr(listener, 'connection_created'):
  526. self._on_connect.append(listener)
  527. if hasattr(listener, 'connection_checked_out'):
  528. self._on_checkout.append(listener)
  529. if hasattr(listener, 'connection_checked_in'):
  530. self._on_checkin.append(listener)
  531. if hasattr(listener, 'connection_disposed'):
  532. self._on_dispose.append(listener)
  533. if hasattr(listener, 'connection_recycled'):
  534. self._on_recycle.append(listener)
  535. if hasattr(listener, 'connection_failed'):
  536. self._on_failure.append(listener)
  537. if hasattr(listener, 'obtained_server_list'):
  538. self._on_server_list.append(listener)
  539. if hasattr(listener, 'pool_disposed'):
  540. self._on_pool_dispose.append(listener)
  541. if hasattr(listener, 'pool_at_max'):
  542. self._on_pool_max.append(listener)
  543. def _notify_on_pool_dispose(self):
  544. if self._on_pool_dispose:
  545. dic = {'pool_id': self.logging_name,
  546. 'level': 'info'}
  547. for l in self._on_pool_dispose:
  548. l.pool_disposed(dic)
  549. def _notify_on_pool_max(self, pool_max):
  550. if self._on_pool_max:
  551. dic = {'pool_id': self.logging_name,
  552. 'level': 'info',
  553. 'pool_max': pool_max}
  554. for l in self._on_pool_max:
  555. l.pool_at_max(dic)
  556. def _notify_on_dispose(self, conn_record, msg=""):
  557. if self._on_dispose:
  558. dic = {'pool_id': self.logging_name,
  559. 'level': 'debug',
  560. 'connection': conn_record}
  561. if msg:
  562. dic['message'] = msg
  563. for l in self._on_dispose:
  564. l.connection_disposed(dic)
  565. def _notify_on_server_list(self, server_list):
  566. dic = {'pool_id': self.logging_name,
  567. 'level': 'debug',
  568. 'server_list': server_list}
  569. if self._on_server_list:
  570. for l in self._on_server_list:
  571. l.obtained_server_list(dic)
  572. def _notify_on_recycle(self, old_conn, new_conn):
  573. if self._on_recycle:
  574. dic = {'pool_id': self.logging_name,
  575. 'level': 'debug',
  576. 'old_conn': old_conn,
  577. 'new_conn': new_conn}
  578. for l in self._on_recycle:
  579. l.connection_recycled(dic)
  580. def _notify_on_connect(self, conn_record, msg="", error=None):
  581. if self._on_connect:
  582. dic = {'pool_id': self.logging_name,
  583. 'level': 'debug',
  584. 'connection': conn_record}
  585. if msg:
  586. dic['message'] = msg
  587. if error:
  588. dic['error'] = error
  589. dic['level'] = 'warn'
  590. for l in self._on_connect:
  591. l.connection_created(dic)
  592. def _notify_on_checkin(self, conn_record):
  593. if self._on_checkin:
  594. dic = {'pool_id': self.logging_name,
  595. 'level': 'debug',
  596. 'connection': conn_record}
  597. for l in self._on_checkin:
  598. l.connection_checked_in(dic)
  599. def _notify_on_checkout(self, conn_record):
  600. if self._on_checkout:
  601. dic = {'pool_id': self.logging_name,
  602. 'level': 'debug',
  603. 'connection': conn_record}
  604. for l in self._on_checkout:
  605. l.connection_checked_out(dic)
  606. def _notify_on_failure(self, error, server, connection=None):
  607. if self._on_failure:
  608. dic = {'pool_id': self.logging_name,
  609. 'level': 'info',
  610. 'error': error,
  611. 'server': server,
  612. 'connection': connection}
  613. for l in self._on_failure:
  614. l.connection_failed(dic)
  615. QueuePool = ConnectionPool
  616. class PoolListener(object):
  617. """Hooks into the lifecycle of connections in a :class:`ConnectionPool`.
  618. Usage::
  619. class MyListener(PoolListener):
  620. def connection_created(self, dic):
  621. '''perform connect operations'''
  622. # etc.
  623. # create a new pool with a listener
  624. p = ConnectionPool(..., listeners=[MyListener()])
  625. # or add a listener after the fact
  626. p.add_listener(MyListener())
  627. Listeners receive a dictionary that contains event information and
  628. is indexed by a string describing that piece of info. For example,
  629. all event dictionaries include 'level', so dic['level'] will return
  630. the prescribed logging level.
  631. There is no need to subclass :class:`PoolListener` to handle events.
  632. Any class that implements one or more of these methods can be used
  633. as a pool listener. The :class:`ConnectionPool` will inspect the methods
  634. provided by a listener object and add the listener to one or more
  635. internal event queues based on its capabilities. In terms of
  636. efficiency and function call overhead, you're much better off only
  637. providing implementations for the hooks you'll be using.
  638. Each of the :class:`PoolListener` methods wil be called with a
  639. :class:`dict` as the single parameter. This :class:`dict` may
  640. contain the following fields:
  641. * `connection`: The :class:`ConnectionWrapper` object that persistently
  642. manages the connection
  643. * `message`: The reason this event happened
  644. * `error`: The :class:`Exception` that caused this event
  645. * `pool_id`: The id of the :class:`ConnectionPool` that this event came from
  646. * `level`: The prescribed logging level for this event. Can be 'debug', 'info',
  647. 'warn', 'error', or 'critical'
  648. Entries in the :class:`dict` that are specific to only one event type are
  649. detailed with each method.
  650. """
  651. def connection_created(self, dic):
  652. """Called once for each new Cassandra connection.
  653. Fields: `pool_id`, `level`, and `connection`.
  654. """
  655. def connection_checked_out(self, dic):
  656. """Called when a connection is retrieved from the Pool.
  657. Fields: `pool_id`, `level`, and `connection`.
  658. """
  659. def connection_checked_in(self, dic):
  660. """Called when a connection returns to the pool.
  661. Fields: `pool_id`, `level`, and `connection`.
  662. """
  663. def connection_disposed(self, dic):
  664. """Called when a connection is closed.
  665. ``dic['message']``: A reason for closing the connection, if any.
  666. Fields: `pool_id`, `level`, `connection`, and `message`.
  667. """
  668. def connection_recycled(self, dic):
  669. """Called when a connection is recycled.
  670. ``dic['old_conn']``: The :class:`ConnectionWrapper` that is being recycled
  671. ``dic['new_conn']``: The :class:`ConnectionWrapper` that is replacing it
  672. Fields: `pool_id`, `level`, `old_conn`, and `new_conn`.
  673. """
  674. def connection_failed(self, dic):
  675. """Called when a connection to a single server fails.
  676. ``dic['server']``: The server the connection was made to.
  677. Fields: `pool_id`, `level`, `error`, `server`, and `connection`.
  678. """
  679. def server_list_obtained(self, dic):
  680. """Called when the pool finalizes its server list.
  681. ``dic['server_list']``: The randomly permuted list of servers that the
  682. pool will choose from.
  683. Fields: `pool_id`, `level`, and `server_list`.
  684. """
  685. def pool_disposed(self, dic):
  686. """Called when a pool is disposed.
  687. Fields: `pool_id`, and `level`.
  688. """
  689. def pool_at_max(self, dic):
  690. """
  691. Called when an attempt is made to get a new connection from the
  692. pool, but the pool is already at its max size.
  693. ``dic['pool_max']``: The max number of connections the pool will
  694. keep open at one time.
  695. Fields: `pool_id`, `pool_max`, and `level`.
  696. """
  697. class AllServersUnavailable(Exception):
  698. """Raised when none of the servers given to a pool can be connected to."""
  699. class NoConnectionAvailable(Exception):
  700. """Raised when there are no connections left in a pool."""
  701. class MaximumRetryException(Exception):
  702. """
  703. Raised when a :class:`ConnectionWrapper` has retried the maximum
  704. allowed times before being returned to the pool; note that all of
  705. the retries do not have to be on the same operation.
  706. """
  707. class InvalidRequestError(Exception):
  708. """
  709. Pycassa was asked to do something it can't do.
  710. This error generally corresponds to runtime state errors.
  711. """