2012-05-16 11 views
18

Parece que no puedo hacer que mi aplicación Flask cierre o reutilice las conexiones DB. Estoy usando PostgreSQL 9.1.3 y¿Cómo hago que Flask SQLAlchemy reutilice las conexiones de db?

Flask==0.8 
Flask-SQLAlchemy==0.16 
psycopg2==2.4.5 

Como mi banco de pruebas se ejecuta el número de conexiones abiertas sube hasta que llega a 20 (el ajuste max_connections en postgresql.conf), entonces veo:

OperationalError: (OperationalError) FATAL: sorry, too many clients already 
None None 

Reduje el código al punto en el que solo llama a create_all y drop_all (pero no emite ningún sql ya que no hay modelos).

I ver las conexiones que se comprueba y salir en los registros de:

DEBUG:sqlalchemy.pool.QueuePool:Connection <connection object at 0x101c1dff0; dsn: 'dbname=cx_test host=localhost', closed: 0> checked out from pool 
DEBUG:sqlalchemy.pool.QueuePool:Connection <connection object at 0x101c1dff0; dsn: 'dbname=cx_test host=localhost', closed: 0> being returned to pool 
WARNING:root:impl <-------- That's the test running 
DEBUG:sqlalchemy.pool.QueuePool:Connection <connection object at 0x101c1dff0; dsn: 'dbname=cx_test host=localhost', closed: 0> checked out from pool 
DEBUG:sqlalchemy.pool.QueuePool:Connection <connection object at 0x101c1dff0; dsn: 'dbname=cx_test host=localhost', closed: 0> being returned to pool 

Para cada ejecución de prueba de la dirección de la conexión (el "objeto de conexión en xyz" parte) es diferente. Sospecho que esto tiene algo que ver con el problema, pero no estoy seguro de cómo investigar más.

El código siguiente reproduce el problema de una nueva Venv:

from flask import Flask 
from flask.ext.sqlalchemy import SQLAlchemy 
from unittest import TestCase 

import logging 
logging.basicConfig(level=logging.DEBUG) 
logging.getLogger('sqlalchemy.pool').setLevel(logging.DEBUG) 
logging.getLogger('sqlalchemy.engine').setLevel(logging.DEBUG) 
logging.getLogger('sqlalchemy.dialects').setLevel(logging.DEBUG) 
logging.getLogger('sqlalchemy.orm').setLevel(logging.DEBUG) 


db = SQLAlchemy() 

def create_app(config=None): 
    app = Flask(__name__) 
    app.config.from_object(config) 
    db.init_app(app) 
    return app 


class AppTestCase(TestCase): 
    SQLALCHEMY_DATABASE_URI = "postgresql://localhost/cx_test" 
    TESTING = True 

    def create_app(self): 
     return create_app(self) 

    def setUp(self): 
     self.app = self.create_app() 
     self.client = self.app.test_client() 
     self._ctx = self.app.test_request_context() 
     self._ctx.push() 
     db.create_all() 

    def tearDown(self): 
     db.session.remove() 
     db.drop_all() 
     self._ctx.pop() 


class TestModel(AppTestCase): 
    def impl(self): 
     logging.warn("impl") 
     pass 

    def test_01(self): 
     self.impl() 

    def test_02(self): 
     self.impl() 

    def test_03(self): 
     self.impl() 

    def test_04(self): 
     self.impl() 

    def test_05(self): 
     self.impl() 

    def test_06(self): 
     self.impl() 

    def test_07(self): 
     self.impl() 

    def test_08(self): 
     self.impl() 

    def test_09(self): 
     self.impl() 

    def test_10(self): 
     self.impl() 

    def test_11(self): 
     self.impl() 

    def test_12(self): 
     self.impl() 

    def test_13(self): 
     self.impl() 

    def test_14(self): 
     self.impl() 

    def test_15(self): 
     self.impl() 

    def test_16(self): 
     self.impl() 

    def test_17(self): 
     self.impl() 

    def test_18(self): 
     self.impl() 

    def test_19(self): 
     self.impl() 



if __name__ == "__main__": 
    import unittest 
    unittest.main() 

Esta es la primera vez que he utilizado fábricas de aplicaciones en el frasco, y he copiado el código en parte de la Flask-SQLAlchemy docs. Elseware esos documentos mencionan que usar un DB en el contexto incorrecto hará que las conexiones se filtren, ¿quizás estoy haciendo el init incorrectamente?

Respuesta

10

Después de leer los documentos de SQLAlchemy y tocar la instancia de db, finalmente obtuve la solución.Añadir db.get_engine(self.app).dispose() en tearDown() para que se vea como:

def tearDown(self): 
    db.session.remove() 
    db.drop_all() 
    db.get_engine(self.app).dispose() 
    self._ctx.pop() 
1

Sabe que se llama al setUp and tearDown antes y después de cada test method. Desde su código, parece que los necesita para garantizar la base de datos vacía.
Sin embargo, también hay setUpClass and tearDownClass, que se llaman una vez para cada clase de prueba.
Creo que puede dividir el código que tiene actualmente y mover la parte relacionada db-connection al Class -level, mientras mantiene la parte relacionada test-method donde debe estar.

+1

Gracias Van - eso resuelve el problema con el banco de pruebas, pero ¿no significa que estoy perdiendo menos conexiones? –

+0

@TomDunham: supongo que tienes razón. no tienes 'postgres', así que no puedo ayudarte, lo siento ... – van

6

Dado que las preguntas fueron formuladas hace un año, me imagino que el OP debe haber resuelto sus problemas. Pero para quien vagó hasta aquí (como yo) tratando de averiguar lo que está pasando, aquí está mi mejor explicación:

Como dijo van, el problema es de hecho con el caso de prueba llamando setUp y tearDown para cada prueba. Aunque la conexión no se está filtrando exactamente de SQLAlchemy, sino que cada prueba tiene su propia setUp, se crean varias instancias de la aplicación: cada aplicación tendrá su propio grupo de conexiones de base de datos, que presumiblemente no se reutilizará ni reciclará cuando la prueba termine

En otras palabras, la conexión se está desprotegido y regresaron a la piscina correctamente, pero la conexión a continuación, viviendo como una conexión inactiva para futuras transacciones dentro del misma aplicación (el punto de la agrupación de conexiones).

En el caso de prueba anterior, se crean alrededor de 20 pools de conexión (cada uno con una conexión inactiva debido a create/drop_all) y que ocupan el límite de conexión postgres.

Cuestiones relacionadas