Files
MoFin/venv/lib/python3.12/site-packages/playhouse/pool.py
T
知微 fa45d8aa5f fix: 小果地址统一node122(兼容LAN+EasyTier)
- health_checklist.json: 192.168.1.122→node122
- ocr_client.py: docstring IP→node122
- docs/market-data-requirements.md: IP→node122
- 所有API调用通过ProxyHandler({})绕过系统代理
  Privoxy对node122:18003返回500,直连正常
2026-06-30 02:56:35 +08:00

288 lines
9.6 KiB
Python

import functools
import heapq
import logging
import threading
import time
from collections import namedtuple
from peewee import MySQLDatabase
from peewee import PostgresqlDatabase
from peewee import SqliteDatabase
logger = logging.getLogger('peewee.pool')
def make_int(val):
if val is not None and not isinstance(val, (int, float)):
return int(val)
return val
class MaxConnectionsExceeded(ValueError): pass
PoolConnection = namedtuple('PoolConnection', ('timestamp', 'connection',
'checked_out'))
class _sentinel(object):
def __lt__(self, other):
return True
def locked(fn):
@functools.wraps(fn)
def inner(self, *args, **kwargs):
with self._pool_lock:
return fn(self, *args, **kwargs)
return inner
class PooledDatabase(object):
def __init__(self, database, max_connections=20, stale_timeout=None,
timeout=None, **kwargs):
self._max_connections = make_int(max_connections)
self._stale_timeout = make_int(stale_timeout)
self._wait_timeout = make_int(timeout)
if self._wait_timeout == 0:
self._wait_timeout = float('inf')
# Lock for pool operations and condition for notifying when connection
# is released back to pool.
self._pool_lock = threading.RLock()
self._pool_available = threading.Condition(self._pool_lock)
# Available / idle connections stored in a heap, sorted oldest first.
self._connections = []
# Counter used for tie-breaker in heap (so we don't try comparing
# connection against connection).
self._heap_counter = 0
# Mapping of connection id to PoolConnection. Ordinarily we would want
# to use something like a WeakKeyDictionary, but Python typically won't
# allow us to create weak references to connection objects.
self._in_use = {}
# Use the memory address of the connection as the key in the event the
# connection object is not hashable. Connections will not get
# garbage-collected, however, because a reference to them will persist
# in "_in_use" as long as the conn has not been closed.
self.conn_key = id
super(PooledDatabase, self).__init__(database, **kwargs)
def init(self, database, max_connections=None, stale_timeout=None,
timeout=None, **connect_kwargs):
super(PooledDatabase, self).init(database, **connect_kwargs)
if max_connections is not None:
self._max_connections = make_int(max_connections)
if stale_timeout is not None:
self._stale_timeout = make_int(stale_timeout)
if timeout is not None:
self._wait_timeout = make_int(timeout)
if self._wait_timeout == 0:
self._wait_timeout = float('inf')
def connect(self, reuse_if_open=False):
if not self._wait_timeout:
return super(PooledDatabase, self).connect(reuse_if_open)
deadline = time.monotonic() + self._wait_timeout
while True:
try:
return super(PooledDatabase, self).connect(reuse_if_open)
except MaxConnectionsExceeded:
remaining = deadline - time.monotonic()
if remaining <= 0:
raise MaxConnectionsExceeded(
'Max connections exceeded, timed out attempting to '
'connect.')
with self._pool_available:
self._pool_available.wait(timeout=min(remaining, 1.0))
@locked
def _connect(self):
while self._connections:
try:
# Remove the oldest connection from the heap.
ts, _counter, conn = heapq.heappop(self._connections)
except IndexError:
break
key = self.conn_key(conn)
if self._is_closed(conn):
# Connection closed either by user or by driver - discard.
logger.debug('Connection %s was closed, discarding.', key)
continue
if self._stale_timeout and self._is_stale(ts):
logger.debug('Connection %s was stale, closing.', key)
self._close_raw(conn)
continue
# Connection OK to use.
self._in_use[key] = PoolConnection(ts, conn, time.time())
return conn
if self._max_connections and (
len(self._in_use) >= self._max_connections):
raise MaxConnectionsExceeded('Exceeded maximum connections.')
conn = super(PooledDatabase, self)._connect()
ts = time.time()
key = self.conn_key(conn)
logger.debug('Created new connection %s.', key)
self._in_use[key] = PoolConnection(ts, conn, time.time())
return conn
def _is_stale(self, timestamp):
# Called on check-out and check-in to ensure the connection has
# not outlived the stale timeout.
return (time.time() - timestamp) > self._stale_timeout
def _is_closed(self, conn):
return False
def _can_reuse(self, conn):
# Called on check-in to make sure the connection can be re-used.
return True
def _close_raw(self, conn):
try:
super(PooledDatabase, self)._close(conn)
except Exception:
logger.debug('Error closing connection %s.', self.conn_key(conn),
exc_info=True)
@locked
def _close(self, conn, close_conn=False):
# if close_conn == True, close underlying driver connection and remove
# from _in_use tracking. Do not return to available conns.
key = self.conn_key(conn)
if close_conn:
self._in_use.pop(key, None)
self._close_raw(conn)
return
if key not in self._in_use:
logger.debug('Connection %s not in use, ignoring close.', key)
return
pool_conn = self._in_use.pop(key)
if self._stale_timeout and self._is_stale(pool_conn.timestamp):
logger.debug('Closing stale connection %s on check-in.', key)
self._close_raw(conn)
elif not self._can_reuse(conn):
logger.debug('Connection %s not reusable, closing.', key)
self._close_raw(conn)
else:
logger.debug('Returning %s to pool.', key)
self._heap_counter += 1
heapq.heappush(self._connections,
(pool_conn.timestamp, self._heap_counter, conn))
# Wake up thread that may be waiting on connection.
self._pool_available.notify()
def manual_close(self):
"""
Close the underlying connection without returning it to the pool.
"""
if self.is_closed():
return False
# Obtain reference to the connection in-use by the calling thread.
conn = self.connection()
key = self.conn_key(conn)
# Remove from _in_use so that subsequent self.close() won't try to
# restore it to the pool. pool lock must be released before calling
# self.close(), since close acquires the database lock.
with self._pool_lock:
self._in_use.pop(key, None)
self.close()
self._close_raw(conn)
@locked
def close_idle(self):
# Close any open connections that are not currently in-use.
idle = self._connections
self._connections = []
for _, _, conn in idle:
self._close_raw(conn)
@locked
def close_stale(self, age=600):
# Close any connections that are in-use but were checked out quite some
# time ago and can be considered stale. May close connections in use by
# running threads.
cutoff = time.time() - age
n = 0
for key, pool_conn in list(self._in_use.items()):
if pool_conn.checked_out < cutoff:
self._close_raw(pool_conn.connection)
del self._in_use[key]
n += 1
self._pool_available.notify_all()
return n
def close_all(self):
# Close all connections -- available and in-use. Warning: may break any
# active connections used by other threads.
# self.close() acquires the database lock, calling it while holding
# the pool lock would invert the lock order used by Database.connect()
# (db lock, then pool lock via _connect) and deadlock.
self.close()
with self._pool_lock:
self.close_idle()
in_use, self._in_use = self._in_use, {}
for pool_conn in in_use.values():
self._close_raw(pool_conn.connection)
self._pool_available.notify_all()
class _PooledMySQLDatabase(PooledDatabase):
def _is_closed(self, conn):
if self.server_version[0] == 8:
args = ()
else:
args = (False,)
try:
conn.ping(*args)
except:
return True
return False
class PooledMySQLDatabase(_PooledMySQLDatabase, MySQLDatabase):
pass
class _PooledPostgresqlDatabase(PooledDatabase):
def _is_closed(self, conn):
if conn.closed:
return True
return self._adapter.is_connection_closed(conn)
def _can_reuse(self, conn):
return self._adapter.is_connection_reusable(conn)
class PooledPostgresqlDatabase(_PooledPostgresqlDatabase, PostgresqlDatabase):
pass
class _PooledSqliteDatabase(PooledDatabase):
def _is_closed(self, conn):
try:
conn.total_changes
except:
return True
return False
class PooledSqliteDatabase(_PooledSqliteDatabase, SqliteDatabase):
pass