Ctrl-C аварийно завершает работу Python после импорта scipy.статистика

Я запускаю 64-разрядный Python 2.7.3 на 64-разрядном Win7. Я могу надежно разбить интерпретатор Python, сделав это:

>>> from scipy import stats
>>> import time
>>> time.sleep(3)

и нажатие Control-C во время сна. В KeyboardInterrupt не поднимается; переводчик аварий. Напечатано следующее:

forrtl: error (200): program aborting due to control-C event
Image              PC                Routine            Line        Source

libifcoremd.dll    00000000045031F8  Unknown               Unknown  Unknown
libifcoremd.dll    00000000044FC789  Unknown               Unknown  Unknown
libifcoremd.dll    00000000044E8583  Unknown               Unknown  Unknown
libifcoremd.dll    000000000445725D  Unknown               Unknown  Unknown
libifcoremd.dll    00000000044672A6  Unknown               Unknown  Unknown
kernel32.dll       0000000077B74AF3  Unknown               Unknown  Unknown
kernel32.dll       0000000077B3F56D  Unknown               Unknown  Unknown
ntdll.dll          0000000077C73281  Unknown               Unknown  Unknown

это делает невозможным прервать длительные вычисления scipy.

Googling для "forrtl" и тому подобное, я вижу предложения о том, что такая проблема связана с использованием Fortran библиотека, которая переопределяет обработку Ctrl-C. Я не вижу ошибки в scipy trackerbut, учитывая, что Scipy-это библиотека для использования С Python, я бы счел это ошибкой. Это нарушает обработку Python Ctrl-C. Есть ли обходной путь для этого?

Edit: следуя предложению @cgohlke, я попытался добавить свой собственный обработчик после импорта scipy. этот вопрос о подобной проблеме показывает, что добавление обработчика сигнала не работает. Я попытался использовать Windows API SetConsoleCtrlHandler функция через pywin32:

from scipy import stats
import win32api
def doSaneThing(sig, func=None):
    print "Here I am"
    raise KeyboardInterrupt
win32api.SetConsoleCtrlHandler(doSaneThing, 1)

после этого, нажав Ctrl-C печатает "вот я", но Python по-прежнему вылетает с ошибкой forrtl. Иногда я также получаю сообщение о том, что" функция ConsoleCtrlHandler не удалась", которая быстро исчезает.

если я запускаю это в IPython, я вижу обычную трассировку Python KeyboardInterrupt перед ошибкой forrtl. Я также вижу обычную трассировку Python, за которой следует ошибка forrtl, если я поднимаю некоторые другие ошибка вместо KeyboardInterrupt (например, ValueError):

ValueError                                Traceback (most recent call last)
<ipython-input-1-08defde66fcb> in doSaneThing(sig, func)
      3 def doSaneThing(sig, func=None):
      4     print "Here I am"
----> 5     raise ValueError
      6 win32api.SetConsoleCtrlHandler(doSaneThing, 1)

ValueError:
forrtl: error (200): program aborting due to control-C event
[etc.]

кажется, что бы ни делал базовый обработчик, он не просто захватывает Ctrl-C напрямую, но реагирует на условие ошибки (ValueError) и сам сбой. Есть ли способ устранить это?

6 ответов


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

и time модуль (строки 868-876) и _multiprocessing модуль (строкам 312-321) вызов SetConsoleCtrlHandler. В случае time модуль, его обработчик консольного управления задает событие Windows,hInterruptEvent. Для основной нити,time.sleep ждет этого события через WaitForSingleObject(hInterruptEvent, ul_millis), где ul_millis - это количество миллисекунд для сна, если не прерывается Ctrl+C. Так как обработчик, который вы установили, возвращает True на time обработчик модуля никогда не вызывается для установки hInterruptEvent, что означает sleep не может быть прервано.

я пробовал использовать imp.init_builtin('time') инициализации time модуль, но, видимо,SetConsoleCtrlHandler игнорирует второй вызов. Кажется, обработчик должен быть удален, а затем повторно вставлен. К сожалению,time module не экспортирует функцию для этого. Итак, как kludge, просто убедитесь, что вы импортируете time модуль после вы устанавливаете свой обработчик. С момента импорта scipy импорт time, вам нужно предварительно загрузить libifcoremd.dll с помощью ctypes чтобы получить обработчики в правильном порядке. Наконец, добавьте вызов thread.interrupt_main чтобы убедиться, что Python SIGINT обработчик вызывается[1].

например:

import os
import imp
import ctypes
import thread
import win32api

# Load the DLL manually to ensure its handler gets
# set before our handler.
basepath = imp.find_module('numpy')[1]
ctypes.CDLL(os.path.join(basepath, 'core', 'libmmd.dll'))
ctypes.CDLL(os.path.join(basepath, 'core', 'libifcoremd.dll'))

# Now set our handler for CTRL_C_EVENT. Other control event 
# types will chain to the next handler.
def handler(dwCtrlType, hook_sigint=thread.interrupt_main):
    if dwCtrlType == 0: # CTRL_C_EVENT
        hook_sigint()
        return 1 # don't chain to the next handler
    return 0 # chain to the next handler

win32api.SetConsoleCtrlHandler(handler, 1)

>>> import time
>>> from scipy import stats
>>> time.sleep(10)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyboardInterrupt

[1] interrupt_main звонки PyErr_SetInterrupt. Это поездки Handlers[SIGINT] и звонки Py_AddPendingCall добавить checksignals_witharg. В свою очередь это вызывает PyErr_CheckSignals. С Handlers[SIGINT] отключается, это вызывает Handlers[SIGINT].func. Наконец, если func is signal.default_int_handler, вы получите KeyboardInterrupt исключения.


я смог получить половину обходного пути, сделав это:

from scipy import stats
import win32api
def doSaneThing(sig, func=None):
    return True
win32api.SetConsoleCtrlHandler(doSaneThing, 1)

возврат true в обработчике останавливает цепочку обработчиков, так что вмешивающийся обработчик Fortran больше не вызывается. Однако этот обходной путь является частичным по двум причинам:

  1. он фактически не вызывает KeyboardInterrupt, что означает, что я не могу реагировать на него в коде Python. Это просто возвращает меня к подсказке.
  2. он не полностью прерывает вещи таким образом, что Ctrl-C обычно делает в Python. Если в новом сеансе Python я делаю time.sleep(3) и нажмите Ctrl-C, сон немедленно прерывается, и я получаю KeyboardInterrupt. При вышеуказанном обходном пути сон не прерывается, и управление возвращается в приглашение только после окончания времени сна.

тем не менее, это все же лучше, чем сбой всей сессии. Для меня это поднимает вопрос о том, почему SciPy (и любые другие библиотеки Python, которые полагаются на эти библиотеки Intel) не делают этого сами себя.

я оставляю этот ответ неприемлемым в надежде, что кто-то может предоставить реальное решение или обходной путь. Под "реальным" я подразумеваю, что нажатие Ctrl-C во время длительного вычисления SciPy должно работать так же, как и при загрузке SciPy. (Обратите внимание, что это не означает, что он должен работать немедленно. Не составляющей расчетов, как простой питон sum(xrange(100000000)) может не сразу прерываться на Ctrl-C, но, по крайней мере, когда они это делают, они вызывают KeyboardInterrupt.)


вот код для исправления dll, чтобы удалить вызов, который устанавливает обработчик Ctrl-C:

import os
import os.path
import imp
import hashlib

basepath = imp.find_module('numpy')[1]
ifcoremd = os.path.join(basepath, 'core', 'libifcoremd.dll')
with open(ifcoremd, 'rb') as dll:
    contents = dll.read()

m = hashlib.md5()
m.update(contents)

patch = {'7cae928b035bbdb90e4bfa725da59188': (0x317FC, '\xeb\x0b'),
  '0f86dcd44a1c2e217054c50262f727bf': (0x3fdd9, '\xeb\x10')}[m.hexdigest()]
if patch:
    contents = bytearray(contents)
    contents[patch[0]:patch[0] + len(patch[1])] = patch[1]
    with open(ifcoremd, 'wb') as dll:
        dll.write(contents)
else:
    print 'Unknown dll version'

EDIT: вот как я добавил патч для x64. Запустите python.exe в отладчик и установить точку останова для SetConsoleCtrlHandler пока вы не доберетесь до вызова, который хотите исправить:

Microsoft (R) Windows Debugger Version 6.12.0002.633 AMD64
Copyright (c) Microsoft Corporation. All rights reserved.

CommandLine: .\venv\Scripts\python.exe
...
0:000> .symfix
0:000> bp kernel32!SetConsoleCtrlHandler
0:000> g
Breakpoint 0 hit
KERNEL32!SetConsoleCtrlHandler:
00007ffc`c25742f0 ff252af00400    jmp     qword ptr [KERNEL32!_imp_SetConsoleCtrlHandler (00007ffc`c25c3320)] ds:00007ffc`c25c3320={KERNELBASE!SetConsoleCtrlHandler (00007ffc`bfa12e10)}
0:000> k 5
Child-SP          RetAddr           Call Site
00000000`007ef7a8 00000000`71415bb4 KERNEL32!SetConsoleCtrlHandler
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\WINDOWS\SYSTEM32\python27.dll -
00000000`007ef7b0 00000000`7035779f MSVCR90!signal+0x17c
00000000`007ef800 00000000`70237ea7 python27!PyOS_getsig+0x3f
00000000`007ef830 00000000`703546cc python27!Py_Main+0x21ce7
00000000`007ef880 00000000`7021698c python27!Py_InitializeEx+0x40c
0:000> g
Python 2.7.11 (v2.7.11:6d1b6a68f775, Dec  5 2015, 20:40:30) [MSC v.1500 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import numpy
...
Breakpoint 0 hit
KERNEL32!SetConsoleCtrlHandler:
00007ffc`c25742f0 ff252af00400    jmp     qword ptr [KERNEL32!_imp_SetConsoleCtrlHandler (00007ffc`c25c3320)] ds:00007ffc`c25c3320={KERNELBASE!SetConsoleCtrlHandler (00007ffc`bfa12e10)}
0:000> k 5
Child-SP          RetAddr           Call Site
00000000`007ec308 00000000`7023df6e KERNEL32!SetConsoleCtrlHandler
00000000`007ec310 00000000`70337877 python27!PyTime_DoubleToTimet+0x10ee
00000000`007ec350 00000000`7033766d python27!PyImport_IsScript+0x4f7
00000000`007ec380 00000000`70338bf2 python27!PyImport_IsScript+0x2ed
00000000`007ec3b0 00000000`703385a9 python27!PyImport_ImportModuleLevel+0xc82
0:000> g
...
>>> import scipy.stats
...
Breakpoint 0 hit
KERNEL32!SetConsoleCtrlHandler:
00007ffc`c25742f0 ff252af00400    jmp     qword ptr [KERNEL32!_imp_SetConsoleCtrlHandler (00007ffc`c25c3320)] ds:00007ffc`c25c3320={KERNELBASE!SetConsoleCtrlHandler (00007ffc`bfa12e10)}
0:000> k 5
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Users\kevin\Documents\venv\lib\site-packages\numpy\core\libifcoremd.dll -
Child-SP          RetAddr           Call Site
00000000`007ed818 00007ffc`828309eb KERNEL32!SetConsoleCtrlHandler
00000000`007ed820 00007ffc`828dfa44 libifcoremd!GETEXCEPTIONPTRSQQ+0xdb
00000000`007ed880 00007ffc`828e59d7 libifcoremd!for_lt_ne+0xc274
00000000`007ed8b0 00007ffc`828e5aff libifcoremd!for_lt_ne+0x12207
00000000`007ed8e0 00007ffc`c292ddc7 libifcoremd!for_lt_ne+0x1232f
0:000> ub  00007ffc`828309eb
libifcoremd!GETEXCEPTIONPTRSQQ+0xbb:
00007ffc`828309cb 00e8            add     al,ch
00007ffc`828309cd df040b          fild    word ptr [rbx+rcx]
00007ffc`828309d0 0033            add     byte ptr [rbx],dh
00007ffc`828309d2 c9              leave
00007ffc`828309d3 ff15bf390e00    call    qword ptr [libifcoremd!for_lt_ne+0x40bc8 (00007ffc`82914398)]
00007ffc`828309d9 488d0d00efffff  lea     rcx,[libifcoremd!for_rtl_finish_+0x20 (00007ffc`8282f8e0)]
00007ffc`828309e0 ba01000000      mov     edx,1
00007ffc`828309e5 ff158d390e00    call    qword ptr [libifcoremd!for_lt_ne+0x40ba8 (00007ffc`82914378)]

мы исправим lea инструкция с родственником jmp (т. е. 0xeb с последующим количеством байтов для перехода)

0:000> ? 00007ffc`828309eb - 00007ffc`828309d9
Evaluate expression: 18 = 00000000`00000012
0:000> f 00007ffc`828309d9 L2 eb 10
Filled 0x2 bytes
0:000> ub  00007ffc`828309eb
libifcoremd!GETEXCEPTIONPTRSQQ+0xbe:
00007ffc`828309ce 040b            add     al,0Bh
00007ffc`828309d0 0033            add     byte ptr [rbx],dh
00007ffc`828309d2 c9              leave
00007ffc`828309d3 ff15bf390e00    call    qword ptr [libifcoremd!for_lt_ne+0x40bc8 (00007ffc`82914398)]
00007ffc`828309d9 eb10            jmp     libifcoremd!GETEXCEPTIONPTRSQQ+0xdb (00007ffc`828309eb)
00007ffc`828309db 0d00efffff      or      eax,0FFFFEF00h
00007ffc`828309e0 ba01000000      mov     edx,1
00007ffc`828309e5 ff158d390e00    call    qword ptr [libifcoremd!for_lt_ne+0x40ba8 (00007ffc`82914378)]

я не знаю как .DLL-файл отображается в этом процессе, поэтому я просто буду искать 0d 00 ef ff ff в файле с помощью HEX-редактор. Это уникальный хит, поэтому мы можем рассчитать местоположение в.dll для исправления.

0:000> db  00007ffc`828309d0
00007ffc`828309d0  00 33 c9 ff 15 bf 39 0e-00 eb 10 0d 00 ef ff ff  .3....9.........
00007ffc`828309e0  ba 01 00 00 00 ff 15 8d-39 0e 00 48 8d 0d 0e 9c  ........9..H....
00007ffc`828309f0  09 00 e8 09 2e 0a 00 48-8d 0d 32 9f 09 00 e8 fd  .......H..2.....
00007ffc`82830a00  2d 0a 00 48 8d 0d ca ee-0e 00 e8 51 90 00 00 85  -..H.......Q....
00007ffc`82830a10  c0 0f 85 88 02 00 00 e8-38 fa 0a 00 ff 15 4e 39  ........8.....N9
00007ffc`82830a20  0e 00 89 c1 e8 d7 2d 0a-00 48 8d 05 f8 be 11 00  ......-..H......
00007ffc`82830a30  45 32 e4 c7 05 0b 4a 13-00 00 00 00 00 41 bd 01  E2....J......A..
00007ffc`82830a40  00 00 00 48 89 05 06 4a-13 00 ff 15 30 39 0e 00  ...H...J....09..
0:000> ? 00007ffc`828309d9 -  00007ffc`828309d0
Evaluate expression: 9 = 00000000`00000009
0:000> ? 00007ffc`828309d9 -  00007ffc`828309d0 + 3FDD0
Evaluate expression: 261593 = 00000000`0003fdd9
0:000>

хорошо, я исправил dll в 0x3fdd9. Давайте посмотрим, как это выглядит сейчас:

Microsoft (R) Windows Debugger Version 6.12.0002.633 AMD64
Copyright (c) Microsoft Corporation. All rights reserved.

CommandLine: .\venv\Scripts\python.exe
...
0:000> bp libifcoremd!GETEXCEPTIONPTRSQQ+c9
Bp expression 'libifcoremd!GETEXCEPTIONPTRSQQ+c9' could not be resolved, adding deferred bp
0:000> g
Python 2.7.11 (v2.7.11:6d1b6a68f775, Dec  5 2015, 20:40:30) [MSC v.1500 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import scipy.stats
...
Breakpoint 0 hit
libifcoremd!GETEXCEPTIONPTRSQQ+0xc9:
00007ffc`845909d9 eb10            jmp     libifcoremd!GETEXCEPTIONPTRSQQ+0xdb (00007ffc`845909eb)
0:000> u
libifcoremd!GETEXCEPTIONPTRSQQ+0xc9:
00007ffc`845909d9 eb10            jmp     libifcoremd!GETEXCEPTIONPTRSQQ+0xdb (00007ffc`845909eb)
00007ffc`845909db 0d00efffff      or      eax,0FFFFEF00h
00007ffc`845909e0 ba01000000      mov     edx,1
00007ffc`845909e5 ff158d390e00    call    qword ptr [libifcoremd!for_lt_ne+0x40ba8 (00007ffc`84674378)]
00007ffc`845909eb 488d0d0e9c0900  lea     rcx,[libifcoremd!GETHANDLEQQ (00007ffc`8462a600)]
00007ffc`845909f2 e8092e0a00      call    libifcoremd!for_lt_ne+0x30 (00007ffc`84633800)
00007ffc`845909f7 488d0d329f0900  lea     rcx,[libifcoremd!GETUNITQQ (00007ffc`8462a930)]
00007ffc`845909fe e8fd2d0a00      call    libifcoremd!for_lt_ne+0x30 (00007ffc`84633800)
0:000>

так теперь былиjmping над нажатием аргументов в стеке и вызове функции. Таким образом, его обработчик Ctrl-C не будет установлен.


настройка переменной среды FOR_DISABLE_CONSOLE_CTRL_HANDLER до 1 кажется, исправить проблему.

редактировать: While Ctrl+C больше не сбой python, он также не может остановить текущий расчет.


решение: патч SetControlCtrlHandler

import ctypes
SetConsoleCtrlHandler_body_new = b'\xC2\x08\x00' if ctypes.sizeof(ctypes.c_void_p) == 4 else b'\xC3'
try: SetConsoleCtrlHandler_body = (lambda kernel32: (lambda pSetConsoleCtrlHandler:
    kernel32.VirtualProtect(pSetConsoleCtrlHandler, ctypes.c_size_t(1), 0x40, ctypes.byref(ctypes.c_uint32(0)))
    and (ctypes.c_char * 3).from_address(pSetConsoleCtrlHandler.value)
)(ctypes.cast(kernel32.SetConsoleCtrlHandler, ctypes.c_void_p)))(ctypes.windll.kernel32)
except: SetConsoleCtrlHandler_body = None
if SetConsoleCtrlHandler_body:
    SetConsoleCtrlHandler_body_old = SetConsoleCtrlHandler_body[0:len(SetConsoleCtrlHandler_body_new)]
    SetConsoleCtrlHandler_body[0:len(SetConsoleCtrlHandler_body_new)] = SetConsoleCtrlHandler_body_new
try:
    import scipy.stats
finally:
    if SetConsoleCtrlHandler_body:
        SetConsoleCtrlHandler_body[0:len(SetConsoleCtrlHandler_body_new)] = SetConsoleCtrlHandler_body_old

попробовать

import os
os.environ['FOR_IGNORE_EXCEPTIONS'] = '1'
import scipy.stats