developer tip

성공적인 py.test 실행 후 'threading'모듈의 KeyError

optionbox 2020. 11. 6. 08:06
반응형

성공적인 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.

참고URL : https://stackoverflow.com/questions/8774958/keyerror-in-module-threading-after-a-successful-py-test-run

반응형