##// END OF EJS Templates
Bind Ctrl-C to kill process, when in process execution.
gvaroquaux -
Show More
@@ -0,0 +1,168 b''
1 # Addapted from killableprocess.py.
2 #______________________________________________________________________________
3 #
4 # killableprocess - subprocesses which can be reliably killed
5 #
6 # Parts of this module are copied from the subprocess.py file contained
7 # in the Python distribution.
8 #
9 # Copyright (c) 2003-2004 by Peter Astrand <astrand@lysator.liu.se>
10 #
11 # Additions and modifications written by Benjamin Smedberg
12 # <benjamin@smedbergs.us> are Copyright (c) 2006 by the Mozilla Foundation
13 # <http://www.mozilla.org/>
14 #
15 # By obtaining, using, and/or copying this software and/or its
16 # associated documentation, you agree that you have read, understood,
17 # and will comply with the following terms and conditions:
18 #
19 # Permission to use, copy, modify, and distribute this software and
20 # its associated documentation for any purpose and without fee is
21 # hereby granted, provided that the above copyright notice appears in
22 # all copies, and that both that copyright notice and this permission
23 # notice appear in supporting documentation, and that the name of the
24 # author not be used in advertising or publicity pertaining to
25 # distribution of the software without specific, written prior
26 # permission.
27 #
28 # THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
29 # INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
30 # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR
31 # CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
32 # OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
33 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
34 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
35
36 r"""killableprocess - Subprocesses which can be reliably killed
37
38 This module is a subclass of the builtin "subprocess" module. It allows
39 processes that launch subprocesses to be reliably killed on Windows (via the Popen.kill() method.
40
41 It also adds a timeout argument to Wait() for a limited period of time before
42 forcefully killing the process.
43
44 Note: On Windows, this module requires Windows 2000 or higher (no support for
45 Windows 95, 98, or NT 4.0). It also requires ctypes, which is bundled with
46 Python 2.5+ or available from http://python.net/crew/theller/ctypes/
47 """
48
49 import subprocess
50 from subprocess import PIPE
51 import sys
52 import os
53 import time
54 import types
55
56 try:
57 from subprocess import CalledProcessError
58 except ImportError:
59 # Python 2.4 doesn't implement CalledProcessError
60 class CalledProcessError(Exception):
61 """This exception is raised when a process run by check_call() returns
62 a non-zero exit status. The exit status will be stored in the
63 returncode attribute."""
64 def __init__(self, returncode, cmd):
65 self.returncode = returncode
66 self.cmd = cmd
67 def __str__(self):
68 return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode)
69
70 mswindows = (sys.platform == "win32")
71
72 if mswindows:
73 import winprocess
74 else:
75 import signal
76
77 if not mswindows:
78 def DoNothing(*args):
79 pass
80
81 class Popen(subprocess.Popen):
82 if not mswindows:
83 # Override __init__ to set a preexec_fn
84 def __init__(self, *args, **kwargs):
85 if len(args) >= 7:
86 raise Exception("Arguments preexec_fn and after must be passed by keyword.")
87
88 real_preexec_fn = kwargs.pop("preexec_fn", None)
89 def setpgid_preexec_fn():
90 os.setpgid(0, 0)
91 if real_preexec_fn:
92 apply(real_preexec_fn)
93
94 kwargs['preexec_fn'] = setpgid_preexec_fn
95
96 subprocess.Popen.__init__(self, *args, **kwargs)
97
98 if mswindows:
99 def _execute_child(self, args, executable, preexec_fn, close_fds,
100 cwd, env, universal_newlines, startupinfo,
101 creationflags, shell,
102 p2cread, p2cwrite,
103 c2pread, c2pwrite,
104 errread, errwrite):
105 if not isinstance(args, types.StringTypes):
106 args = subprocess.list2cmdline(args)
107
108 if startupinfo is None:
109 startupinfo = winprocess.STARTUPINFO()
110
111 if None not in (p2cread, c2pwrite, errwrite):
112 startupinfo.dwFlags |= winprocess.STARTF_USESTDHANDLES
113
114 startupinfo.hStdInput = int(p2cread)
115 startupinfo.hStdOutput = int(c2pwrite)
116 startupinfo.hStdError = int(errwrite)
117 if shell:
118 startupinfo.dwFlags |= winprocess.STARTF_USESHOWWINDOW
119 startupinfo.wShowWindow = winprocess.SW_HIDE
120 comspec = os.environ.get("COMSPEC", "cmd.exe")
121 args = comspec + " /c " + args
122
123 # We create a new job for this process, so that we can kill
124 # the process and any sub-processes
125 self._job = winprocess.CreateJobObject()
126
127 creationflags |= winprocess.CREATE_SUSPENDED
128 creationflags |= winprocess.CREATE_UNICODE_ENVIRONMENT
129
130 hp, ht, pid, tid = winprocess.CreateProcess(
131 executable, args,
132 None, None, # No special security
133 1, # Must inherit handles!
134 creationflags,
135 winprocess.EnvironmentBlock(env),
136 cwd, startupinfo)
137
138 self._child_created = True
139 self._handle = hp
140 self._thread = ht
141 self.pid = pid
142
143 winprocess.AssignProcessToJobObject(self._job, hp)
144 winprocess.ResumeThread(ht)
145
146 if p2cread is not None:
147 p2cread.Close()
148 if c2pwrite is not None:
149 c2pwrite.Close()
150 if errwrite is not None:
151 errwrite.Close()
152
153 def kill(self, group=True):
154 """Kill the process. If group=True, all sub-processes will also be killed."""
155 if mswindows:
156 if group:
157 winprocess.TerminateJobObject(self._job, 127)
158 else:
159 winprocess.TerminateProcess(self._handle, 127)
160 self.returncode = 127
161 else:
162 if group:
163 os.killpg(self.pid, signal.SIGKILL)
164 else:
165 os.kill(self.pid, signal.SIGKILL)
166 self.returncode = -9
167
168
@@ -15,7 +15,7 b' __docformat__ = "restructuredtext en"'
15 15 #-------------------------------------------------------------------------------
16 16 # Imports
17 17 #-------------------------------------------------------------------------------
18 from subprocess import Popen, PIPE
18 from killable_process import Popen, PIPE
19 19 from threading import Thread
20 20 from time import sleep
21 21
@@ -28,6 +28,7 b' from console_widget import ConsoleWidget'
28 28 import __builtin__
29 29 from time import sleep
30 30 import sys
31 import signal
31 32
32 33 from threading import Lock
33 34
@@ -49,19 +50,45 b' class WxController(PrefilterFrontEnd, ConsoleWidget):'
49 50 output_prompt = \
50 51 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02%i\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
51 52
53 # Print debug info on what is happening to the console.
52 54 debug = True
53 55
56 # The title of the terminal, as captured through the ANSI escape
57 # sequences.
58
59 def _set_title(self, title):
60 return self.Parent.SetTitle(title)
61
62 def _get_title(self):
63 return self.Parent.GetTitle()
64
65 title = property(_get_title, _set_title)
66
67 #--------------------------------------------------------------------------
68 # Private Attributes
69 #--------------------------------------------------------------------------
70
71 # A flag governing the behavior of the input. Can be:
72 #
73 # 'readline' for readline-like behavior with a prompt
74 # and an edit buffer.
75 # 'subprocess' for sending the raw input directly to a
76 # subprocess.
77 # 'buffering' for buffering of the input, that will be used
78 # when the input state switches back to another state.
79 _input_state = 'readline'
80
54 81 # Attribute to store reference to the pipes of a subprocess, if we
55 82 # are running any.
56 running_process = False
83 _running_process = False
57 84
58 85 # A queue for writing fast streams to the screen without flooding the
59 86 # event loop
60 write_buffer = []
87 _out_buffer = []
61 88
62 # A lock to lock the write_buffer to make sure we don't empty it
89 # A lock to lock the _out_buffer to make sure we don't empty it
63 90 # while it is being swapped
64 write_buffer_lock = Lock()
91 _out_buffer_lock = Lock()
65 92
66 93 #--------------------------------------------------------------------------
67 94 # Public API
@@ -147,6 +174,11 b' class WxController(PrefilterFrontEnd, ConsoleWidget):'
147 174 print >>sys.__stdout__, completions
148 175
149 176
177 def new_prompt(self, prompt):
178 self._input_state = 'readline'
179 ConsoleWidget.new_prompt(self, prompt)
180
181
150 182 def raw_input(self, prompt):
151 183 """ A replacement from python's raw_input.
152 184 """
@@ -161,10 +193,12 b' class WxController(PrefilterFrontEnd, ConsoleWidget):'
161 193 wx.Yield()
162 194 sleep(0.1)
163 195 self._on_enter = self.__old_on_enter
196 self._input_state = 'buffering'
164 197 return self.get_current_edit_buffer().rstrip('\n')
165 198
166 199
167 200 def execute(self, python_string, raw_string=None):
201 self._input_state = 'buffering'
168 202 self.CallTipCancel()
169 203 self._cursor = wx.BusyCursor()
170 204 if raw_string is None:
@@ -197,15 +231,15 b' class WxController(PrefilterFrontEnd, ConsoleWidget):'
197 231
198 232
199 233 def system_call(self, command_string):
200 self.running_process = True
201 self.running_process = PipedProcess(command_string,
234 self._input_state = 'subprocess'
235 self._running_process = PipedProcess(command_string,
202 236 out_callback=self.buffered_write,
203 237 end_callback = self._end_system_call)
204 self.running_process.start()
238 self._running_process.start()
205 239 # XXX: another one of these polling loops to have a blocking
206 240 # call
207 241 wx.Yield()
208 while self.running_process:
242 while self._running_process:
209 243 wx.Yield()
210 244 sleep(0.1)
211 245 # Be sure to flush the buffer.
@@ -219,32 +253,13 b' class WxController(PrefilterFrontEnd, ConsoleWidget):'
219 253 This can be called outside of the main loop, in separate
220 254 threads.
221 255 """
222 self.write_buffer_lock.acquire()
223 self.write_buffer.append(text)
224 self.write_buffer_lock.release()
256 self._out_buffer_lock.acquire()
257 self._out_buffer.append(text)
258 self._out_buffer_lock.release()
225 259 if not self._buffer_flush_timer.IsRunning():
226 260 self._buffer_flush_timer.Start(100) # milliseconds
227 261
228 262
229 def _end_system_call(self):
230 """ Called at the end of a system call.
231 """
232 self.running_process = False
233
234
235 def _buffer_flush(self, event):
236 """ Called by the timer to flush the write buffer.
237
238 This is always called in the mainloop, by the wx timer.
239 """
240 self.write_buffer_lock.acquire()
241 write_buffer = self.write_buffer
242 self.write_buffer = []
243 self.write_buffer_lock.release()
244 self.write(''.join(write_buffer))
245 self._buffer_flush_timer.Stop()
246
247
248 263 def show_traceback(self):
249 264 start_line = self.GetCurrentLine()
250 265 PrefilterFrontEnd.show_traceback(self)
@@ -261,14 +276,27 b' class WxController(PrefilterFrontEnd, ConsoleWidget):'
261 276 """ Capture the character events, let the parent
262 277 widget handle them, and put our logic afterward.
263 278 """
279 print >>sys.__stderr__, event.KeyCode
264 280 current_line_number = self.GetCurrentLine()
265 if self.running_process and event.KeyCode<256 \
281 if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown():
282 # Capture Control-C
283 if self._input_state == 'subprocess':
284 if self.debug:
285 print >>sys.__stderr__, 'Killing running process'
286 self._running_process.process.kill()
287 elif self._input_state == 'buffering':
288 if self.debug:
289 print >>sys.__stderr__, 'Raising KeyboardException'
290 raise KeyboardException
291 # XXX: We need to make really sure we
292 # get back to a prompt.
293 elif self._input_state == 'subprocess' and event.KeyCode<256 \
266 294 and event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
267 # We are running a process, let us not be too clever.
295 # We are running a process, we redirect keys.
268 296 ConsoleWidget._on_key_down(self, event, skip=skip)
269 297 if self.debug:
270 298 print >>sys.__stderr__, chr(event.KeyCode)
271 self.running_process.process.stdin.write(chr(event.KeyCode))
299 self._running_process.process.stdin.write(chr(event.KeyCode))
272 300 elif event.KeyCode in (ord('('), 57):
273 301 # Calltips
274 302 event.Skip()
@@ -320,18 +348,32 b' class WxController(PrefilterFrontEnd, ConsoleWidget):'
320 348 else:
321 349 ConsoleWidget._on_key_up(self, event, skip=skip)
322 350
351
323 352 def _on_enter(self):
324 353 if self.debug:
325 354 print >>sys.__stdout__, repr(self.get_current_edit_buffer())
326 355 PrefilterFrontEnd._on_enter(self)
327 356
328 def _set_title(self, title):
329 return self.Parent.SetTitle(title)
330 357
331 def _get_title(self):
332 return self.Parent.GetTitle()
358 def _end_system_call(self):
359 """ Called at the end of a system call.
360 """
361 print >>sys.__stderr__, 'End of system call'
362 self._input_state = 'buffering'
363 self._running_process = False
333 364
334 title = property(_get_title, _set_title)
365
366 def _buffer_flush(self, event):
367 """ Called by the timer to flush the write buffer.
368
369 This is always called in the mainloop, by the wx timer.
370 """
371 self._out_buffer_lock.acquire()
372 _out_buffer = self._out_buffer
373 self._out_buffer = []
374 self._out_buffer_lock.release()
375 self.write(''.join(_out_buffer))
376 self._buffer_flush_timer.Stop()
335 377
336 378
337 379 if __name__ == '__main__':
General Comments 0
You need to be logged in to leave comments. Login now