2019-10-16 11:47:02 +02:00
|
|
|
import asyncio
|
2020-08-10 13:24:14 +02:00
|
|
|
from typing import Any, Dict, List, Optional
|
2019-10-16 11:47:02 +02:00
|
|
|
|
|
|
|
import aioredis
|
|
|
|
from channels_redis.core import ConnectionPool as ChannelRedisConnectionPool
|
|
|
|
from django.conf import settings
|
|
|
|
|
2020-08-19 15:25:35 +02:00
|
|
|
from . import logging
|
2019-10-16 11:47:02 +02:00
|
|
|
|
2020-08-19 15:25:35 +02:00
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
2019-10-16 11:47:02 +02:00
|
|
|
connection_pool_limit = getattr(settings, "CONNECTION_POOL_LIMIT", 100)
|
2020-08-19 15:25:35 +02:00
|
|
|
logger.info(f"CONNECTION_POOL_LIMIT={connection_pool_limit}")
|
2019-10-16 11:47:02 +02:00
|
|
|
|
|
|
|
|
2020-08-10 13:24:14 +02:00
|
|
|
class InvalidConnection(Exception):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2019-10-16 11:47:02 +02:00
|
|
|
class ConnectionPool(ChannelRedisConnectionPool):
|
|
|
|
""" Adds a trivial, soft limit for the pool """
|
|
|
|
|
|
|
|
def __init__(self, host: Any) -> None:
|
|
|
|
self.counter = 0
|
|
|
|
super().__init__(host)
|
|
|
|
|
|
|
|
async def pop(
|
|
|
|
self, *args: List[Any], **kwargs: Dict[str, Any]
|
|
|
|
) -> aioredis.commands.Redis:
|
|
|
|
while self.counter > connection_pool_limit:
|
|
|
|
await asyncio.sleep(0.1)
|
|
|
|
self.counter += 1
|
|
|
|
|
2020-08-10 13:24:14 +02:00
|
|
|
return await self.pop_ensured_connection(*args, **kwargs)
|
|
|
|
|
|
|
|
async def pop_ensured_connection(
|
|
|
|
self, *args: List[Any], **kwargs: Dict[str, Any]
|
|
|
|
) -> aioredis.commands.Redis:
|
|
|
|
redis: Optional[aioredis.commands.Redis] = None
|
|
|
|
|
|
|
|
while redis is None:
|
|
|
|
redis = await super().pop(*args, **kwargs)
|
|
|
|
|
|
|
|
try:
|
|
|
|
await self.try_ping(redis)
|
|
|
|
except InvalidConnection:
|
|
|
|
if redis is not None:
|
|
|
|
super().conn_error(redis)
|
|
|
|
redis = None
|
|
|
|
|
|
|
|
return redis
|
|
|
|
|
|
|
|
async def try_ping(self, redis: aioredis.commands.Redis) -> None:
|
|
|
|
try:
|
|
|
|
pong = await redis.ping()
|
|
|
|
if pong != b"PONG":
|
|
|
|
logger.info("Redis connection invalid, did not recieve PONG")
|
|
|
|
raise InvalidConnection()
|
|
|
|
except (ConnectionRefusedError, ConnectionResetError):
|
|
|
|
logger.info("Redis connection invalid, connection is bad")
|
|
|
|
raise InvalidConnection()
|
2019-10-16 11:47:02 +02:00
|
|
|
|
|
|
|
def push(self, conn: aioredis.commands.Redis) -> None:
|
|
|
|
super().push(conn)
|
|
|
|
self.counter -= 1
|
|
|
|
|
|
|
|
def conn_error(self, conn: aioredis.commands.Redis) -> None:
|
|
|
|
super().conn_error(conn)
|
|
|
|
self.counter -= 1
|
|
|
|
|
|
|
|
def reset(self) -> None:
|
|
|
|
super().reset()
|
|
|
|
self.counter = 0
|