2012-05-16 4 views
18

Ich kann meine Flask-App scheinbar nicht dazu bringen, DB-Verbindungen zu schließen oder wiederzuverwenden. Ich bin mit PostgreSQL 9.1.3 undWie mache ich Flask SQLAlchemy wiederverwenden Db-Verbindungen?

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

Als meine Testsuite der Anzahl der offenen Verbindungen läuft steigt, bis er 20 trifft (die max_connections Einstellung in postgresql.conf), dann sehe ich:

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

Ich habe den Code auf den Punkt reduziert, wo es nur ruft create_all und drop_all (aber keine SQL ausgeben, da es keine Modelle gibt).

Ich sehe Verbindungen in und aus in den Protokollen überprüft werden:

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 

Für jeden Test die Adresse der Verbindung ausgeführt werden (das „Verbindungsobjekt an xyz“ Teil) ist anders. Ich vermute, das hat etwas mit dem Problem zu tun, aber ich weiß nicht, wie ich weiter nachforschen soll.

Der folgende Code reproduziert das Problem in einer neuen 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() 

Dies ist das erste Mal, dass ich app Fabriken in der Flasche verwendet haben, und ich kopiert diesen Code zum Teil aus dem Flask-SQLAlchemy docs. Elseware Diese Dokumente erwähnen, dass die Verwendung einer Datenbank im falschen Kontext dazu führt, dass Verbindungen auslaufen - vielleicht mache ich die Init falsch?

Antwort

10

Nach dem Lesen der SQLAlchemy-Dokumente und ein wenig Fummelei mit der db-Instanz, habe ich endlich die Lösung.In db.get_engine(self.app).dispose() in tearDown(), so dass es wie folgt aussieht:

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

Sie wissen, dass die setUp and tearDown vor und nach jeder test method aufgerufen werden. Aus Ihrem Code sieht es so aus, als ob Sie sie benötigen, um eine leere Datenbank zu gewährleisten.
Allerdings gibt es auch setUpClass and tearDownClass, die für jede Testklasse einmal aufgerufen werden.
Ich glaube, Sie können den Code teilen, den Sie derzeit haben, und verschieben Sie den db-connection bezogenen Teil auf die Class-Ebene, während Sie den test-method verwandten Teil, wo es sein muss.

+1

Dank Van - das löst das Problem mit der Testsuite, aber bedeutet es nicht nur, dass ich weniger Verbindungen lecke? –

+0

@TomDunham: Ich denke, du hast Recht. habe keine postgres, also kann dir da nicht helfen, sorry .. – van

6

Da die Fragen vor etwa einem Jahr gestellt wurden, habe ich festgestellt, dass das OP seine Probleme gelöst haben muss. Aber für wen auch immer wanderte hier (wie ich) versuchen, herauszufinden, was los ist, hier ist meine beste Erklärung:

Wie gesagt, das Problem ist in der Tat mit dem Testfall setUp und tearDown für jeden Test aufrufen. Obwohl die Verbindung nicht genau von SQLAlchemy abgeleitet wird, sondern weil jeder Test seine eigene setUp hat, werden mehrere Instanzen der App erstellt: Jede App hat ihren eigenen Datenbankverbindungspool, der vermutlich nicht wiederverwendet oder wiederverwendet wird wenn der Test beendet ist.

Mit anderen Worten, die Verbindung ausgecheckt wird, und an den Pool zurückgegeben richtig, aber die Verbindung dann als Verbindung im Leerlauf für zukünftige Transaktionen lebt im gleichen app (der Verbindungspunkt Pooling).

In dem obigen Testfall werden etwa 20 Verbindungspools (mit einer inaktiven Verbindung wegen create/drop_all) erstellt und belegen das Postgres-Verbindungslimit.

Verwandte Themen