22

FlaskアプリでDB接続を閉じたり再利用したりできないようです。私はPostgreSQL9.1.3を使用しています

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

max_connectionsテストスイートを実行すると、開いている接続の数が20(の設定)に達するまで増加し、次のpostgresql.confように表示されます。

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

create_allコードを、 andを呼び出すだけのポイントに減らしましたdrop_all(ただし、モデルがないため、SQLを発行していません)。

ログに接続がチェックインおよびチェックアウトされているのがわかります。

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

テストの実行ごとに、接続のアドレス(「xyzの接続オブジェクト」の部分)は異なります。これは問題と関係があるのではないかと思いますが、さらに調査する方法がわかりません。

以下のコードは、新しい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()

フラスコでアプリファクトリを使用するのはこれが初めてであり、このコードの一部をFlask-SQLAlchemyのドキュメントからコピーしました。それ以外の場合、これらのドキュメントでは、間違ったコンテキストでデータベースを使用すると接続がリークする可能性があると述べています-おそらく私はinitを間違って行っていますか?

4

4 に答える 4

11

SQLAlchemyのドキュメントを読み、dbインスタンスをいじった後、ようやく解決策が得られました。次のように追加db.get_engine(self.app).dispose()します。tearDown()

def tearDown(self):
    db.session.remove()
    db.drop_all()
    db.get_engine(self.app).dispose()
    self._ctx.pop()
于 2013-08-01T15:36:22.957 に答える
9

質問は約1年前に行われたので、OPは彼の問題を解決したに違いないと思います。しかし、何が起こっているのかを理解しようとしてここに(私のように)さまよった人のために、ここに私の最良の説明があります:

ヴァンが言ったように、問題は確かにテストケースの呼び出しsetUptearDown各テストにあります。接続がSQLAlchemyから正確にリ​​ークしているわけではありませんが、その代わりに、各テストに独自setUpのインスタンスが作成されます。各アプリには独自のデータベース接続プールがあり、おそらく再利用またはリサイクルされません。テストが終了します。

つまり、接続はチェックアウトされてプールに正しく返されますが、接続は同じアプリ内の将来のトランザクション(接続プールのポイント)のアイドル接続として存続します。

上記のテストケースでは、約20の接続プール(create / drop_allのためにそれぞれアイドル接続)が作成され、postgres接続制限を占有しています。

于 2013-05-06T00:48:46.573 に答える
2

編集:SQLALCHEMY_COMMIT_ON_TEARDOWNは、バージョン2.4.3のFlask-SQLAlchemyで非推奨になりました。ここで変更メモを確認できます。ここでは、自分で電話することをお勧め.commit()します。

https://flask-sqlalchemy.palletsprojects.com/en/2.x/changelog/?highlight=sqlalchemy_commit_on_teardown#version-2-4-3

私がやっていることは、応答ステータスコードが400未満の場合app.after_requestに呼び出す自分自身を登録.commit()することです。これには、応答ステータスコードが400未満のHTTPトランザクションがデータベースにコミットされるようにアプリケーションを適切に構成する必要がありますが、これは良いことだと思います。設計原理。

----以下の古い古い回答----

最新バージョンのFlask-SQLAlchemyでは、session.remove()が自動的に呼び出されapp.after_requestます。

また、SQLALCHEMY_COMMIT_ON_TEARDOWNここの設定を参照してください:

https://pythonhosted.org/Flask-SQLAlchemy/config.html?highlight=sqlalchemy_commit_on_teardown

これにより、トランザクションも自動的にコミットされます。

于 2015-03-16T00:32:17.087 に答える
0

setUp and tearDownが毎回前後に呼び出されることを知っていtest methodます。あなたのコードから、空のデータベースを確保するためにそれらが必要であるように見えます。
ただし、setUpClass and tearDownClassテストクラスごとに1回呼び出されるもあります。
現在持っているコードを分割して、関連部分を必要な場所に残したまま、関連部分db-connectionを-Classレベルに移動できると思います。test-method

于 2012-05-17T16:12:16.067 に答える