성공적인 py.test 실행 후 'threading'모듈의 KeyError
py.test로 일련의 테스트를 실행하고 있습니다. 통과합니다. 이피! 하지만이 메시지가 나타납니다.
Exception KeyError: KeyError(4427427920,) in <module 'threading' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/threading.pyc'> ignored
그 출처를 추적하려면 어떻게해야합니까? (스레딩을 직접 사용하지 않지만 gevent를 사용하고 있습니다.)
나는 비슷한 문제를 관찰하고 정확히 무슨 일이 일어나고 있는지보기로 결정했습니다. 내 결과를 설명하겠습니다. 누군가가 유용하다고 생각하기를 바랍니다.
단편
실제로 threading
모듈 을 원숭이 패치하는 것과 관련이 있습니다. 사실 몽키 패칭 스레드 전에 스레딩 모듈을 가져 와서 예외를 쉽게 트리거 할 수 있습니다. 다음 두 줄이면 충분합니다.
import threading
import gevent.monkey; gevent.monkey.patch_thread()
실행되면 무시됨에 대한 메시지를 뱉습니다 KeyError
.
(env)czajnik@autosan:~$ python test.py
Exception KeyError: KeyError(139924387112272,) in <module 'threading' from '/usr/lib/python2.7/threading.pyc'> ignored
가져 오기 행을 바꾸면 문제가 사라집니다.
긴 이야기
여기서 디버깅을 중지 할 수 있지만 문제의 정확한 원인을 이해하는 것이 가치가 있다고 결정했습니다.
첫 번째 단계는 무시 된 예외에 대한 메시지를 인쇄하는 코드를 찾는 것이 었습니다. 내가 그것을 (대한 grepping 찾는 것이 조금 힘들었다 Exception.*ignored
나는 결국라는 함수 발견했습니다 굴복 아무것도), Cpython과 소스 코드 주위에 grepping void PyErr_WriteUnraisable(PyObject *obj)
에 파이썬 / error.c을 매우 흥미로운 코멘트와 함께 :
/* Call when an exception has occurred but there is no way for Python
to handle it. Examples: exception in __del__ or during GC. */
gdb
다음 C 레벨 스택 추적을 얻기 위해에서 약간의 도움을 받아 누가 호출하는지 확인하기로 결정했습니다 .
#0 0x0000000000542c40 in PyErr_WriteUnraisable ()
#1 0x00000000004af2d3 in Py_Finalize ()
#2 0x00000000004aa72e in Py_Main ()
#3 0x00007ffff68e576d in __libc_start_main (main=0x41b980 <main>, argc=2,
ubp_av=0x7fffffffe5f8, init=<optimized out>, fini=<optimized out>,
rtld_fini=<optimized out>, stack_end=0x7fffffffe5e8) at libc-start.c:226
#4 0x000000000041b9b1 in _start ()
이제 Py_Finalize가 실행 되는 동안 예외가 발생했음을 분명히 알 수 있습니다. 이 호출은 Python 인터프리터를 종료하고 할당 된 메모리를 해제하는 등의 책임이 있습니다. 종료 직전에 호출됩니다.
다음 단계는 Py_Finalize()
코드 를 살펴 보는 것이 었습니다 ( Python / pythonrun.c에 있음 ). 첫 번째 호출 wait_for_thread_shutdown()
은 문제가 스레딩과 관련되어 있다는 것을 알고 있으므로 살펴볼 가치가 있습니다. 이 함수는 차례로 모듈 _shutdown
에서 호출 가능을 호출 threading
합니다. 좋아요, 이제 파이썬 코드로 돌아갈 수 있습니다.
를 보면 threading.py
다음과 같은 흥미로운 부분을 발견했습니다 I :
class _MainThread(Thread):
def _exitfunc(self):
self._Thread__stop()
t = _pickSomeNonDaemonThread()
if t:
if __debug__:
self._note("%s: waiting for other threads", self)
while t:
t.join()
t = _pickSomeNonDaemonThread()
if __debug__:
self._note("%s: exiting", self)
self._Thread__delete()
# Create the main thread object,
# and make it available for the interpreter
# (Py_Main) as threading._shutdown.
_shutdown = _MainThread()._exitfunc
분명히 threading._shutdown()
call 의 책임은 모든 non-daemon 쓰레드를 결합하고 main 쓰레드를 삭제하는 것입니다. 나는 threading.py
약간 패치하기로 결정했다 - _exitfunc()
전신을 try
/로 감싸고 except
트레이스 백 모듈로 스택 트레이스를 인쇄한다 . 이것은 다음과 같은 추적을 제공했습니다.
Traceback (most recent call last):
File "/usr/lib/python2.7/threading.py", line 785, in _exitfunc
self._Thread__delete()
File "/usr/lib/python2.7/threading.py", line 639, in __delete
del _active[_get_ident()]
KeyError: 26805584
Now we know the exact place where the exception is thrown - inside Thread.__delete()
method.
The rest of the story is obvious after reading threading.py
for a while. The _active
dictionary maps thread IDs (as returned by _get_ident()
) to Thread
instances, for all threads created. When threading
module is loaded, an instance of _MainThread
class is always created and added to _active
(even if no other threads are explicitly created).
The problem is that one of the methods patched by gevent
's monkey-patching is _get_ident()
- original one maps to thread.get_ident()
, monkey-patching replaces it with green_thread.get_ident()
. Obviously both calls return different IDs for main thread.
Now, if threading
module is loaded before monkey-patching, _get_ident()
call returns one value when _MainThread
instance is created and added to _active
, and another value at the time _exitfunc()
is called - hence KeyError
in del _active[_get_ident()]
.
On the contrary, if monkey-patching is done before threading
is loaded, all is fine - at the time _MainThread
instance is being added to _active
, _get_ident()
is already patched, and the same thread ID is returned at cleanup time. That's it!
To make sure I import modules in the right order, I added the following snippet to my code, just before monkey-patching call:
import sys
if 'threading' in sys.modules:
raise Exception('threading module loaded before patching!')
import gevent.monkey; gevent.monkey.patch_thread()
I hope you find my debugging story useful :)
You could use this:
import sys
if 'threading' in sys.modules:
del sys.modules['threading']
import gevent
import gevent.socket
import gevent.monkey
gevent.monkey.patch_all()
I had a similar problem with a gevent prototype script.
The Greenlet callback was executing fine and I was synchronizing back to the main thread via g.join(). For my problem, I had to call gevent.shutdown() to shutdown (what I assume is) the Hub. After I manually shutdown the event loop, the program terminates properly without that error.
'developer tip' 카테고리의 다른 글
오류 : 프로세스 ID 0에 연결하지 못했습니다. (0) | 2020.11.06 |
---|---|
C에서 함수 호출 재정의 (0) | 2020.11.06 |
Internet Explorer에서 파일 자동 다운로드를 시작하는 방법은 무엇입니까? (0) | 2020.11.06 |
Excel 일련 날짜 번호를 .NET DateTime으로 변환하려면 어떻게합니까? (0) | 2020.11.06 |
Android에서 모든 연락처의 전화 번호 읽기 (0) | 2020.11.06 |