KeyError в модуле 'threading' после успешного запуска py.test - PullRequest
68 голосов
/ 08 января 2012

Я запускаю ряд тестов с py.test.Они проходят.Yippie!Но я получаю это сообщение:

Exception KeyError: KeyError(4427427920,) in <module 'threading' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/threading.pyc'> ignored

Как мне найти источник этого?(Я не использую потоки напрямую, но использую gevent.)

Ответы [ 3 ]

213 голосов
/ 28 сентября 2012

Я заметил похожую проблему и решил посмотреть, что именно происходит - позвольте мне описать мои выводы. Я надеюсь, что кто-то найдет это полезным.

Рассказ

Это действительно связано с исправлением обезьяны модулем threading. На самом деле, я могу легко вызвать исключение, импортировав модуль потоков перед тем, как монтировать патчи потоков. Следующие 2 строки достаточно:

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

Если вы поменяете местами строки импорта, проблема исчезнет.

Длинная история

Я мог бы прекратить отладку здесь, но я решил, что стоит понять точную причину проблемы.

Первым шагом было найти код, который печатает сообщение об игнорируемом исключении. Мне было немного трудно его найти (поиск по Exception.*ignored ничего не дал), но по поиску исходного кода CPython я в конце концов нашел функцию с именем void PyErr_WriteUnraisable(PyObject *obj) в Python / 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. Хорошо, теперь мы можем вернуться к коду Python.

Глядя на threading.py Я нашел следующие интересные части:

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() состоит в том, чтобы присоединиться ко всем потокам, не являющимся демонами, и удалить основной поток (что бы это ни значило). Я решил немного пропатчить threading.py - обернуть все тело _exitfunc() try / except и распечатать трассировку стека с помощью модуля traceback . Это дало следующий след:

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

Теперь мы знаем точное место, где выбрасывается исключение - внутри Thread.__delete() метода.

Остальная часть истории становится очевидной после прочтения threading.py некоторое время. Словарь _active отображает идентификаторы потоков (возвращаемые _get_ident()) в экземпляры Thread для всех созданных потоков. Когда загружен модуль threading, экземпляр класса _MainThread всегда создается и добавляется в _active (даже если никакие другие потоки не создаются явно).

Проблема в том, что один из методов, исправленных патчами обезьяны gevent, - _get_ident() - оригинал соответствует thread.get_ident(), патч-обезьяна заменяет его green_thread.get_ident(). Очевидно, что оба вызова возвращают разные идентификаторы для основного потока.

Теперь, если модуль threading загружен до установки исправлений обезьяны, вызов _get_ident() возвращает одно значение, когда экземпляр _MainThread создается и добавляется к _active, а другое значение в то время, когда вызывается _exitfunc(), - следовательно KeyError в del _active[_get_ident()].

Наоборот, если патч обезьяны выполняется до загрузки threading, все в порядке - в то время, когда _MainThread экземпляр добавляется в _active, _get_ident() уже исправлен, и тот же поток Идентификатор возвращается во время очистки. Вот и все!

Чтобы убедиться, что я импортирую модули в правильном порядке, я добавил следующий фрагмент кода в свой код, прямо перед вызовом monkey-patching:

import sys
if 'threading' in sys.modules:
        raise Exception('threading module loaded before patching!')
import gevent.monkey; gevent.monkey.patch_thread()

Надеюсь, вы найдете мою историю отладки полезной:)

19 голосов
/ 27 августа 2013

Вы можете использовать это:

import sys
if 'threading' in sys.modules:
    del sys.modules['threading']
import gevent
import gevent.socket
import gevent.monkey
gevent.monkey.patch_all()
1 голос
/ 20 января 2012

У меня была похожая проблема с прототипом сценария gevent.

Обратный вызов Greenlet выполнялся нормально, и я синхронизировался с основным потоком с помощью g.join (). Для моей проблемы мне пришлось вызвать gevent.shutdown (), чтобы отключить (как я полагаю) концентратор. После того, как я вручную отключил цикл обработки событий, программа завершает работу без этой ошибки.

...