##// END OF EJS Templates
%tasks clear: clear task list
Ville M. Vainio -
Show More
@@ -1,175 +1,183 b''
1 """ Preliminary "job control" extensions for IPython
1 """ Preliminary "job control" extensions for IPython
2
2
3 requires python 2.4 (or separate 'subprocess' module
3 requires python 2.4 (or separate 'subprocess' module
4
4
5 This provides 2 features, launching background jobs and killing foreground jobs from another IPython instance.
5 This provides 2 features, launching background jobs and killing foreground jobs from another IPython instance.
6
6
7 Launching background jobs:
7 Launching background jobs:
8
8
9 Usage:
9 Usage:
10
10
11 [ipython]|2> import jobctrl
11 [ipython]|2> import jobctrl
12 [ipython]|3> &ls
12 [ipython]|3> &ls
13 <3> <jobctrl.IpyPopen object at 0x00D87FD0>
13 <3> <jobctrl.IpyPopen object at 0x00D87FD0>
14 [ipython]|4> _3.go
14 [ipython]|4> _3.go
15 -----------> _3.go()
15 -----------> _3.go()
16 ChangeLog
16 ChangeLog
17 IPython
17 IPython
18 MANIFEST.in
18 MANIFEST.in
19 README
19 README
20 README_Windows.txt
20 README_Windows.txt
21
21
22 ...
22 ...
23
23
24 Killing foreground tasks:
24 Killing foreground tasks:
25
25
26 Launch IPython instance, run a blocking command:
26 Launch IPython instance, run a blocking command:
27
27
28 [Q:/ipython]|1> import jobctrl
28 [Q:/ipython]|1> import jobctrl
29 [Q:/ipython]|2> cat
29 [Q:/ipython]|2> cat
30
30
31 Now launch a new IPython prompt and kill the process:
31 Now launch a new IPython prompt and kill the process:
32
32
33 IPython 0.8.3.svn.r2919 [on Py 2.5]
33 IPython 0.8.3.svn.r2919 [on Py 2.5]
34 [Q:/ipython]|1> import jobctrl
34 [Q:/ipython]|1> import jobctrl
35 [Q:/ipython]|2> %tasks
35 [Q:/ipython]|2> %tasks
36 6020: 'cat ' (Q:\ipython)
36 6020: 'cat ' (Q:\ipython)
37 [Q:/ipython]|3> %kill
37 [Q:/ipython]|3> %kill
38 SUCCESS: The process with PID 6020 has been terminated.
38 SUCCESS: The process with PID 6020 has been terminated.
39 [Q:/ipython]|4>
39 [Q:/ipython]|4>
40
40
41 (you don't need to specify PID for %kill if only one task is running)
41 (you don't need to specify PID for %kill if only one task is running)
42 """
42 """
43
43
44 from subprocess import Popen,PIPE
44 from subprocess import Popen,PIPE
45 import os,shlex,sys,time
45 import os,shlex,sys,time
46
46
47 from IPython import genutils
47 from IPython import genutils
48
48
49 import IPython.ipapi
49 import IPython.ipapi
50
50
51 if os.name == 'nt':
51 if os.name == 'nt':
52 def kill_process(pid):
52 def kill_process(pid):
53 os.system('taskkill /F /PID %d' % pid)
53 os.system('taskkill /F /PID %d' % pid)
54 else:
54 else:
55 def kill_process(pid):
55 def kill_process(pid):
56 os.system('kill -9 %d' % pid)
56 os.system('kill -9 %d' % pid)
57
57
58
58
59
59
60 class IpyPopen(Popen):
60 class IpyPopen(Popen):
61 def go(self):
61 def go(self):
62 print self.communicate()[0]
62 print self.communicate()[0]
63 def __repr__(self):
63 def __repr__(self):
64 return '<IPython job "%s" PID=%d>' % (self.line, self.pid)
64 return '<IPython job "%s" PID=%d>' % (self.line, self.pid)
65
65
66 def kill(self):
66 def kill(self):
67 kill_process(self.pid)
67 kill_process(self.pid)
68
68
69 def startjob(job):
69 def startjob(job):
70 p = IpyPopen(shlex.split(job), stdout=PIPE, shell = False)
70 p = IpyPopen(shlex.split(job), stdout=PIPE, shell = False)
71 p.line = job
71 p.line = job
72 return p
72 return p
73
73
74 def jobctrl_prefilter_f(self,line):
74 def jobctrl_prefilter_f(self,line):
75 if line.startswith('&'):
75 if line.startswith('&'):
76 pre,fn,rest = self.split_user_input(line[1:])
76 pre,fn,rest = self.split_user_input(line[1:])
77
77
78 line = ip.IP.expand_aliases(fn,rest)
78 line = ip.IP.expand_aliases(fn,rest)
79 return '_ip.startjob(%s)' % genutils.make_quoted_expr(line)
79 return '_ip.startjob(%s)' % genutils.make_quoted_expr(line)
80
80
81 raise IPython.ipapi.TryNext
81 raise IPython.ipapi.TryNext
82
82
83
83
84 def job_list(ip):
84 def job_list(ip):
85 keys = ip.db.keys('tasks/*')
85 keys = ip.db.keys('tasks/*')
86 ents = [ip.db[k] for k in keys]
86 ents = [ip.db[k] for k in keys]
87 return ents
87 return ents
88
88
89 def magic_tasks(self,line):
89 def magic_tasks(self,line):
90 """ Show a list of tasks.
90 """ Show a list of tasks.
91
91
92 A 'task' is a process that has been started in IPython when 'jobctrl' extension is enabled.
92 A 'task' is a process that has been started in IPython when 'jobctrl' extension is enabled.
93 Tasks can be killed with %kill.
93 Tasks can be killed with %kill.
94
95 '%tasks clear' clears the task list (from stale tasks)
94 """
96 """
95 ip = self.getapi()
97 ip = self.getapi()
98 if line.strip() == 'clear':
99 for k in ip.db.keys('tasks/*'):
100 print "Clearing",ip.db[k]
101 del ip.db[k]
102 return
103
96 ents = job_list(ip)
104 ents = job_list(ip)
97 if not ents:
105 if not ents:
98 print "No tasks running"
106 print "No tasks running"
99 for pid,cmd,cwd,t in ents:
107 for pid,cmd,cwd,t in ents:
100 dur = int(time.time()-t)
108 dur = int(time.time()-t)
101 print "%d: '%s' (%s) %d:%02d" % (pid,cmd,cwd, dur / 60,dur%60)
109 print "%d: '%s' (%s) %d:%02d" % (pid,cmd,cwd, dur / 60,dur%60)
102
110
103 def magic_kill(self,line):
111 def magic_kill(self,line):
104 """ Kill a task
112 """ Kill a task
105
113
106 Without args, either kill one task (if only one running) or show list (if many)
114 Without args, either kill one task (if only one running) or show list (if many)
107 With arg, assume it's the process id.
115 With arg, assume it's the process id.
108
116
109 %kill is typically (much) more powerful than trying to terminate a process with ctrl+C.
117 %kill is typically (much) more powerful than trying to terminate a process with ctrl+C.
110 """
118 """
111 ip = self.getapi()
119 ip = self.getapi()
112 jobs = job_list(ip)
120 jobs = job_list(ip)
113
121
114 if not line.strip():
122 if not line.strip():
115 if len(jobs) == 1:
123 if len(jobs) == 1:
116 kill_process(jobs[0][0])
124 kill_process(jobs[0][0])
117 else:
125 else:
118 magic_tasks(self,line)
126 magic_tasks(self,line)
119 return
127 return
120
128
121 try:
129 try:
122 pid = int(line)
130 pid = int(line)
123 kill_process(pid)
131 kill_process(pid)
124 except ValueError:
132 except ValueError:
125 magic_tasks(self,line)
133 magic_tasks(self,line)
126
134
127 if sys.platform == 'win32':
135 if sys.platform == 'win32':
128 shell_internal_commands = 'break chcp cls copy ctty date del erase dir md mkdir path prompt rd rmdir time type ver vol'.split()
136 shell_internal_commands = 'break chcp cls copy ctty date del erase dir md mkdir path prompt rd rmdir time type ver vol'.split()
129 else:
137 else:
130 # todo linux commands
138 # todo linux commands
131 shell_internal_commands = []
139 shell_internal_commands = []
132
140
133
141
134 def jobctrl_shellcmd(ip,cmd):
142 def jobctrl_shellcmd(ip,cmd):
135 """ os.system replacement that stores process info to db['tasks/t1234'] """
143 """ os.system replacement that stores process info to db['tasks/t1234'] """
136 cmd = cmd.strip()
144 cmd = cmd.strip()
137 cmdname = cmd.split(None,1)[0]
145 cmdname = cmd.split(None,1)[0]
138 if cmdname in shell_internal_commands:
146 if cmdname in shell_internal_commands:
139 use_shell = True
147 use_shell = True
140 else:
148 else:
141 use_shell = False
149 use_shell = False
142
150
143 jobentry = None
151 jobentry = None
144 try:
152 try:
145 try:
153 try:
146 p = Popen(cmd,shell = use_shell)
154 p = Popen(cmd,shell = use_shell)
147 except WindowsError:
155 except WindowsError:
148 if use_shell:
156 if use_shell:
149 # try with os.system
157 # try with os.system
150 os.system(cmd)
158 os.system(cmd)
151 return
159 return
152 else:
160 else:
153 # have to go via shell, sucks
161 # have to go via shell, sucks
154 p = Popen(cmd,shell = True)
162 p = Popen(cmd,shell = True)
155
163
156 jobentry = 'tasks/t' + str(p.pid)
164 jobentry = 'tasks/t' + str(p.pid)
157 ip.db[jobentry] = (p.pid,cmd,os.getcwd(),time.time())
165 ip.db[jobentry] = (p.pid,cmd,os.getcwd(),time.time())
158 p.communicate()
166 p.communicate()
159
167
160 finally:
168 finally:
161 if jobentry:
169 if jobentry:
162 del ip.db[jobentry]
170 del ip.db[jobentry]
163
171
164
172
165 def install():
173 def install():
166 global ip
174 global ip
167 ip = IPython.ipapi.get()
175 ip = IPython.ipapi.get()
168 # needed to make startjob visible as _ip.startjob('blah')
176 # needed to make startjob visible as _ip.startjob('blah')
169 ip.startjob = startjob
177 ip.startjob = startjob
170 ip.set_hook('input_prefilter', jobctrl_prefilter_f)
178 ip.set_hook('input_prefilter', jobctrl_prefilter_f)
171 ip.set_hook('shell_hook', jobctrl_shellcmd)
179 ip.set_hook('shell_hook', jobctrl_shellcmd)
172 ip.expose_magic('kill',magic_kill)
180 ip.expose_magic('kill',magic_kill)
173 ip.expose_magic('tasks',magic_tasks)
181 ip.expose_magic('tasks',magic_tasks)
174
182
175 install()
183 install()
General Comments 0
You need to be logged in to leave comments. Login now