Eu me esforcei para encontrar informações realmente detalhadas sobre como o ThreadedConnectionPool funciona. https://bbengfort.github.io/observations/2017/12/06/psycopg2-transactions.html não é ruim, mas acontece que sua afirmação de que getconn bloqueia até que uma conexão fique disponível está incorreta. Verificando o código, tudo que ThreadedConnectionPool adiciona é um bloqueio em torno dos métodos AbstractConnectionPool para evitar condições de corrida. Se mais de conexões maxconn forem tentadas em qualquer ponto, o conjunto de conexões esgotado PoolError será gerado.
Se você quiser algo um pouco mais simples do que a resposta aceita, envolver ainda mais os métodos em um semáforo fornecendo o bloqueio até que uma conexão fique disponível deve resolver o problema:
from psycopg2.pool import ThreadedConnectionPool
from threading import Semaphore
class ReallyThreadedConnectionPool(ThreadedConnectionPool):
def __init__(self, minconn, maxconn, *args, **kwargs):
self._semaphore = Semaphore(maxconn)
super().__init__(minconn, maxconn, *args, **kwargs)
def getconn(self, *args, **kwargs):
self._semaphore.acquire()
return super().getconn(*args, **kwargs)
def putconn(self, *args, **kwargs):
super().putconn(*args, **kwargs)
self._semaphore.release()