try catch python scipy keyboardinterrupt

try - python catch keyboard interrupt



Ctrl-C bloquea Python después de importar scipy.stats (6)

Estoy ejecutando Python 2.7.3 de 64 bits en Win7 de 64 bits. Puedo hacer fallar al intérprete de Python de manera confiable al hacer esto:

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

y presionando Control-C durante el sueño. No se levanta un KeyboardInterrupt; el intérprete se estrella. Se imprime lo siguiente:

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

Esto hace que sea imposible interrumpir los cálculos scipy de larga ejecución.

Buscando en Google para "forrtl" y similares, veo sugerencias de que este tipo de problema se debe al uso de una biblioteca Fortran que anula el manejo de Ctrl-C. No veo un error en el rastreador de Scipy, pero dado que Scipy es una biblioteca para usar con Python, consideraría esto un error. Se rompe el manejo de Ctrl-C por parte de Python. ¿Hay alguna solución para esto?

Edición: Siguiendo la sugerencia de @cgohlke, intenté agregar mi propio controlador después de importar scipy. Esta pregunta sobre un problema relacionado muestra que agregar un controlador de señales no funciona. Intenté usar la función SetConsoleCtrlHandler API de Windows a través de pywin32:

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

Después de esto, al presionar Ctrl-C se imprime "Aquí estoy", pero Python aún falla con el error forrtl. A veces también recibo un mensaje que dice "La función ConsoleCtrlHandler falló", que desaparece rápidamente.

Si ejecuto esto en IPython, puedo ver un seguimiento normal de Python KeyboardInterrupt antes del error forrtl. También veo un rastreo de Python normal seguido del error forrtl si levanto algún otro error en lugar de KeyboardInterrupt (por ejemplo, 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.]

Parece que lo que sea que esté haciendo el controlador subyacente, no solo atrapa Ctrl-C directamente, sino que reacciona ante la condición de error (ValueError) y se bloquea. ¿Hay alguna forma de eliminar esto?


Solución: parche 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


Aquí hay un código para parchear la dll para eliminar la llamada que instala el controlador 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''

EDITAR: Así es como agregué un parche para el x64. Ejecute python.exe en el depurador y establezca un punto de interrupción para SetConsoleCtrlHandler hasta que llegue a la llamada que desea parchear:

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)]

jmp la instrucción lea con un jmp relativo (que es 0xeb seguido del número de bytes para saltar)

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)]

No sé cómo se asigna el archivo .dll en este proceso, así que solo buscaré 0d 00 ef ff ff en el archivo con un editor hexadecimal. Es un hit único, por lo que podemos calcular la ubicación en la .dll para parchear.

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>

Ok, he parcheado el dll en 0x3fdd9 . Veamos cómo se ve ahora:

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>

Así que ahora estamos jmp los argumentos en la pila y la función call. Por lo tanto, su controlador Ctrl-C no se instalará.


Aquí hay una variación de su solución publicada que puede funcionar. Tal vez haya una mejor manera de resolver este problema, o tal vez incluso evitarlo todo al configurar una variable de entorno que le indique a la DLL que omita la instalación de un controlador. Esperemos que esto te ayude hasta que encuentres una mejor manera.

Tanto el módulo de time (líneas 868-876) como el módulo de _multiprocessing (líneas 312-321) llaman a SetConsoleCtrlHandler . En el caso del módulo de time , su controlador de control de consola establece un evento de Windows, hInterruptEvent . Para el subproceso principal, time.sleep espera este evento a través de WaitForSingleObject(hInterruptEvent, ul_millis) , donde ul_millis es el número de milisegundos para dormir a menos que sea interrumpido por Ctrl + C. Dado que el controlador que ha instalado devuelve True , el controlador del módulo de time nunca recibe una llamada para establecer hInterruptEvent , lo que significa que la sleep no puede interrumpirse.

Intenté usar imp.init_builtin(''time'') para reinicializar el módulo de time , pero aparentemente SetConsoleCtrlHandler ignora la segunda llamada. Parece que el manejador debe ser eliminado y luego reinsertado. Desafortunadamente, el módulo de time no exporta una función para eso. Entonces, como un kludge, solo asegúrese de importar el módulo de time después de instalar su controlador. Dado que la importación de scipy también importa el time , debe precargar libifcoremd.dll usando ctypes para que los manejadores ctypes en el orden correcto. Finalmente, agregue una llamada a thread.interrupt_main para asegurarse de que el controlador SIGINT de Python sea llamado [1] .

Por ejemplo:

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 llama a PyErr_SetInterrupt . Esto dispara a los Handlers[SIGINT] y llama a Py_AddPendingCall para agregar Py_AddPendingCall . A su vez esto llama a PyErr_CheckSignals . Dado que los Handlers[SIGINT] se han disparado, esto llama a los Handlers[SIGINT].func . Finalmente, si func es signal.default_int_handler , obtendrá una excepción KeyboardInterrupt .


Establecer la variable de entorno FOR_DISABLE_CONSOLE_CTRL_HANDLER en 1 parece solucionar el problema .

EDITAR : Si bien Ctrl + C ya no bloquea Python, tampoco puede detener el cálculo actual.


He podido conseguir una media solución al hacer esto:

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

El retorno verdadero en el controlador detiene la cadena de controladores para que ya no se llame al controlador Fortran entrometido. Sin embargo, esta solución es solo parcial, por dos razones:

  1. En realidad, no genera un Interruptor de teclado, lo que significa que no puedo reaccionar con el código Python. Simplemente me deja de nuevo en el indicador.
  2. No interrumpe completamente las cosas como lo hace normalmente Ctrl-C en Python. Si en una nueva sesión de Python hago un time.sleep(3) y time.sleep(3) Ctrl-C, el sueño se time.sleep(3) y time.sleep(3) un time.sleep(3) teclado. Con la solución anterior, el sueño no se interrumpe, y el control regresa al indicador solo después de que el tiempo de suspensión haya finalizado.

No obstante, esto es aún mejor que bloquear toda la sesión. Para mí, esto plantea la pregunta de por qué SciPy (y cualquier otra biblioteca de Python que se basa en estas bibliotecas de Intel) no lo hacen ellos mismos.

Dejo esta respuesta sin aceptar con la esperanza de que alguien pueda proporcionar una solución real o una solución alternativa. Por "real" me refiero a que presionar Ctrl-C durante un cálculo de SciPy de larga ejecución debería funcionar como lo hace cuando no se carga SciPy. (Tenga en cuenta que esto no significa que tenga que funcionar de inmediato. Es posible que los cálculos que no son de SciPy como la sum(xrange(100000000)) Python sum(xrange(100000000)) no se aborten inmediatamente en Ctrl-C, pero al menos cuando lo hacen, levantan un Interruptor de teclado).


Tratar

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