base.py 39 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121
  1. # sqlalchemy/pool.py
  2. # Copyright (C) 2005-2022 the SQLAlchemy authors and contributors
  3. # <see AUTHORS file>
  4. #
  5. # This module is part of SQLAlchemy and is released under
  6. # the MIT License: https://www.opensource.org/licenses/mit-license.php
  7. """Base constructs for connection pools.
  8. """
  9. from collections import deque
  10. import time
  11. import weakref
  12. from .. import event
  13. from .. import exc
  14. from .. import log
  15. from .. import util
  16. reset_rollback = util.symbol("reset_rollback")
  17. reset_commit = util.symbol("reset_commit")
  18. reset_none = util.symbol("reset_none")
  19. class _ConnDialect(object):
  20. """partial implementation of :class:`.Dialect`
  21. which provides DBAPI connection methods.
  22. When a :class:`_pool.Pool` is combined with an :class:`_engine.Engine`,
  23. the :class:`_engine.Engine` replaces this with its own
  24. :class:`.Dialect`.
  25. """
  26. is_async = False
  27. def do_rollback(self, dbapi_connection):
  28. dbapi_connection.rollback()
  29. def do_commit(self, dbapi_connection):
  30. dbapi_connection.commit()
  31. def do_close(self, dbapi_connection):
  32. dbapi_connection.close()
  33. def do_ping(self, dbapi_connection):
  34. raise NotImplementedError(
  35. "The ping feature requires that a dialect is "
  36. "passed to the connection pool."
  37. )
  38. def get_driver_connection(self, connection):
  39. return connection
  40. class _AsyncConnDialect(_ConnDialect):
  41. is_async = True
  42. class Pool(log.Identified):
  43. """Abstract base class for connection pools."""
  44. _dialect = _ConnDialect()
  45. def __init__(
  46. self,
  47. creator,
  48. recycle=-1,
  49. echo=None,
  50. logging_name=None,
  51. reset_on_return=True,
  52. events=None,
  53. dialect=None,
  54. pre_ping=False,
  55. _dispatch=None,
  56. ):
  57. """
  58. Construct a Pool.
  59. :param creator: a callable function that returns a DB-API
  60. connection object. The function will be called with
  61. parameters.
  62. :param recycle: If set to a value other than -1, number of
  63. seconds between connection recycling, which means upon
  64. checkout, if this timeout is surpassed the connection will be
  65. closed and replaced with a newly opened connection. Defaults to -1.
  66. :param logging_name: String identifier which will be used within
  67. the "name" field of logging records generated within the
  68. "sqlalchemy.pool" logger. Defaults to a hexstring of the object's
  69. id.
  70. :param echo: if True, the connection pool will log
  71. informational output such as when connections are invalidated
  72. as well as when connections are recycled to the default log handler,
  73. which defaults to ``sys.stdout`` for output.. If set to the string
  74. ``"debug"``, the logging will include pool checkouts and checkins.
  75. The :paramref:`_pool.Pool.echo` parameter can also be set from the
  76. :func:`_sa.create_engine` call by using the
  77. :paramref:`_sa.create_engine.echo_pool` parameter.
  78. .. seealso::
  79. :ref:`dbengine_logging` - further detail on how to configure
  80. logging.
  81. :param reset_on_return: Determine steps to take on
  82. connections as they are returned to the pool, which were
  83. not otherwise handled by a :class:`_engine.Connection`.
  84. reset_on_return can have any of these values:
  85. * ``"rollback"`` - call rollback() on the connection,
  86. to release locks and transaction resources.
  87. This is the default value. The vast majority
  88. of use cases should leave this value set.
  89. * ``True`` - same as 'rollback', this is here for
  90. backwards compatibility.
  91. * ``"commit"`` - call commit() on the connection,
  92. to release locks and transaction resources.
  93. A commit here may be desirable for databases that
  94. cache query plans if a commit is emitted,
  95. such as Microsoft SQL Server. However, this
  96. value is more dangerous than 'rollback' because
  97. any data changes present on the transaction
  98. are committed unconditionally.
  99. * ``None`` - don't do anything on the connection.
  100. This setting is only appropriate if the database / DBAPI
  101. works in pure "autocommit" mode at all times, or if the
  102. application uses the :class:`_engine.Engine` with consistent
  103. connectivity patterns. See the section
  104. :ref:`pool_reset_on_return` for more details.
  105. * ``False`` - same as None, this is here for
  106. backwards compatibility.
  107. .. seealso::
  108. :ref:`pool_reset_on_return`
  109. :param events: a list of 2-tuples, each of the form
  110. ``(callable, target)`` which will be passed to :func:`.event.listen`
  111. upon construction. Provided here so that event listeners
  112. can be assigned via :func:`_sa.create_engine` before dialect-level
  113. listeners are applied.
  114. :param dialect: a :class:`.Dialect` that will handle the job
  115. of calling rollback(), close(), or commit() on DBAPI connections.
  116. If omitted, a built-in "stub" dialect is used. Applications that
  117. make use of :func:`_sa.create_engine` should not use this parameter
  118. as it is handled by the engine creation strategy.
  119. .. versionadded:: 1.1 - ``dialect`` is now a public parameter
  120. to the :class:`_pool.Pool`.
  121. :param pre_ping: if True, the pool will emit a "ping" (typically
  122. "SELECT 1", but is dialect-specific) on the connection
  123. upon checkout, to test if the connection is alive or not. If not,
  124. the connection is transparently re-connected and upon success, all
  125. other pooled connections established prior to that timestamp are
  126. invalidated. Requires that a dialect is passed as well to
  127. interpret the disconnection error.
  128. .. versionadded:: 1.2
  129. """
  130. if logging_name:
  131. self.logging_name = self._orig_logging_name = logging_name
  132. else:
  133. self._orig_logging_name = None
  134. log.instance_logger(self, echoflag=echo)
  135. self._creator = creator
  136. self._recycle = recycle
  137. self._invalidate_time = 0
  138. self._pre_ping = pre_ping
  139. self._reset_on_return = util.symbol.parse_user_argument(
  140. reset_on_return,
  141. {
  142. reset_rollback: ["rollback", True],
  143. reset_none: ["none", None, False],
  144. reset_commit: ["commit"],
  145. },
  146. "reset_on_return",
  147. resolve_symbol_names=False,
  148. )
  149. self.echo = echo
  150. if _dispatch:
  151. self.dispatch._update(_dispatch, only_propagate=False)
  152. if dialect:
  153. self._dialect = dialect
  154. if events:
  155. for fn, target in events:
  156. event.listen(self, target, fn)
  157. @util.hybridproperty
  158. def _is_asyncio(self):
  159. return self._dialect.is_async
  160. @property
  161. def _creator(self):
  162. return self.__dict__["_creator"]
  163. @_creator.setter
  164. def _creator(self, creator):
  165. self.__dict__["_creator"] = creator
  166. self._invoke_creator = self._should_wrap_creator(creator)
  167. def _should_wrap_creator(self, creator):
  168. """Detect if creator accepts a single argument, or is sent
  169. as a legacy style no-arg function.
  170. """
  171. try:
  172. argspec = util.get_callable_argspec(self._creator, no_self=True)
  173. except TypeError:
  174. return lambda crec: creator()
  175. defaulted = argspec[3] is not None and len(argspec[3]) or 0
  176. positionals = len(argspec[0]) - defaulted
  177. # look for the exact arg signature that DefaultStrategy
  178. # sends us
  179. if (argspec[0], argspec[3]) == (["connection_record"], (None,)):
  180. return creator
  181. # or just a single positional
  182. elif positionals == 1:
  183. return creator
  184. # all other cases, just wrap and assume legacy "creator" callable
  185. # thing
  186. else:
  187. return lambda crec: creator()
  188. def _close_connection(self, connection):
  189. self.logger.debug("Closing connection %r", connection)
  190. try:
  191. self._dialect.do_close(connection)
  192. except Exception:
  193. self.logger.error(
  194. "Exception closing connection %r", connection, exc_info=True
  195. )
  196. def _create_connection(self):
  197. """Called by subclasses to create a new ConnectionRecord."""
  198. return _ConnectionRecord(self)
  199. def _invalidate(self, connection, exception=None, _checkin=True):
  200. """Mark all connections established within the generation
  201. of the given connection as invalidated.
  202. If this pool's last invalidate time is before when the given
  203. connection was created, update the timestamp til now. Otherwise,
  204. no action is performed.
  205. Connections with a start time prior to this pool's invalidation
  206. time will be recycled upon next checkout.
  207. """
  208. rec = getattr(connection, "_connection_record", None)
  209. if not rec or self._invalidate_time < rec.starttime:
  210. self._invalidate_time = time.time()
  211. if _checkin and getattr(connection, "is_valid", False):
  212. connection.invalidate(exception)
  213. def recreate(self):
  214. """Return a new :class:`_pool.Pool`, of the same class as this one
  215. and configured with identical creation arguments.
  216. This method is used in conjunction with :meth:`dispose`
  217. to close out an entire :class:`_pool.Pool` and create a new one in
  218. its place.
  219. """
  220. raise NotImplementedError()
  221. def dispose(self):
  222. """Dispose of this pool.
  223. This method leaves the possibility of checked-out connections
  224. remaining open, as it only affects connections that are
  225. idle in the pool.
  226. .. seealso::
  227. :meth:`Pool.recreate`
  228. """
  229. raise NotImplementedError()
  230. def connect(self):
  231. """Return a DBAPI connection from the pool.
  232. The connection is instrumented such that when its
  233. ``close()`` method is called, the connection will be returned to
  234. the pool.
  235. """
  236. return _ConnectionFairy._checkout(self)
  237. def _return_conn(self, record):
  238. """Given a _ConnectionRecord, return it to the :class:`_pool.Pool`.
  239. This method is called when an instrumented DBAPI connection
  240. has its ``close()`` method called.
  241. """
  242. self._do_return_conn(record)
  243. def _do_get(self):
  244. """Implementation for :meth:`get`, supplied by subclasses."""
  245. raise NotImplementedError()
  246. def _do_return_conn(self, conn):
  247. """Implementation for :meth:`return_conn`, supplied by subclasses."""
  248. raise NotImplementedError()
  249. def status(self):
  250. raise NotImplementedError()
  251. class _ConnectionRecord(object):
  252. """Internal object which maintains an individual DBAPI connection
  253. referenced by a :class:`_pool.Pool`.
  254. The :class:`._ConnectionRecord` object always exists for any particular
  255. DBAPI connection whether or not that DBAPI connection has been
  256. "checked out". This is in contrast to the :class:`._ConnectionFairy`
  257. which is only a public facade to the DBAPI connection while it is checked
  258. out.
  259. A :class:`._ConnectionRecord` may exist for a span longer than that
  260. of a single DBAPI connection. For example, if the
  261. :meth:`._ConnectionRecord.invalidate`
  262. method is called, the DBAPI connection associated with this
  263. :class:`._ConnectionRecord`
  264. will be discarded, but the :class:`._ConnectionRecord` may be used again,
  265. in which case a new DBAPI connection is produced when the
  266. :class:`_pool.Pool`
  267. next uses this record.
  268. The :class:`._ConnectionRecord` is delivered along with connection
  269. pool events, including :meth:`_events.PoolEvents.connect` and
  270. :meth:`_events.PoolEvents.checkout`, however :class:`._ConnectionRecord`
  271. still
  272. remains an internal object whose API and internals may change.
  273. .. seealso::
  274. :class:`._ConnectionFairy`
  275. """
  276. def __init__(self, pool, connect=True):
  277. self.__pool = pool
  278. if connect:
  279. self.__connect()
  280. self.finalize_callback = deque()
  281. fresh = False
  282. fairy_ref = None
  283. starttime = None
  284. dbapi_connection = None
  285. """A reference to the actual DBAPI connection being tracked.
  286. May be ``None`` if this :class:`._ConnectionRecord` has been marked
  287. as invalidated; a new DBAPI connection may replace it if the owning
  288. pool calls upon this :class:`._ConnectionRecord` to reconnect.
  289. For adapted drivers, like the Asyncio implementations, this is a
  290. :class:`.AdaptedConnection` that adapts the driver connection
  291. to the DBAPI protocol.
  292. Use :attr:`._ConnectionRecord.driver_connection` to obtain the
  293. connection objected returned by the driver.
  294. .. versionadded:: 1.4.24
  295. """
  296. @property
  297. def driver_connection(self):
  298. """The connection object as returned by the driver after a connect.
  299. For normal sync drivers that support the DBAPI protocol, this object
  300. is the same as the one referenced by
  301. :attr:`._ConnectionRecord.dbapi_connection`.
  302. For adapted drivers, like the Asyncio ones, this is the actual object
  303. that was returned by the driver ``connect`` call.
  304. As :attr:`._ConnectionRecord.dbapi_connection` it may be ``None``
  305. if this :class:`._ConnectionRecord` has been marked as invalidated.
  306. .. versionadded:: 1.4.24
  307. """
  308. if self.dbapi_connection is None:
  309. return None
  310. else:
  311. return self.__pool._dialect.get_driver_connection(
  312. self.dbapi_connection
  313. )
  314. @property
  315. def connection(self):
  316. """An alias to :attr:`._ConnectionRecord.dbapi_connection`.
  317. This alias is deprecated, please use the new name.
  318. .. deprecated:: 1.4.24
  319. """
  320. return self.dbapi_connection
  321. @connection.setter
  322. def connection(self, value):
  323. self.dbapi_connection = value
  324. _soft_invalidate_time = 0
  325. @util.memoized_property
  326. def info(self):
  327. """The ``.info`` dictionary associated with the DBAPI connection.
  328. This dictionary is shared among the :attr:`._ConnectionFairy.info`
  329. and :attr:`_engine.Connection.info` accessors.
  330. .. note::
  331. The lifespan of this dictionary is linked to the
  332. DBAPI connection itself, meaning that it is **discarded** each time
  333. the DBAPI connection is closed and/or invalidated. The
  334. :attr:`._ConnectionRecord.record_info` dictionary remains
  335. persistent throughout the lifespan of the
  336. :class:`._ConnectionRecord` container.
  337. """
  338. return {}
  339. @util.memoized_property
  340. def record_info(self):
  341. """An "info' dictionary associated with the connection record
  342. itself.
  343. Unlike the :attr:`._ConnectionRecord.info` dictionary, which is linked
  344. to the lifespan of the DBAPI connection, this dictionary is linked
  345. to the lifespan of the :class:`._ConnectionRecord` container itself
  346. and will remain persistent throughout the life of the
  347. :class:`._ConnectionRecord`.
  348. .. versionadded:: 1.1
  349. """
  350. return {}
  351. @classmethod
  352. def checkout(cls, pool):
  353. rec = pool._do_get()
  354. try:
  355. dbapi_connection = rec.get_connection()
  356. except Exception as err:
  357. with util.safe_reraise():
  358. rec._checkin_failed(err, _fairy_was_created=False)
  359. echo = pool._should_log_debug()
  360. fairy = _ConnectionFairy(dbapi_connection, rec, echo)
  361. rec.fairy_ref = ref = weakref.ref(
  362. fairy,
  363. lambda ref: _finalize_fairy
  364. and _finalize_fairy(None, rec, pool, ref, echo, True),
  365. )
  366. _strong_ref_connection_records[ref] = rec
  367. if echo:
  368. pool.logger.debug(
  369. "Connection %r checked out from pool", dbapi_connection
  370. )
  371. return fairy
  372. def _checkin_failed(self, err, _fairy_was_created=True):
  373. self.invalidate(e=err)
  374. self.checkin(
  375. _fairy_was_created=_fairy_was_created,
  376. )
  377. def checkin(self, _fairy_was_created=True):
  378. if self.fairy_ref is None and _fairy_was_created:
  379. # _fairy_was_created is False for the initial get connection phase;
  380. # meaning there was no _ConnectionFairy and we must unconditionally
  381. # do a checkin.
  382. #
  383. # otherwise, if fairy_was_created==True, if fairy_ref is None here
  384. # that means we were checked in already, so this looks like
  385. # a double checkin.
  386. util.warn("Double checkin attempted on %s" % self)
  387. return
  388. self.fairy_ref = None
  389. connection = self.dbapi_connection
  390. pool = self.__pool
  391. while self.finalize_callback:
  392. finalizer = self.finalize_callback.pop()
  393. finalizer(connection)
  394. if pool.dispatch.checkin:
  395. pool.dispatch.checkin(connection, self)
  396. pool._return_conn(self)
  397. @property
  398. def in_use(self):
  399. return self.fairy_ref is not None
  400. @property
  401. def last_connect_time(self):
  402. return self.starttime
  403. def close(self):
  404. if self.dbapi_connection is not None:
  405. self.__close()
  406. def invalidate(self, e=None, soft=False):
  407. """Invalidate the DBAPI connection held by this
  408. :class:`._ConnectionRecord`.
  409. This method is called for all connection invalidations, including
  410. when the :meth:`._ConnectionFairy.invalidate` or
  411. :meth:`_engine.Connection.invalidate` methods are called,
  412. as well as when any
  413. so-called "automatic invalidation" condition occurs.
  414. :param e: an exception object indicating a reason for the
  415. invalidation.
  416. :param soft: if True, the connection isn't closed; instead, this
  417. connection will be recycled on next checkout.
  418. .. versionadded:: 1.0.3
  419. .. seealso::
  420. :ref:`pool_connection_invalidation`
  421. """
  422. # already invalidated
  423. if self.dbapi_connection is None:
  424. return
  425. if soft:
  426. self.__pool.dispatch.soft_invalidate(
  427. self.dbapi_connection, self, e
  428. )
  429. else:
  430. self.__pool.dispatch.invalidate(self.dbapi_connection, self, e)
  431. if e is not None:
  432. self.__pool.logger.info(
  433. "%sInvalidate connection %r (reason: %s:%s)",
  434. "Soft " if soft else "",
  435. self.dbapi_connection,
  436. e.__class__.__name__,
  437. e,
  438. )
  439. else:
  440. self.__pool.logger.info(
  441. "%sInvalidate connection %r",
  442. "Soft " if soft else "",
  443. self.dbapi_connection,
  444. )
  445. if soft:
  446. self._soft_invalidate_time = time.time()
  447. else:
  448. self.__close()
  449. self.dbapi_connection = None
  450. def get_connection(self):
  451. recycle = False
  452. # NOTE: the various comparisons here are assuming that measurable time
  453. # passes between these state changes. however, time.time() is not
  454. # guaranteed to have sub-second precision. comparisons of
  455. # "invalidation time" to "starttime" should perhaps use >= so that the
  456. # state change can take place assuming no measurable time has passed,
  457. # however this does not guarantee correct behavior here as if time
  458. # continues to not pass, it will try to reconnect repeatedly until
  459. # these timestamps diverge, so in that sense using > is safer. Per
  460. # https://stackoverflow.com/a/1938096/34549, Windows time.time() may be
  461. # within 16 milliseconds accuracy, so unit tests for connection
  462. # invalidation need a sleep of at least this long between initial start
  463. # time and invalidation for the logic below to work reliably.
  464. if self.dbapi_connection is None:
  465. self.info.clear()
  466. self.__connect()
  467. elif (
  468. self.__pool._recycle > -1
  469. and time.time() - self.starttime > self.__pool._recycle
  470. ):
  471. self.__pool.logger.info(
  472. "Connection %r exceeded timeout; recycling",
  473. self.dbapi_connection,
  474. )
  475. recycle = True
  476. elif self.__pool._invalidate_time > self.starttime:
  477. self.__pool.logger.info(
  478. "Connection %r invalidated due to pool invalidation; "
  479. + "recycling",
  480. self.dbapi_connection,
  481. )
  482. recycle = True
  483. elif self._soft_invalidate_time > self.starttime:
  484. self.__pool.logger.info(
  485. "Connection %r invalidated due to local soft invalidation; "
  486. + "recycling",
  487. self.dbapi_connection,
  488. )
  489. recycle = True
  490. if recycle:
  491. self.__close()
  492. self.info.clear()
  493. self.__connect()
  494. return self.dbapi_connection
  495. def _is_hard_or_soft_invalidated(self):
  496. return (
  497. self.dbapi_connection is None
  498. or self.__pool._invalidate_time > self.starttime
  499. or (self._soft_invalidate_time > self.starttime)
  500. )
  501. def __close(self):
  502. self.finalize_callback.clear()
  503. if self.__pool.dispatch.close:
  504. self.__pool.dispatch.close(self.dbapi_connection, self)
  505. self.__pool._close_connection(self.dbapi_connection)
  506. self.dbapi_connection = None
  507. def __connect(self):
  508. pool = self.__pool
  509. # ensure any existing connection is removed, so that if
  510. # creator fails, this attribute stays None
  511. self.dbapi_connection = None
  512. try:
  513. self.starttime = time.time()
  514. self.dbapi_connection = connection = pool._invoke_creator(self)
  515. pool.logger.debug("Created new connection %r", connection)
  516. self.fresh = True
  517. except Exception as e:
  518. with util.safe_reraise():
  519. pool.logger.debug("Error on connect(): %s", e)
  520. else:
  521. # in SQLAlchemy 1.4 the first_connect event is not used by
  522. # the engine, so this will usually not be set
  523. if pool.dispatch.first_connect:
  524. pool.dispatch.first_connect.for_modify(
  525. pool.dispatch
  526. ).exec_once_unless_exception(self.dbapi_connection, self)
  527. # init of the dialect now takes place within the connect
  528. # event, so ensure a mutex is used on the first run
  529. pool.dispatch.connect.for_modify(
  530. pool.dispatch
  531. )._exec_w_sync_on_first_run(self.dbapi_connection, self)
  532. def _finalize_fairy(
  533. dbapi_connection,
  534. connection_record,
  535. pool,
  536. ref, # this is None when called directly, not by the gc
  537. echo,
  538. reset=True,
  539. fairy=None,
  540. ):
  541. """Cleanup for a :class:`._ConnectionFairy` whether or not it's already
  542. been garbage collected.
  543. When using an async dialect no IO can happen here (without using
  544. a dedicated thread), since this is called outside the greenlet
  545. context and with an already running loop. In this case function
  546. will only log a message and raise a warning.
  547. """
  548. if ref:
  549. _strong_ref_connection_records.pop(ref, None)
  550. elif fairy:
  551. _strong_ref_connection_records.pop(weakref.ref(fairy), None)
  552. if ref is not None:
  553. if connection_record.fairy_ref is not ref:
  554. return
  555. assert dbapi_connection is None
  556. dbapi_connection = connection_record.dbapi_connection
  557. # null pool is not _is_asyncio but can be used also with async dialects
  558. dont_restore_gced = pool._dialect.is_async
  559. if dont_restore_gced:
  560. detach = not connection_record or ref
  561. can_manipulate_connection = not ref
  562. else:
  563. detach = not connection_record
  564. can_manipulate_connection = True
  565. if dbapi_connection is not None:
  566. if connection_record and echo:
  567. pool.logger.debug(
  568. "Connection %r being returned to pool%s",
  569. dbapi_connection,
  570. ", transaction state was already reset by caller"
  571. if not reset
  572. else "",
  573. )
  574. try:
  575. fairy = fairy or _ConnectionFairy(
  576. dbapi_connection,
  577. connection_record,
  578. echo,
  579. )
  580. assert fairy.dbapi_connection is dbapi_connection
  581. if reset and can_manipulate_connection:
  582. fairy._reset(pool)
  583. if detach:
  584. if connection_record:
  585. fairy._pool = pool
  586. fairy.detach()
  587. if can_manipulate_connection:
  588. if pool.dispatch.close_detached:
  589. pool.dispatch.close_detached(dbapi_connection)
  590. pool._close_connection(dbapi_connection)
  591. else:
  592. message = (
  593. "The garbage collector is trying to clean up "
  594. "connection %r. This feature is unsupported on async "
  595. "dbapi, since no IO can be performed at this stage to "
  596. "reset the connection. Please close out all "
  597. "connections when they are no longer used, calling "
  598. "``close()`` or using a context manager to "
  599. "manage their lifetime."
  600. ) % dbapi_connection
  601. pool.logger.error(message)
  602. util.warn(message)
  603. except BaseException as e:
  604. pool.logger.error(
  605. "Exception during reset or similar", exc_info=True
  606. )
  607. if connection_record:
  608. connection_record.invalidate(e=e)
  609. if not isinstance(e, Exception):
  610. raise
  611. if connection_record and connection_record.fairy_ref is not None:
  612. connection_record.checkin()
  613. # a dictionary of the _ConnectionFairy weakrefs to _ConnectionRecord, so that
  614. # GC under pypy will call ConnectionFairy finalizers. linked directly to the
  615. # weakref that will empty itself when collected so that it should not create
  616. # any unmanaged memory references.
  617. _strong_ref_connection_records = {}
  618. class _ConnectionFairy(object):
  619. """Proxies a DBAPI connection and provides return-on-dereference
  620. support.
  621. This is an internal object used by the :class:`_pool.Pool` implementation
  622. to provide context management to a DBAPI connection delivered by
  623. that :class:`_pool.Pool`.
  624. The name "fairy" is inspired by the fact that the
  625. :class:`._ConnectionFairy` object's lifespan is transitory, as it lasts
  626. only for the length of a specific DBAPI connection being checked out from
  627. the pool, and additionally that as a transparent proxy, it is mostly
  628. invisible.
  629. .. seealso::
  630. :class:`._ConnectionRecord`
  631. """
  632. def __init__(self, dbapi_connection, connection_record, echo):
  633. self.dbapi_connection = dbapi_connection
  634. self._connection_record = connection_record
  635. self._echo = echo
  636. dbapi_connection = None
  637. """A reference to the actual DBAPI connection being tracked.
  638. .. versionadded:: 1.4.24
  639. .. seealso::
  640. :attr:`._ConnectionFairy.driver_connection`
  641. :attr:`._ConnectionRecord.dbapi_connection`
  642. :ref:`faq_dbapi_connection`
  643. """
  644. _connection_record = None
  645. """A reference to the :class:`._ConnectionRecord` object associated
  646. with the DBAPI connection.
  647. This is currently an internal accessor which is subject to change.
  648. """
  649. @property
  650. def driver_connection(self):
  651. """The connection object as returned by the driver after a connect.
  652. .. versionadded:: 1.4.24
  653. .. seealso::
  654. :attr:`._ConnectionFairy.dbapi_connection`
  655. :attr:`._ConnectionRecord.driver_connection`
  656. :ref:`faq_dbapi_connection`
  657. """
  658. return self._connection_record.driver_connection
  659. @property
  660. def connection(self):
  661. """An alias to :attr:`._ConnectionFairy.dbapi_connection`.
  662. This alias is deprecated, please use the new name.
  663. .. deprecated:: 1.4.24
  664. """
  665. return self.dbapi_connection
  666. @connection.setter
  667. def connection(self, value):
  668. self.dbapi_connection = value
  669. @classmethod
  670. def _checkout(cls, pool, threadconns=None, fairy=None):
  671. if not fairy:
  672. fairy = _ConnectionRecord.checkout(pool)
  673. fairy._pool = pool
  674. fairy._counter = 0
  675. if threadconns is not None:
  676. threadconns.current = weakref.ref(fairy)
  677. if fairy.dbapi_connection is None:
  678. raise exc.InvalidRequestError("This connection is closed")
  679. fairy._counter += 1
  680. if (
  681. not pool.dispatch.checkout and not pool._pre_ping
  682. ) or fairy._counter != 1:
  683. return fairy
  684. # Pool listeners can trigger a reconnection on checkout, as well
  685. # as the pre-pinger.
  686. # there are three attempts made here, but note that if the database
  687. # is not accessible from a connection standpoint, those won't proceed
  688. # here.
  689. attempts = 2
  690. while attempts > 0:
  691. connection_is_fresh = fairy._connection_record.fresh
  692. fairy._connection_record.fresh = False
  693. try:
  694. if pool._pre_ping:
  695. if not connection_is_fresh:
  696. if fairy._echo:
  697. pool.logger.debug(
  698. "Pool pre-ping on connection %s",
  699. fairy.dbapi_connection,
  700. )
  701. result = pool._dialect.do_ping(fairy.dbapi_connection)
  702. if not result:
  703. if fairy._echo:
  704. pool.logger.debug(
  705. "Pool pre-ping on connection %s failed, "
  706. "will invalidate pool",
  707. fairy.dbapi_connection,
  708. )
  709. raise exc.InvalidatePoolError()
  710. elif fairy._echo:
  711. pool.logger.debug(
  712. "Connection %s is fresh, skipping pre-ping",
  713. fairy.dbapi_connection,
  714. )
  715. pool.dispatch.checkout(
  716. fairy.dbapi_connection, fairy._connection_record, fairy
  717. )
  718. return fairy
  719. except exc.DisconnectionError as e:
  720. if e.invalidate_pool:
  721. pool.logger.info(
  722. "Disconnection detected on checkout, "
  723. "invalidating all pooled connections prior to "
  724. "current timestamp (reason: %r)",
  725. e,
  726. )
  727. fairy._connection_record.invalidate(e)
  728. pool._invalidate(fairy, e, _checkin=False)
  729. else:
  730. pool.logger.info(
  731. "Disconnection detected on checkout, "
  732. "invalidating individual connection %s (reason: %r)",
  733. fairy.dbapi_connection,
  734. e,
  735. )
  736. fairy._connection_record.invalidate(e)
  737. try:
  738. fairy.dbapi_connection = (
  739. fairy._connection_record.get_connection()
  740. )
  741. except Exception as err:
  742. with util.safe_reraise():
  743. fairy._connection_record._checkin_failed(
  744. err,
  745. _fairy_was_created=True,
  746. )
  747. # prevent _ConnectionFairy from being carried
  748. # in the stack trace. Do this after the
  749. # connection record has been checked in, so that
  750. # if the del triggers a finalize fairy, it won't
  751. # try to checkin a second time.
  752. del fairy
  753. attempts -= 1
  754. pool.logger.info("Reconnection attempts exhausted on checkout")
  755. fairy.invalidate()
  756. raise exc.InvalidRequestError("This connection is closed")
  757. def _checkout_existing(self):
  758. return _ConnectionFairy._checkout(self._pool, fairy=self)
  759. def _checkin(self, reset=True):
  760. _finalize_fairy(
  761. self.dbapi_connection,
  762. self._connection_record,
  763. self._pool,
  764. None,
  765. self._echo,
  766. reset=reset,
  767. fairy=self,
  768. )
  769. self.dbapi_connection = None
  770. self._connection_record = None
  771. _close = _checkin
  772. def _reset(self, pool):
  773. if pool.dispatch.reset:
  774. pool.dispatch.reset(self, self._connection_record)
  775. if pool._reset_on_return is reset_rollback:
  776. if self._echo:
  777. pool.logger.debug(
  778. "Connection %s rollback-on-return", self.dbapi_connection
  779. )
  780. pool._dialect.do_rollback(self)
  781. elif pool._reset_on_return is reset_commit:
  782. if self._echo:
  783. pool.logger.debug(
  784. "Connection %s commit-on-return",
  785. self.dbapi_connection,
  786. )
  787. pool._dialect.do_commit(self)
  788. @property
  789. def _logger(self):
  790. return self._pool.logger
  791. @property
  792. def is_valid(self):
  793. """Return True if this :class:`._ConnectionFairy` still refers
  794. to an active DBAPI connection."""
  795. return self.dbapi_connection is not None
  796. @util.memoized_property
  797. def info(self):
  798. """Info dictionary associated with the underlying DBAPI connection
  799. referred to by this :class:`.ConnectionFairy`, allowing user-defined
  800. data to be associated with the connection.
  801. The data here will follow along with the DBAPI connection including
  802. after it is returned to the connection pool and used again
  803. in subsequent instances of :class:`._ConnectionFairy`. It is shared
  804. with the :attr:`._ConnectionRecord.info` and
  805. :attr:`_engine.Connection.info`
  806. accessors.
  807. The dictionary associated with a particular DBAPI connection is
  808. discarded when the connection itself is discarded.
  809. """
  810. return self._connection_record.info
  811. @property
  812. def record_info(self):
  813. """Info dictionary associated with the :class:`._ConnectionRecord
  814. container referred to by this :class:`.ConnectionFairy`.
  815. Unlike the :attr:`._ConnectionFairy.info` dictionary, the lifespan
  816. of this dictionary is persistent across connections that are
  817. disconnected and/or invalidated within the lifespan of a
  818. :class:`._ConnectionRecord`.
  819. .. versionadded:: 1.1
  820. """
  821. if self._connection_record:
  822. return self._connection_record.record_info
  823. else:
  824. return None
  825. def invalidate(self, e=None, soft=False):
  826. """Mark this connection as invalidated.
  827. This method can be called directly, and is also called as a result
  828. of the :meth:`_engine.Connection.invalidate` method. When invoked,
  829. the DBAPI connection is immediately closed and discarded from
  830. further use by the pool. The invalidation mechanism proceeds
  831. via the :meth:`._ConnectionRecord.invalidate` internal method.
  832. :param e: an exception object indicating a reason for the invalidation.
  833. :param soft: if True, the connection isn't closed; instead, this
  834. connection will be recycled on next checkout.
  835. .. versionadded:: 1.0.3
  836. .. seealso::
  837. :ref:`pool_connection_invalidation`
  838. """
  839. if self.dbapi_connection is None:
  840. util.warn("Can't invalidate an already-closed connection.")
  841. return
  842. if self._connection_record:
  843. self._connection_record.invalidate(e=e, soft=soft)
  844. if not soft:
  845. self.dbapi_connection = None
  846. self._checkin()
  847. def cursor(self, *args, **kwargs):
  848. """Return a new DBAPI cursor for the underlying connection.
  849. This method is a proxy for the ``connection.cursor()`` DBAPI
  850. method.
  851. """
  852. return self.dbapi_connection.cursor(*args, **kwargs)
  853. def __getattr__(self, key):
  854. return getattr(self.dbapi_connection, key)
  855. def detach(self):
  856. """Separate this connection from its Pool.
  857. This means that the connection will no longer be returned to the
  858. pool when closed, and will instead be literally closed. The
  859. containing ConnectionRecord is separated from the DB-API connection,
  860. and will create a new connection when next used.
  861. Note that any overall connection limiting constraints imposed by a
  862. Pool implementation may be violated after a detach, as the detached
  863. connection is removed from the pool's knowledge and control.
  864. """
  865. if self._connection_record is not None:
  866. rec = self._connection_record
  867. rec.fairy_ref = None
  868. rec.dbapi_connection = None
  869. # TODO: should this be _return_conn?
  870. self._pool._do_return_conn(self._connection_record)
  871. self.info = self.info.copy()
  872. self._connection_record = None
  873. if self._pool.dispatch.detach:
  874. self._pool.dispatch.detach(self.dbapi_connection, rec)
  875. def close(self):
  876. self._counter -= 1
  877. if self._counter == 0:
  878. self._checkin()
  879. def _close_no_reset(self):
  880. self._counter -= 1
  881. if self._counter == 0:
  882. self._checkin(reset=False)