🐞バグを説明してください
aiohttpからの特定のオブジェクトがリークされると、それらはasyncio例外ハンドラーを呼び出します。これは一部の環境(たとえば、 aiorunをstop_on_unhandled_errors=True
)では、プログラム全体が停止することを意味します。
たとえば、 ClientResponse
の関連コードは次のとおりです。
💡再現するには
これを行うaiohttpオブジェクトをリークします。
💡期待される動作
open()
ファイルを.close()
しないのと同じ理由で、オブジェクトをリークしても例外は発生しません。
これを行うすべてのオブジェクトは、すでにリソースリークに関する警告を発しているようです。例外ハンドラーを呼び出す理由もわかりません。
📋お使いのバージョンのPython
$ python --version
Python 3.8.6
📋aiohttp / yarl / multidictディストリビューションのバージョン
$ python -m pip show aiohttp
Name: aiohttp
Version: 3.6.2
`` `コンソール
$ python -m pip show multidict
名前:multidict
バージョン:4.7.6
```console
$ python -m pip show yarl
Name: yarl
Version: 1.5.1
📋追加のコンテキスト
cjrh / aiorun#56
同意しません。
閉じられていないリソースは、正しいコードを書くことで簡単に修正できる十分に深刻なプログラミングエラーです。
たとえば、asyncio自体は、待機していない非同期呼び出しに関するレポートにcall_exception_handler()
を使用します。
衒学的なコードを書きたくない場合は、 stop_on_unhandled_errors=False
使用できます。
では、本当の例外を提起してみませんか?
__del__
例外を発生させることは無視されるので、私は推測します。 しかし、それはあるべき姿ですよね? 「何かがオブジェクトを割り当てて、それを適切に閉じない」ことをデバッグすることは非常に困難です。特に、バグが自分のコードではなくライブラリにある場合はなおさらです。 source_traceback
コンテキストキーは文書化されておらず、メモリリークのデバッグを可能にするためにasyncioのデフォルトの例外ハンドラーの文書化されていない動作に依存するのは不親切に思えます。
また、asyncioがcall_exception_handler()
を使用して、待機していないコルーチンについて警告する場所もわかりません。 待望のコルーチンがガベージコレクションされると、 _PyErr_WarnUnawaitedCoroutine
が呼び出されます。
/* If `gen` is a coroutine, and if it was never awaited on,
issue a RuntimeWarning. */
if (gen->gi_code != NULL &&
((PyCodeObject *)gen->gi_code)->co_flags & CO_COROUTINE &&
gen->gi_frame->f_lasti == -1)
{
_PyErr_WarnUnawaitedCoroutine((PyObject *)gen);
}
warnings._warn_unawaited_coroutine()
を呼び出します:
void
_PyErr_WarnUnawaitedCoroutine(PyObject *coro)
{
/* First, we attempt to funnel the warning through
warnings._warn_unawaited_coroutine.
これはRuntimeWarningを発行します。 call_exception_handler()
がどこにも使用されていません。実際、コンテキストにexception
が含まれていないstdlibでcall_exception_handler()
れていることはありません。
また、ファイルを閉じるのを忘れても例外が発生しない場合は、ランダムなaiohttpオブジェクトのクリーンアップを忘れる必要があるのはなぜですか?
そうです、 __del__
メソッドから例外を発生させることはお勧めできません。
そのため、別のシグナリングソリューションが必要です。
Asyncioは呼び出しませんcall_exception_handler()
から__del__
、参照Task.__del__
:例えばhttps://github.com/python/cpython/blob/master/Lib/asyncio/tasks.py #L139 -L148
そうです、 source_traceback
はhttps://docs.python.org/3/library/asyncio-eventloop.html?highlight=call_exception_handler#asyncio.loop.call_exception_handlerに文書化されていません
ドキュメントを修正するために、 https://github.com/python/cpython/でプルリクエストを作成して
また、ファイルを閉じるのを忘れても例外が発生しない場合は、ランダムなaiohttpオブジェクトのクリーンアップを忘れる必要があるのはなぜですか?
aiohttpから例外は__del__
オブジェクトのファイナライザの呼び出しが決定不能であるので、それはPythonでちょうど不可能で、あまりにも。
閉じられていないファイルはResourceWarning
ます。 この警告はデフォルトでは無視されます: https ://docs.python.org/3/library/warnings.html#default -warning-filter
そのため、この種の警告は、明示的に有効にしないと、非常に多くの開発者に表示されません。 これは歴史的な理由で部分的に行われます。長い間、この種の警告は存在しませんでした。多くのライブラリは、作成者が適切なリソースクリーンアップなどの必要性を認識していないため、 file.close()
呼び出しません。 。
幸い、 file.close()
はfile.__del__
安全に呼び出すことができ、通常の呼び出しです。
しかし、asyncioの世界では、 close()
は非常に頻繁に異なります。それは、 __del__
から呼び出すことができない非同期関数である必要があります。
例えば、 transport.close()
を待たずにprotocol.connection_lost()
であっても、プレーンソケットの警告につながります。 SSLの場合、状況はさらに悪化します。
aiohttpにはsync / asyncファイナライザーが少し混乱していますが、私はそれに取り組んでいます。 aiohttp4.0は大幅にクリーンアップする必要があります。
そのため、クリーンアップが非常に重要であり、警告を発する必要があります。 call_exception_handler()
は、不適切な使用法をより目立たせるために使用されます。
Asyncio呼び出す_does_
call_exception_handler()
から__del__
参照、Task.__del__
例えば
十分に公平なことですが、私はその電話に出られませんでした。
ドキュメントを修正するために、python / cpythonでプルリクエストを作成していただけませんか?
正直なところ、それを文書化する最善の方法がわかりません。 ドキュメント、docstring、および実装の間に他のいくつかの不一致に気付いたので、 bpo-42347を作成しました。
そのため、クリーンアップが非常に重要であり、警告を発する必要があります。call_exception_handler()を使用して、不適切な使用法をより目立たせます。
使用しているライブラリがそれ自体の後で適切にクリーンアップされないため、エラーが発生したときにイライラするだけだと思います。 アプリケーションを停止することがそれほど重要であることに気づかなかったし、リークがaiorunがループを停止した理由であることに最初は気づいていなかった。