##// END OF EJS Templates
raise PIDFileError on empty PID file, not ValueError in baseapp
MinRK -
Show More
@@ -1,263 +1,267 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 The Base Application class for IPython.parallel apps
4 The Base Application class for IPython.parallel apps
5
5
6 Authors:
6 Authors:
7
7
8 * Brian Granger
8 * Brian Granger
9 * Min RK
9 * Min RK
10
10
11 """
11 """
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Copyright (C) 2008-2011 The IPython Development Team
14 # Copyright (C) 2008-2011 The IPython Development Team
15 #
15 #
16 # Distributed under the terms of the BSD License. The full license is in
16 # Distributed under the terms of the BSD License. The full license is in
17 # the file COPYING, distributed as part of this software.
17 # the file COPYING, distributed as part of this software.
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21 # Imports
21 # Imports
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23
23
24 from __future__ import with_statement
24 from __future__ import with_statement
25
25
26 import os
26 import os
27 import logging
27 import logging
28 import re
28 import re
29 import sys
29 import sys
30
30
31 from subprocess import Popen, PIPE
31 from subprocess import Popen, PIPE
32
32
33 from IPython.core import release
33 from IPython.core import release
34 from IPython.core.crashhandler import CrashHandler
34 from IPython.core.crashhandler import CrashHandler
35 from IPython.core.application import (
35 from IPython.core.application import (
36 BaseIPythonApplication,
36 BaseIPythonApplication,
37 base_aliases as base_ip_aliases,
37 base_aliases as base_ip_aliases,
38 base_flags as base_ip_flags
38 base_flags as base_ip_flags
39 )
39 )
40 from IPython.utils.path import expand_path
40 from IPython.utils.path import expand_path
41
41
42 from IPython.utils.traitlets import Unicode, Bool, Instance, Dict, List
42 from IPython.utils.traitlets import Unicode, Bool, Instance, Dict, List
43
43
44 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
45 # Module errors
45 # Module errors
46 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
47
47
48 class PIDFileError(Exception):
48 class PIDFileError(Exception):
49 pass
49 pass
50
50
51
51
52 #-----------------------------------------------------------------------------
52 #-----------------------------------------------------------------------------
53 # Crash handler for this application
53 # Crash handler for this application
54 #-----------------------------------------------------------------------------
54 #-----------------------------------------------------------------------------
55
55
56
56
57 _message_template = """\
57 _message_template = """\
58 Oops, $self.app_name crashed. We do our best to make it stable, but...
58 Oops, $self.app_name crashed. We do our best to make it stable, but...
59
59
60 A crash report was automatically generated with the following information:
60 A crash report was automatically generated with the following information:
61 - A verbatim copy of the crash traceback.
61 - A verbatim copy of the crash traceback.
62 - Data on your current $self.app_name configuration.
62 - Data on your current $self.app_name configuration.
63
63
64 It was left in the file named:
64 It was left in the file named:
65 \t'$self.crash_report_fname'
65 \t'$self.crash_report_fname'
66 If you can email this file to the developers, the information in it will help
66 If you can email this file to the developers, the information in it will help
67 them in understanding and correcting the problem.
67 them in understanding and correcting the problem.
68
68
69 You can mail it to: $self.contact_name at $self.contact_email
69 You can mail it to: $self.contact_name at $self.contact_email
70 with the subject '$self.app_name Crash Report'.
70 with the subject '$self.app_name Crash Report'.
71
71
72 If you want to do it now, the following command will work (under Unix):
72 If you want to do it now, the following command will work (under Unix):
73 mail -s '$self.app_name Crash Report' $self.contact_email < $self.crash_report_fname
73 mail -s '$self.app_name Crash Report' $self.contact_email < $self.crash_report_fname
74
74
75 To ensure accurate tracking of this issue, please file a report about it at:
75 To ensure accurate tracking of this issue, please file a report about it at:
76 $self.bug_tracker
76 $self.bug_tracker
77 """
77 """
78
78
79 class ParallelCrashHandler(CrashHandler):
79 class ParallelCrashHandler(CrashHandler):
80 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
80 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
81
81
82 message_template = _message_template
82 message_template = _message_template
83
83
84 def __init__(self, app):
84 def __init__(self, app):
85 contact_name = release.authors['Min'][0]
85 contact_name = release.authors['Min'][0]
86 contact_email = release.authors['Min'][1]
86 contact_email = release.authors['Min'][1]
87 bug_tracker = 'http://github.com/ipython/ipython/issues'
87 bug_tracker = 'http://github.com/ipython/ipython/issues'
88 super(ParallelCrashHandler,self).__init__(
88 super(ParallelCrashHandler,self).__init__(
89 app, contact_name, contact_email, bug_tracker
89 app, contact_name, contact_email, bug_tracker
90 )
90 )
91
91
92
92
93 #-----------------------------------------------------------------------------
93 #-----------------------------------------------------------------------------
94 # Main application
94 # Main application
95 #-----------------------------------------------------------------------------
95 #-----------------------------------------------------------------------------
96 base_aliases = {}
96 base_aliases = {}
97 base_aliases.update(base_ip_aliases)
97 base_aliases.update(base_ip_aliases)
98 base_aliases.update({
98 base_aliases.update({
99 'profile_dir' : 'ProfileDir.location',
99 'profile_dir' : 'ProfileDir.location',
100 'log_level' : 'BaseParallelApplication.log_level',
100 'log_level' : 'BaseParallelApplication.log_level',
101 'work_dir' : 'BaseParallelApplication.work_dir',
101 'work_dir' : 'BaseParallelApplication.work_dir',
102 'log_to_file' : 'BaseParallelApplication.log_to_file',
102 'log_to_file' : 'BaseParallelApplication.log_to_file',
103 'clean_logs' : 'BaseParallelApplication.clean_logs',
103 'clean_logs' : 'BaseParallelApplication.clean_logs',
104 'log_url' : 'BaseParallelApplication.log_url',
104 'log_url' : 'BaseParallelApplication.log_url',
105 })
105 })
106
106
107 base_flags = {
107 base_flags = {
108 'log-to-file' : (
108 'log-to-file' : (
109 {'BaseParallelApplication' : {'log_to_file' : True}},
109 {'BaseParallelApplication' : {'log_to_file' : True}},
110 "send log output to a file"
110 "send log output to a file"
111 )
111 )
112 }
112 }
113 base_flags.update(base_ip_flags)
113 base_flags.update(base_ip_flags)
114
114
115 class BaseParallelApplication(BaseIPythonApplication):
115 class BaseParallelApplication(BaseIPythonApplication):
116 """The base Application for IPython.parallel apps
116 """The base Application for IPython.parallel apps
117
117
118 Principle extensions to BaseIPyythonApplication:
118 Principle extensions to BaseIPyythonApplication:
119
119
120 * work_dir
120 * work_dir
121 * remote logging via pyzmq
121 * remote logging via pyzmq
122 * IOLoop instance
122 * IOLoop instance
123 """
123 """
124
124
125 crash_handler_class = ParallelCrashHandler
125 crash_handler_class = ParallelCrashHandler
126
126
127 def _log_level_default(self):
127 def _log_level_default(self):
128 # temporarily override default_log_level to INFO
128 # temporarily override default_log_level to INFO
129 return logging.INFO
129 return logging.INFO
130
130
131 work_dir = Unicode(os.getcwdu(), config=True,
131 work_dir = Unicode(os.getcwdu(), config=True,
132 help='Set the working dir for the process.'
132 help='Set the working dir for the process.'
133 )
133 )
134 def _work_dir_changed(self, name, old, new):
134 def _work_dir_changed(self, name, old, new):
135 self.work_dir = unicode(expand_path(new))
135 self.work_dir = unicode(expand_path(new))
136
136
137 log_to_file = Bool(config=True,
137 log_to_file = Bool(config=True,
138 help="whether to log to a file")
138 help="whether to log to a file")
139
139
140 clean_logs = Bool(False, config=True,
140 clean_logs = Bool(False, config=True,
141 help="whether to cleanup old logfiles before starting")
141 help="whether to cleanup old logfiles before starting")
142
142
143 log_url = Unicode('', config=True,
143 log_url = Unicode('', config=True,
144 help="The ZMQ URL of the iplogger to aggregate logging.")
144 help="The ZMQ URL of the iplogger to aggregate logging.")
145
145
146 def _config_files_default(self):
146 def _config_files_default(self):
147 return ['ipcontroller_config.py', 'ipengine_config.py', 'ipcluster_config.py']
147 return ['ipcontroller_config.py', 'ipengine_config.py', 'ipcluster_config.py']
148
148
149 loop = Instance('zmq.eventloop.ioloop.IOLoop')
149 loop = Instance('zmq.eventloop.ioloop.IOLoop')
150 def _loop_default(self):
150 def _loop_default(self):
151 from zmq.eventloop.ioloop import IOLoop
151 from zmq.eventloop.ioloop import IOLoop
152 return IOLoop.instance()
152 return IOLoop.instance()
153
153
154 aliases = Dict(base_aliases)
154 aliases = Dict(base_aliases)
155 flags = Dict(base_flags)
155 flags = Dict(base_flags)
156
156
157 def initialize(self, argv=None):
157 def initialize(self, argv=None):
158 """initialize the app"""
158 """initialize the app"""
159 super(BaseParallelApplication, self).initialize(argv)
159 super(BaseParallelApplication, self).initialize(argv)
160 self.to_work_dir()
160 self.to_work_dir()
161 self.reinit_logging()
161 self.reinit_logging()
162
162
163 def to_work_dir(self):
163 def to_work_dir(self):
164 wd = self.work_dir
164 wd = self.work_dir
165 if unicode(wd) != os.getcwdu():
165 if unicode(wd) != os.getcwdu():
166 os.chdir(wd)
166 os.chdir(wd)
167 self.log.info("Changing to working dir: %s" % wd)
167 self.log.info("Changing to working dir: %s" % wd)
168 # This is the working dir by now.
168 # This is the working dir by now.
169 sys.path.insert(0, '')
169 sys.path.insert(0, '')
170
170
171 def reinit_logging(self):
171 def reinit_logging(self):
172 # Remove old log files
172 # Remove old log files
173 log_dir = self.profile_dir.log_dir
173 log_dir = self.profile_dir.log_dir
174 if self.clean_logs:
174 if self.clean_logs:
175 for f in os.listdir(log_dir):
175 for f in os.listdir(log_dir):
176 if re.match(r'%s-\d+\.(log|err|out)'%self.name,f):
176 if re.match(r'%s-\d+\.(log|err|out)'%self.name,f):
177 os.remove(os.path.join(log_dir, f))
177 os.remove(os.path.join(log_dir, f))
178 if self.log_to_file:
178 if self.log_to_file:
179 # Start logging to the new log file
179 # Start logging to the new log file
180 log_filename = self.name + u'-' + str(os.getpid()) + u'.log'
180 log_filename = self.name + u'-' + str(os.getpid()) + u'.log'
181 logfile = os.path.join(log_dir, log_filename)
181 logfile = os.path.join(log_dir, log_filename)
182 open_log_file = open(logfile, 'w')
182 open_log_file = open(logfile, 'w')
183 else:
183 else:
184 open_log_file = None
184 open_log_file = None
185 if open_log_file is not None:
185 if open_log_file is not None:
186 self.log.removeHandler(self._log_handler)
186 self.log.removeHandler(self._log_handler)
187 self._log_handler = logging.StreamHandler(open_log_file)
187 self._log_handler = logging.StreamHandler(open_log_file)
188 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
188 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
189 self._log_handler.setFormatter(self._log_formatter)
189 self._log_handler.setFormatter(self._log_formatter)
190 self.log.addHandler(self._log_handler)
190 self.log.addHandler(self._log_handler)
191
191
192 def write_pid_file(self, overwrite=False):
192 def write_pid_file(self, overwrite=False):
193 """Create a .pid file in the pid_dir with my pid.
193 """Create a .pid file in the pid_dir with my pid.
194
194
195 This must be called after pre_construct, which sets `self.pid_dir`.
195 This must be called after pre_construct, which sets `self.pid_dir`.
196 This raises :exc:`PIDFileError` if the pid file exists already.
196 This raises :exc:`PIDFileError` if the pid file exists already.
197 """
197 """
198 pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid')
198 pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid')
199 if os.path.isfile(pid_file):
199 if os.path.isfile(pid_file):
200 pid = self.get_pid_from_file()
200 pid = self.get_pid_from_file()
201 if not overwrite:
201 if not overwrite:
202 raise PIDFileError(
202 raise PIDFileError(
203 'The pid file [%s] already exists. \nThis could mean that this '
203 'The pid file [%s] already exists. \nThis could mean that this '
204 'server is already running with [pid=%s].' % (pid_file, pid)
204 'server is already running with [pid=%s].' % (pid_file, pid)
205 )
205 )
206 with open(pid_file, 'w') as f:
206 with open(pid_file, 'w') as f:
207 self.log.info("Creating pid file: %s" % pid_file)
207 self.log.info("Creating pid file: %s" % pid_file)
208 f.write(repr(os.getpid())+'\n')
208 f.write(repr(os.getpid())+'\n')
209
209
210 def remove_pid_file(self):
210 def remove_pid_file(self):
211 """Remove the pid file.
211 """Remove the pid file.
212
212
213 This should be called at shutdown by registering a callback with
213 This should be called at shutdown by registering a callback with
214 :func:`reactor.addSystemEventTrigger`. This needs to return
214 :func:`reactor.addSystemEventTrigger`. This needs to return
215 ``None``.
215 ``None``.
216 """
216 """
217 pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid')
217 pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid')
218 if os.path.isfile(pid_file):
218 if os.path.isfile(pid_file):
219 try:
219 try:
220 self.log.info("Removing pid file: %s" % pid_file)
220 self.log.info("Removing pid file: %s" % pid_file)
221 os.remove(pid_file)
221 os.remove(pid_file)
222 except:
222 except:
223 self.log.warn("Error removing the pid file: %s" % pid_file)
223 self.log.warn("Error removing the pid file: %s" % pid_file)
224
224
225 def get_pid_from_file(self):
225 def get_pid_from_file(self):
226 """Get the pid from the pid file.
226 """Get the pid from the pid file.
227
227
228 If the pid file doesn't exist a :exc:`PIDFileError` is raised.
228 If the pid file doesn't exist a :exc:`PIDFileError` is raised.
229 """
229 """
230 pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid')
230 pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid')
231 if os.path.isfile(pid_file):
231 if os.path.isfile(pid_file):
232 with open(pid_file, 'r') as f:
232 with open(pid_file, 'r') as f:
233 pid = int(f.read().strip())
233 s = f.read().strip()
234 try:
235 pid = int(s)
236 except:
237 raise PIDFileError("invalid pid file: %s (contents: %r)"%(pid_file, s))
234 return pid
238 return pid
235 else:
239 else:
236 raise PIDFileError('pid file not found: %s' % pid_file)
240 raise PIDFileError('pid file not found: %s' % pid_file)
237
241
238 def check_pid(self, pid):
242 def check_pid(self, pid):
239 if os.name == 'nt':
243 if os.name == 'nt':
240 try:
244 try:
241 import ctypes
245 import ctypes
242 # returns 0 if no such process (of ours) exists
246 # returns 0 if no such process (of ours) exists
243 # positive int otherwise
247 # positive int otherwise
244 p = ctypes.windll.kernel32.OpenProcess(1,0,pid)
248 p = ctypes.windll.kernel32.OpenProcess(1,0,pid)
245 except Exception:
249 except Exception:
246 self.log.warn(
250 self.log.warn(
247 "Could not determine whether pid %i is running via `OpenProcess`. "
251 "Could not determine whether pid %i is running via `OpenProcess`. "
248 " Making the likely assumption that it is."%pid
252 " Making the likely assumption that it is."%pid
249 )
253 )
250 return True
254 return True
251 return bool(p)
255 return bool(p)
252 else:
256 else:
253 try:
257 try:
254 p = Popen(['ps','x'], stdout=PIPE, stderr=PIPE)
258 p = Popen(['ps','x'], stdout=PIPE, stderr=PIPE)
255 output,_ = p.communicate()
259 output,_ = p.communicate()
256 except OSError:
260 except OSError:
257 self.log.warn(
261 self.log.warn(
258 "Could not determine whether pid %i is running via `ps x`. "
262 "Could not determine whether pid %i is running via `ps x`. "
259 " Making the likely assumption that it is."%pid
263 " Making the likely assumption that it is."%pid
260 )
264 )
261 return True
265 return True
262 pids = map(int, re.findall(r'^\W*\d+', output, re.MULTILINE))
266 pids = map(int, re.findall(r'^\W*\d+', output, re.MULTILINE))
263 return pid in pids
267 return pid in pids
General Comments 0
You need to be logged in to leave comments. Login now