🐞 Descreva o bug
Quando certos objetos de aiohttp vazam, eles invocam o manipulador de exceção asyncio, que em alguns ambientes (por exemplo, ao usar aiorun , com stop_on_unhandled_errors=True
) significa que o programa inteiro está parado.
Por exemplo, aqui está o código relevante em ClientResponse
:
💡 Para reproduzir
Vazamento de um objeto aiohttp que faz isso.
💡 Comportamento esperado
O vazamento de objetos não deve causar uma exceção, pela mesma razão que open()
ing um arquivo e não .close()
ing não levanta uma exceção.
Todos os objetos que fazem isso parecem já gerar um aviso sobre o vazamento de recursos - não vejo um motivo para eles chamarem o manipulador de exceções também.
📋 Sua versão do Python
$ python --version
Python 3.8.6
📋 Sua versão das distribuições aiohttp / yarl / multidict
$ python -m pip show aiohttp
Name: aiohttp
Version: 3.6.2
`` `console
$ python -m pip show multidict
Nome: multidict
Versão: 4.7.6
```console
$ python -m pip show yarl
Name: yarl
Version: 1.5.1
📋 Contexto adicional
cjrh / aiorun # 56
Discordo.
O recurso não fechado é um erro de programação suficientemente sério que pode ser corrigido facilmente escrevendo o código correto.
Por exemplo, o próprio asyncio usa call_exception_handler()
para relatar sobre chamadas assíncronas não esperadas.
Se você não quiser escrever um código pedante - você pode usar stop_on_unhandled_errors=False
.
Então, por que não levantar uma exceção real então?
Acho que porque o lançamento de uma exceção em __del__
será ignorado. Mas é assim que deve ser, não? É muito difícil depurar "algo aloca um objeto e não o fecha adequadamente", especialmente quando o bug não está em seu próprio código, mas em alguma biblioteca; a chave de contexto source_traceback
está documentada e parece hostil confiar no comportamento não documentado do manipulador de exceção padrão de asyncio para tornar possível depurar vazamentos de memória.
Eu também não vejo onde asyncio usa call_exception_handler()
para avisar sobre corrotinas não esperadas. Quando uma co-rotina não esperada é coletada como lixo, ela invoca _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);
}
que invoca warnings._warn_unawaited_coroutine()
:
void
_PyErr_WarnUnawaitedCoroutine(PyObject *coro)
{
/* First, we attempt to funnel the warning through
warnings._warn_unawaited_coroutine.
que emite um RuntimeWarning. Não vejo call_exception_handler()
usado em nenhum lugar lá - na verdade, não vejo nenhum uso de call_exception_handler()
em stdlib que não inclua exception
no contexto.
E novamente: se esquecer de fechar um arquivo não levanta uma exceção, por que esquecer de limpar qualquer objeto aiohttp aleatório?
Você está certo, levantar uma exceção do método __del__
é uma prática desencorajada.
É por isso que precisamos de outra solução de sinalização.
Asyncio faz chamada call_exception_handler()
de __del__
, consulte Task.__del__
por exemplo: https://github.com/python/cpython/blob/master/Lib/asyncio/tasks.py # L139 -L148
Você está certo, source_traceback
não está documentado em https://docs.python.org/3/library/asyncio-eventloop.html?highlight=call_exception_handler#asyncio.loop.call_exception_handler
Você se importaria de criar uma solicitação pull em https://github.com/python/cpython/ para corrigir a documentação? Fico feliz em revisar / mesclar.
E novamente: se esquecer de fechar um arquivo não levanta uma exceção, por que esquecer de limpar qualquer objeto aiohttp aleatório?
aiohttp não levanta uma exceção de __del__
também, é simplesmente impossível em Python porque a chamada do finalizador de objeto é indeterminística.
Arquivo não fechado gera ResourceWarning
. Este aviso é ignorado por padrão: https://docs.python.org/3/library/warnings.html#default -warning-filter
É por isso que esse tipo de aviso não é visível para muitos desenvolvedores se eles não os habilitarem explicitamente. Isso é feito parcialmente por razões históricas: por muito tempo esse tipo de aviso não existia, muitas bibliotecas não chamam file.close()
porque seus autores não estão cientes da necessidade de uma limpeza harmoniosa de recursos, etc., etc. .
Felizmente, file.close()
pode ser chamada de file.__del__
com segurança, é uma chamada normal.
Mas no mundo assíncrono close()
é muito diferente: tem que ser uma função assíncrona que não pode ser chamada de __del__
.
Por exemplo, transport.close()
sem esperar por protocol.connection_lost()
leva a avisos mesmo para soquetes simples; para SSL, a situação é ainda pior.
o aiohttp tem um pouco de confusão com os finalizadores de sincronização / async, mas estou trabalhando nisso; aiohttp 4.0 deve ser limpo significativamente.
É por isso que a limpeza é muito importante, um aviso deve ser gerado e call_exception_handler()
será usado para tornar o mau uso mais visível.
Asyncio _does_ chama
call_exception_handler()
de__del__
, consulteTask.__del__
por exemplo
Muito bem, perdi a chamada.
Você se importaria de criar uma solicitação de pull em python / cpython para corrigir a documentação?
Sinceramente, não tenho certeza da melhor forma de documentar isso. Eu criei o bpo-42347 porque notei algumas outras discrepâncias entre os docs, o docstring e a implementação.
É por isso que a limpeza é muito importante, um aviso deve ser gerado e call_exception_handler () será usado para tornar o mau uso mais visível.
Eu acho que é frustrante quando você recebe erros porque uma biblioteca que você está usando não se limpa de maneira adequada. Não percebi que era tão importante para interromper o aplicativo, e nem percebi inicialmente que o vazamento era o motivo pelo qual o aiorun estava interrompendo o loop.