Friday, March 9, 2012

Please let me interrupt you

The standard way of interrupting a long running script in a Python shell is to press Ctrl+C which signals a keyboard interrupt and the interpreter raises a KeyboardInterrupt exception.  I was trying for a while to find a way to raise a KeyboardInterrupt execption at the remote python engine without much success.  The way IDLE handles this, is by running a separate thread in the server, which waits for such a signal to arrive from IDLE and then uses the interrupt_main function of the threading module.  I thought that having a separate thread just for this purpose would slow down the execution of scripts and the PyScripter solution of reinitializing the engine was good enough.

Resently, a user has posted an issue about this at PyScripter’s bug tracker and I had another look at the problem.  So I found a solution using Windows API and avoiding run overheads,  For the benefit of Windows hackers I show the code below:

function CtrlHandler( fdwCtrlType : dword): LongBool; stdcall;
begin
  Result := True;
end;

AttachConsole := GetProcAddress (GetModuleHandle ('kernel32.dll'), 'AttachConsole');
if Assigned(AttachConsole) then
try
  OSCheck(AttachConsole(fRemotePython.ServerProcess.ProcessInfo.dwProcessId));
  OSCheck(SetConsoleCtrlHandler(@CtrlHandler, True));
  try
    OSCheck(GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0));
    Sleep(100);
  finally
    OSCheck(SetConsoleCtrlHandler(@CtrlHandler, False));
    OSCheck(FreeConsole);
  end;
except
end;

Note that the CtrlHandler is needed to avoid killing PyScripter itself.  It took me a while to work out this piece of code since, I could not find such code sample (only pieces of the puzzle) in the Internet.  The only solution I found involved injecting code into the server process.

So starting from the forthcoming version 2.5, the Abort command will result in a KeyboardInterrupt exeption in script is run or debugged.  So there is no need to reinitialize the engine to stop a script.  After the script stops you can enter the Post-Mortem mode to see what the script was doing when it received the Keyboard interrupt.

I could have added a shortcut Ctrl+C to the interpreter for raising a Keyboard interrupt, but the Ctrl-C shortcut in GUI applications is associated with the copy command.  So the Abort command will achieve the same thing as pressing Ctrl+C in Python Shell.  When a scrip is stopped at a breakpoint, the Abort command will work as before.

No comments: