LoginSignup
10
15

More than 5 years have passed since last update.

requests 使用時の ResourceWarning: unclosed 回避策

Posted at

requests 2.4.3 を使用したコードを書いていて、以下のような警告が発生した。

ResourceWarning: unclosed <socket.socket ...>

または

ResourceWarning: unclosed <ssl.SSLSocket ...>

ソケットがクローズされていないと言われているようだ。
ResourceWarningはデバッグモードでビルドされていないと無視されるため通常は発生しない(ように見える)が、ユニットテスト実行時に毎回発生するのが気になる。
何より、本当にソケットがクローズされていないなら適切にクローズしないといけないのでは、と思い調べてみた。

requests リポジトリの issue 確認

本件は issue に報告済みだったのでそちらを見てみる。
ResourceWarning in python 3.2+ #1882
ざっと流し読んだ感じだと、

  • バグかどうか判断しかねていて、修正していない。
  • requestsではコネクションプーリングの機構があり、コネクションがGCで回収された際にソケットをクローズするようにしている。

ということらしい。
ユニットテストなどでGCが走る前にコードの実行が終了すると警告として現れるが、実質問題にはならないようだ。
これを踏まえて現状取れる回避策を考える。

回避策1. 警告を抑止する

身も蓋もないが、

unittest.main(warnings='ignore')

これで警告を出さなくする。
今回の ResourceWarning に限っては「The warning is annoying but it's not symptomatic of a problem.」とのことなので無視してもいいかもしれないけど、全ての警告を出さなくするのは乱暴なやり方ではある…。

回避策2. Request.connection.close() を呼ぶ

前述の issue で報告されていた回避策を元に、withブロックで使用できるクラスを作ってみる。

http_wrapper.py
class HttpWrapper(object):
    def __init__(self, http_method, url, **kwargs):
        self.http_method = http_method
        self.url = url
        self.kwargs = kwargs

    def __enter__(self):
        result = self.http_method(self.url, **self.kwargs)
        self.connection = result.connection
        return result

    def __exit__(self, type, value, traceback):
        self.connection.close()

以下のように使用する。

with HttpWrapper(requests.get, 'http://...', auth=...) as result:
    # resultを使った処理

ただし、これではコネクションプールを全く活用していない事になる…。

回避策3. Sessionを明示的に使用し、Session.close() を呼ぶ

回避策2とほぼ同じだが、前述の issue でコミッタと思わしき方が提案されていた回避策を元に、withブロックで使用できるクラスを作ってみる。

session_manager.py
from enum import Enum
from requests import Session


class HttpMethod(Enum):
    get = 'get'
    post = 'post'
    put = 'put'
    delete = 'delete'

class SessionManager(object):
    def __init__(self, http_method, url, **kwargs):
        self.http_method = http_method
        self.url = url
        self.kwargs = kwargs

    def __enter__(self):
        self.session = Session()
        result = getattr(self.session, self.http_method.name)(self.url, **self.kwargs)
        return result

    def __exit__(self, type, value, traceback):
        self.session.close()

以下のように使用する。

with SessionManager(HttpMethod.get, 'http://...', auth=...) as result:
    # resultを使った処理

こちらもやはりコネクションプールを全く活用していない事になる…。

結論(…は、出ていないが)

どの回避策も今ひとつのような気がする…。
結局、現時点ではどのようにすればいいのだろうか。
issue も Open のままなので、 requests 側で対応してくれることを期待して今は警告を出しっ放しにしておく、というのが無難かもしれない…。

10
15
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
15