読者です 読者をやめる 読者になる 読者になる

Django1.0.2はMiddlewareの初期化コードがスレッドセーフではない

Django

工エエェ(´Д`)ェエエ工

django.core.handlers.wsgi.WSGIHandler(mod_pythonでDjangoを動かす以外に使われるHandler)は、スレッドセーフではなく、Middlewareが適切に初期化されないままリクエストの処理が行われる可能性があります。

該当箇所は次のようなコードになっています。

if self._request_middleware is None:
    self.initLock.acquire()
    # Check that middleware is still uninitialised.
    if self._request_middleware is None:
        self.load_middleware()
    self.initLock.release()

問題は、上記コード5行目のself.load_middleware()が完了する前に、self._request_middlewareに非Noneの値を設定していることに起因しています。

  1. Apacheインスタンスが(再)起動する
  2. スレッドT1がself._request_middlewareがNoneであるかをチェックする
  3. スレッドT1がself.initLockのロックを獲得し、self.load_middleware()を呼び出す
  4. スレッドT2が実行される。スレッドT1はまだself.load_middleware()を実行中である
  5. この時点で、self._request_middlewareはNoneではないし、Middlewareの初期化も完了していない。しかし、スレッドT2のリクエストの実行はそのま実行される。本来ならば、スレッドT2はself.initLock獲得待ち状態になることが期待されている
  6. スレッドT1がMiddlewareの初期化を完了し、ロックを開放する

Middlewareが適切に初期化されていないと、認証機構(django.contrib.auth)やセッション(django.contrib.session)が正しく動作しないので、

  • "AttributeError: 'WSGIRequest' object has no attribute 'user'"
  • "AttributeError: 'WSGIRequest' object has no attribute 'session'"

といったエラーが発生します。マルチスレッドでDjangoを動作させていると、ほどほどの同時リクエスト数であっても、簡単に再現することができます。

この問題はr10038(とその他の関連コミット)で修正されています。

以前に、mod_wsgiアプリケーションを再起動した時にエラーになるということを書きましたが、mod_wsgiの問題ではなく、Djangoのこの問題だったようです。この修正だけでも、Django 1.1が待ち遠しいですね。