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ブロックで使用できるクラスを作ってみる。
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ブロックで使用できるクラスを作ってみる。
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 側で対応してくれることを期待して今は警告を出しっ放しにしておく、というのが無難かもしれない…。