web-dev-qa-db-ja.com

Flaskセッションは並列リクエストで一貫して更新されません

並列で実行されているリクエストがFlaskのsessionを変更すると、一部のキーのみが記録されることに気づきました。これは、FlaskのデフォルトのCookieセッションとRedisバックエンドを使用するFlask-Sessionの両方で発生します。プロジェクトは新しいものではありませんが、同じセッションで同時に多くのリクエストが発生した場合にのみ、このことが顕著になりました。

import time
from flask import Flask, session
from flask_session import Session

app = Flask(__name__)
app.secret_key = "example"
app.config["SESSION_TYPE"] = "redis"
Session(app)

@app.route("/set/<value>")
def set_value(value):
    """Simulate long running task."""
    time.sleep(1)
    session[value] = "done"
    return "ok\n"

@app.route("/keys")
def keys():
    return str(session.keys()) + "\n"

次のシェルスクリプトは、問題を示しています。すべてのリクエストは完了していますが、最終的なリストには1つのキーしか存在せず、テスト実行ごとに異なることに注意してください。

#!/bin/bash
# set session
curl -c 'cookie' http://localhost:5007/keys
# run parallel
curl -b 'cookie' http://localhost:5007/set/key1 && echo "done1" &
curl -b 'cookie' http://localhost:5007/set/key2 && echo "done2" & 
curl -b 'cookie' http://localhost:5007/set/key3 && echo "done3" &
wait
# get result
curl -b 'cookie' http://localhost:5007/keys
$ sh test.sh 
dict_keys(['_permanent'])
ok
ok
ok
done3
done1
done2
dict_keys(['_permanent', 'key2'])

$ sh test.sh 
dict_keys(['_permanent'])
ok
done3
ok
ok
done2
done1
dict_keys(['_permanent', 'key1'])

リクエストの完了後にすべてのキーが存在しないのはなぜですか?

18
Bear Brown

Cookieベースのセッションはスレッドセーフではありません。特定の要求は、それとともに送信されたセッションCookieのみを確認し、その要求の変更を含むCookieのみを返します。これはFlaskに固有のものではなく、HTTPリクエストが機能する方法です。

3つの要求を並行して発行します。それらはすべて、_permanentキーのみを含む初期Cookieを読み取り、要求を送信し、特定のキーでCookieを設定する応答を取得します。各応答Cookieには、_permanentキーとkey_keyNキーのみが含まれます。どちらのリクエストでもファイルへの最後の書き込みが終了し、以前のデータが上書きされるため、Cookieのみが残ります。

実際には、これは問題ではありません。セッションは、要求間で急速に変化するデータを保存することを実際には意図しておらず、それがデータベースの目的です。ログインなど、セッションを変更するものは、同じセッションと並行して発生しません(とにかくべき等です)。

これが本当に心配な場合は、サーバー側のセッションを使用してデータをデータベースに格納します。データベースは書き込みの同期に優れています。


すでにFlask-SessionとRedisを使用していますが、Flask-Sessionの実装を詳しく調べると、この問題が発生する理由がわかります。 Flask-Sessionは、各セッションキーを個別に保存するのではなく、すべてのキーとともに単一のシリアル化された値を書き込みます。したがって、Cookieベースのセッションと同じ問題が発生します。そのリクエスト中に存在したものだけがRedisに戻され、並行して発生したことを上書きします。

この場合、独自のSessionInterfaceサブクラスを記述して、各キーを個別に保存することをお勧めします。 save_sessionをオーバーライドして、sessionにすべてのキーを設定し、存在しないキーを削除します。

18
davidism