🐞 Опишите ошибку
Когда происходит утечка определенных объектов из aiohttp, они вызывают обработчик исключений asyncio, что в некоторых средах (например, при использовании aiorun с stop_on_unhandled_errors=True
) означает, что вся программа останавливается.
Например, вот соответствующий код в ClientResponse
:
💡 Воспроизвести
Утечка объекта aiohttp, который это делает.
💡 Ожидаемое поведение
Утечка объектов не должна вызывать исключения по той же причине, что open()
ing файл, а не .close()
ing, не вызывает исключения.
Все объекты, которые делают это, похоже, уже вызывают предупреждение об утечке ресурсов - я не вижу причин для них также вызывать обработчик исключений.
📋 Ваша версия 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()
где-либо использовалось - на самом деле я не вижу никакого использования call_exception_handler()
в stdlib, которое не включает exception
в контексте.
И снова: если забывание закрыть файл не вызывает исключения, почему следует забывать очищать любой случайный объект 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
Не могли бы вы создать запрос на перенос на
И снова: если забывание закрыть файл не вызывает исключения, почему следует забывать очищать любой случайный объект 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 немного беспорядок с синхронизаторами / асинхронными финализаторами, но я над этим работаю; aiohttp 4.0 следует значительно почистить.
Вот почему очистка очень важна, должно быть выдано предупреждение, и call_exception_handler()
будет использоваться, чтобы сделать неправильное использование более заметным.
Asyncio _does_ вызывает
call_exception_handler()
из__del__
, например, см.Task.__del__
Честно говоря, я пропустил этот звонок.
Не могли бы вы создать запрос на перенос на python / cpython, чтобы исправить документацию?
Честно говоря, я не уверен, как лучше всего это задокументировать. Я создал bpo-42347, так как заметил некоторые другие несоответствия между документами,
Вот почему очистка очень важна, должно быть выдано предупреждение, а call_exception_handler () будет использоваться, чтобы сделать неправильное использование более заметным.
Я думаю, это просто неприятно, когда вы получаете ошибки, потому что библиотека, которую вы используете, не очищает себя должным образом. Я не понимал, что это настолько важно, что должно остановить приложение, и поначалу даже не понимал, что утечка была причиной того, что aiorun останавливал цикл.