##// 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 # Imports
16 # Imports
17 #-------------------------------------------------------------------------------
17 #-------------------------------------------------------------------------------
18 from subprocess import Popen, PIPE
18 from killable_process import Popen, PIPE
19 from threading import Thread
19 from threading import Thread
20 from time import sleep
20 from time import sleep
21
21
@@ -28,6 +28,7 b' from console_widget import ConsoleWidget'
28 import __builtin__
28 import __builtin__
29 from time import sleep
29 from time import sleep
30 import sys
30 import sys
31 import signal
31
32
32 from threading import Lock
33 from threading import Lock
33
34
@@ -49,19 +50,45 b' class WxController(PrefilterFrontEnd, ConsoleWidget):'
49 output_prompt = \
50 output_prompt = \
50 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02%i\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
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 debug = True
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 # Attribute to store reference to the pipes of a subprocess, if we
81 # Attribute to store reference to the pipes of a subprocess, if we
55 # are running any.
82 # are running any.
56 running_process = False
83 _running_process = False
57
84
58 # A queue for writing fast streams to the screen without flooding the
85 # A queue for writing fast streams to the screen without flooding the
59 # event loop
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 # while it is being swapped
90 # while it is being swapped
64 write_buffer_lock = Lock()
91 _out_buffer_lock = Lock()
65
92
66 #--------------------------------------------------------------------------
93 #--------------------------------------------------------------------------
67 # Public API
94 # Public API
@@ -147,6 +174,11 b' class WxController(PrefilterFrontEnd, ConsoleWidget):'
147 print >>sys.__stdout__, completions
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 def raw_input(self, prompt):
182 def raw_input(self, prompt):
151 """ A replacement from python's raw_input.
183 """ A replacement from python's raw_input.
152 """
184 """
@@ -161,10 +193,12 b' class WxController(PrefilterFrontEnd, ConsoleWidget):'
161 wx.Yield()
193 wx.Yield()
162 sleep(0.1)
194 sleep(0.1)
163 self._on_enter = self.__old_on_enter
195 self._on_enter = self.__old_on_enter
196 self._input_state = 'buffering'
164 return self.get_current_edit_buffer().rstrip('\n')
197 return self.get_current_edit_buffer().rstrip('\n')
165
198
166
199
167 def execute(self, python_string, raw_string=None):
200 def execute(self, python_string, raw_string=None):
201 self._input_state = 'buffering'
168 self.CallTipCancel()
202 self.CallTipCancel()
169 self._cursor = wx.BusyCursor()
203 self._cursor = wx.BusyCursor()
170 if raw_string is None:
204 if raw_string is None:
@@ -197,15 +231,15 b' class WxController(PrefilterFrontEnd, ConsoleWidget):'
197
231
198
232
199 def system_call(self, command_string):
233 def system_call(self, command_string):
200 self.running_process = True
234 self._input_state = 'subprocess'
201 self.running_process = PipedProcess(command_string,
235 self._running_process = PipedProcess(command_string,
202 out_callback=self.buffered_write,
236 out_callback=self.buffered_write,
203 end_callback = self._end_system_call)
237 end_callback = self._end_system_call)
204 self.running_process.start()
238 self._running_process.start()
205 # XXX: another one of these polling loops to have a blocking
239 # XXX: another one of these polling loops to have a blocking
206 # call
240 # call
207 wx.Yield()
241 wx.Yield()
208 while self.running_process:
242 while self._running_process:
209 wx.Yield()
243 wx.Yield()
210 sleep(0.1)
244 sleep(0.1)
211 # Be sure to flush the buffer.
245 # Be sure to flush the buffer.
@@ -219,32 +253,13 b' class WxController(PrefilterFrontEnd, ConsoleWidget):'
219 This can be called outside of the main loop, in separate
253 This can be called outside of the main loop, in separate
220 threads.
254 threads.
221 """
255 """
222 self.write_buffer_lock.acquire()
256 self._out_buffer_lock.acquire()
223 self.write_buffer.append(text)
257 self._out_buffer.append(text)
224 self.write_buffer_lock.release()
258 self._out_buffer_lock.release()
225 if not self._buffer_flush_timer.IsRunning():
259 if not self._buffer_flush_timer.IsRunning():
226 self._buffer_flush_timer.Start(100) # milliseconds
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 def show_traceback(self):
263 def show_traceback(self):
249 start_line = self.GetCurrentLine()
264 start_line = self.GetCurrentLine()
250 PrefilterFrontEnd.show_traceback(self)
265 PrefilterFrontEnd.show_traceback(self)
@@ -261,14 +276,27 b' class WxController(PrefilterFrontEnd, ConsoleWidget):'
261 """ Capture the character events, let the parent
276 """ Capture the character events, let the parent
262 widget handle them, and put our logic afterward.
277 widget handle them, and put our logic afterward.
263 """
278 """
279 print >>sys.__stderr__, event.KeyCode
264 current_line_number = self.GetCurrentLine()
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 and event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
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 ConsoleWidget._on_key_down(self, event, skip=skip)
296 ConsoleWidget._on_key_down(self, event, skip=skip)
269 if self.debug:
297 if self.debug:
270 print >>sys.__stderr__, chr(event.KeyCode)
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 elif event.KeyCode in (ord('('), 57):
300 elif event.KeyCode in (ord('('), 57):
273 # Calltips
301 # Calltips
274 event.Skip()
302 event.Skip()
@@ -320,18 +348,32 b' class WxController(PrefilterFrontEnd, ConsoleWidget):'
320 else:
348 else:
321 ConsoleWidget._on_key_up(self, event, skip=skip)
349 ConsoleWidget._on_key_up(self, event, skip=skip)
322
350
351
323 def _on_enter(self):
352 def _on_enter(self):
324 if self.debug:
353 if self.debug:
325 print >>sys.__stdout__, repr(self.get_current_edit_buffer())
354 print >>sys.__stdout__, repr(self.get_current_edit_buffer())
326 PrefilterFrontEnd._on_enter(self)
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):
358 def _end_system_call(self):
332 return self.Parent.GetTitle()
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 if __name__ == '__main__':
379 if __name__ == '__main__':
General Comments 0
You need to be logged in to leave comments. Login now