##// END OF EJS Templates
Use check_pid from utils in IPython.parallel
Thomas Kluyver -
Show More
@@ -1,276 +1,260 b''
1 1 # encoding: utf-8
2 2 """
3 3 The Base Application class for IPython.parallel apps
4 4
5 5 Authors:
6 6
7 7 * Brian Granger
8 8 * Min RK
9 9
10 10 """
11 11
12 12 #-----------------------------------------------------------------------------
13 13 # Copyright (C) 2008-2011 The IPython Development Team
14 14 #
15 15 # Distributed under the terms of the BSD License. The full license is in
16 16 # the file COPYING, distributed as part of this software.
17 17 #-----------------------------------------------------------------------------
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Imports
21 21 #-----------------------------------------------------------------------------
22 22
23 23 import os
24 24 import logging
25 25 import re
26 26 import sys
27 27
28 28 from subprocess import Popen, PIPE
29 29
30 30 from IPython.config.application import catch_config_error, LevelFormatter
31 31 from IPython.core import release
32 32 from IPython.core.crashhandler import CrashHandler
33 33 from IPython.core.application import (
34 34 BaseIPythonApplication,
35 35 base_aliases as base_ip_aliases,
36 36 base_flags as base_ip_flags
37 37 )
38 38 from IPython.utils.path import expand_path
39 from IPython.utils.process import check_pid
39 40 from IPython.utils import py3compat
40 41 from IPython.utils.py3compat import unicode_type
41 42
42 43 from IPython.utils.traitlets import Unicode, Bool, Instance, Dict
43 44
44 45 #-----------------------------------------------------------------------------
45 46 # Module errors
46 47 #-----------------------------------------------------------------------------
47 48
48 49 class PIDFileError(Exception):
49 50 pass
50 51
51 52
52 53 #-----------------------------------------------------------------------------
53 54 # Crash handler for this application
54 55 #-----------------------------------------------------------------------------
55 56
56 57 class ParallelCrashHandler(CrashHandler):
57 58 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
58 59
59 60 def __init__(self, app):
60 61 contact_name = release.authors['Min'][0]
61 62 contact_email = release.author_email
62 63 bug_tracker = 'https://github.com/ipython/ipython/issues'
63 64 super(ParallelCrashHandler,self).__init__(
64 65 app, contact_name, contact_email, bug_tracker
65 66 )
66 67
67 68
68 69 #-----------------------------------------------------------------------------
69 70 # Main application
70 71 #-----------------------------------------------------------------------------
71 72 base_aliases = {}
72 73 base_aliases.update(base_ip_aliases)
73 74 base_aliases.update({
74 75 'work-dir' : 'BaseParallelApplication.work_dir',
75 76 'log-to-file' : 'BaseParallelApplication.log_to_file',
76 77 'clean-logs' : 'BaseParallelApplication.clean_logs',
77 78 'log-url' : 'BaseParallelApplication.log_url',
78 79 'cluster-id' : 'BaseParallelApplication.cluster_id',
79 80 })
80 81
81 82 base_flags = {
82 83 'log-to-file' : (
83 84 {'BaseParallelApplication' : {'log_to_file' : True}},
84 85 "send log output to a file"
85 86 )
86 87 }
87 88 base_flags.update(base_ip_flags)
88 89
89 90 class BaseParallelApplication(BaseIPythonApplication):
90 91 """The base Application for IPython.parallel apps
91 92
92 93 Principle extensions to BaseIPyythonApplication:
93 94
94 95 * work_dir
95 96 * remote logging via pyzmq
96 97 * IOLoop instance
97 98 """
98 99
99 100 crash_handler_class = ParallelCrashHandler
100 101
101 102 def _log_level_default(self):
102 103 # temporarily override default_log_level to INFO
103 104 return logging.INFO
104 105
105 106 def _log_format_default(self):
106 107 """override default log format to include time"""
107 108 return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)s %(message)s"
108 109
109 110 work_dir = Unicode(py3compat.getcwd(), config=True,
110 111 help='Set the working dir for the process.'
111 112 )
112 113 def _work_dir_changed(self, name, old, new):
113 114 self.work_dir = unicode_type(expand_path(new))
114 115
115 116 log_to_file = Bool(config=True,
116 117 help="whether to log to a file")
117 118
118 119 clean_logs = Bool(False, config=True,
119 120 help="whether to cleanup old logfiles before starting")
120 121
121 122 log_url = Unicode('', config=True,
122 123 help="The ZMQ URL of the iplogger to aggregate logging.")
123 124
124 125 cluster_id = Unicode('', config=True,
125 126 help="""String id to add to runtime files, to prevent name collisions when
126 127 using multiple clusters with a single profile simultaneously.
127 128
128 129 When set, files will be named like: 'ipcontroller-<cluster_id>-engine.json'
129 130
130 131 Since this is text inserted into filenames, typical recommendations apply:
131 132 Simple character strings are ideal, and spaces are not recommended (but should
132 133 generally work).
133 134 """
134 135 )
135 136 def _cluster_id_changed(self, name, old, new):
136 137 self.name = self.__class__.name
137 138 if new:
138 139 self.name += '-%s'%new
139 140
140 141 def _config_files_default(self):
141 142 return ['ipcontroller_config.py', 'ipengine_config.py', 'ipcluster_config.py']
142 143
143 144 loop = Instance('zmq.eventloop.ioloop.IOLoop')
144 145 def _loop_default(self):
145 146 from zmq.eventloop.ioloop import IOLoop
146 147 return IOLoop.instance()
147 148
148 149 aliases = Dict(base_aliases)
149 150 flags = Dict(base_flags)
150 151
151 152 @catch_config_error
152 153 def initialize(self, argv=None):
153 154 """initialize the app"""
154 155 super(BaseParallelApplication, self).initialize(argv)
155 156 self.to_work_dir()
156 157 self.reinit_logging()
157 158
158 159 def to_work_dir(self):
159 160 wd = self.work_dir
160 161 if unicode_type(wd) != py3compat.getcwd():
161 162 os.chdir(wd)
162 163 self.log.info("Changing to working dir: %s" % wd)
163 164 # This is the working dir by now.
164 165 sys.path.insert(0, '')
165 166
166 167 def reinit_logging(self):
167 168 # Remove old log files
168 169 log_dir = self.profile_dir.log_dir
169 170 if self.clean_logs:
170 171 for f in os.listdir(log_dir):
171 172 if re.match(r'%s-\d+\.(log|err|out)' % self.name, f):
172 173 try:
173 174 os.remove(os.path.join(log_dir, f))
174 175 except (OSError, IOError):
175 176 # probably just conflict from sibling process
176 177 # already removing it
177 178 pass
178 179 if self.log_to_file:
179 180 # Start logging to the new log file
180 181 log_filename = self.name + u'-' + str(os.getpid()) + u'.log'
181 182 logfile = os.path.join(log_dir, log_filename)
182 183 open_log_file = open(logfile, 'w')
183 184 else:
184 185 open_log_file = None
185 186 if open_log_file is not None:
186 187 while self.log.handlers:
187 188 self.log.removeHandler(self.log.handlers[0])
188 189 self._log_handler = logging.StreamHandler(open_log_file)
189 190 self.log.addHandler(self._log_handler)
190 191 else:
191 192 self._log_handler = self.log.handlers[0]
192 193 # Add timestamps to log format:
193 194 self._log_formatter = LevelFormatter(self.log_format,
194 195 datefmt=self.log_datefmt)
195 196 self._log_handler.setFormatter(self._log_formatter)
196 197 # do not propagate log messages to root logger
197 198 # ipcluster app will sometimes print duplicate messages during shutdown
198 199 # if this is 1 (default):
199 200 self.log.propagate = False
200 201
201 202 def write_pid_file(self, overwrite=False):
202 203 """Create a .pid file in the pid_dir with my pid.
203 204
204 205 This must be called after pre_construct, which sets `self.pid_dir`.
205 206 This raises :exc:`PIDFileError` if the pid file exists already.
206 207 """
207 208 pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid')
208 209 if os.path.isfile(pid_file):
209 210 pid = self.get_pid_from_file()
210 211 if not overwrite:
211 212 raise PIDFileError(
212 213 'The pid file [%s] already exists. \nThis could mean that this '
213 214 'server is already running with [pid=%s].' % (pid_file, pid)
214 215 )
215 216 with open(pid_file, 'w') as f:
216 217 self.log.info("Creating pid file: %s" % pid_file)
217 218 f.write(repr(os.getpid())+'\n')
218 219
219 220 def remove_pid_file(self):
220 221 """Remove the pid file.
221 222
222 223 This should be called at shutdown by registering a callback with
223 224 :func:`reactor.addSystemEventTrigger`. This needs to return
224 225 ``None``.
225 226 """
226 227 pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid')
227 228 if os.path.isfile(pid_file):
228 229 try:
229 230 self.log.info("Removing pid file: %s" % pid_file)
230 231 os.remove(pid_file)
231 232 except:
232 233 self.log.warn("Error removing the pid file: %s" % pid_file)
233 234
234 235 def get_pid_from_file(self):
235 236 """Get the pid from the pid file.
236 237
237 238 If the pid file doesn't exist a :exc:`PIDFileError` is raised.
238 239 """
239 240 pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid')
240 241 if os.path.isfile(pid_file):
241 242 with open(pid_file, 'r') as f:
242 243 s = f.read().strip()
243 244 try:
244 245 pid = int(s)
245 246 except:
246 247 raise PIDFileError("invalid pid file: %s (contents: %r)"%(pid_file, s))
247 248 return pid
248 249 else:
249 250 raise PIDFileError('pid file not found: %s' % pid_file)
250 251
251 252 def check_pid(self, pid):
252 if os.name == 'nt':
253 try:
254 import ctypes
255 # returns 0 if no such process (of ours) exists
256 # positive int otherwise
257 p = ctypes.windll.kernel32.OpenProcess(1,0,pid)
258 except Exception:
259 self.log.warn(
260 "Could not determine whether pid %i is running via `OpenProcess`. "
261 " Making the likely assumption that it is."%pid
262 )
263 return True
264 return bool(p)
265 else:
266 try:
267 p = Popen(['ps','x'], stdout=PIPE, stderr=PIPE)
268 output,_ = p.communicate()
269 except OSError:
270 self.log.warn(
271 "Could not determine whether pid %i is running via `ps x`. "
272 " Making the likely assumption that it is."%pid
273 )
274 return True
275 pids = list(map(int, re.findall(br'^\W*\d+', output, re.MULTILINE)))
276 return pid in pids
253 try:
254 return check_pid(pid)
255 except Exception:
256 self.log.warn(
257 "Could not determine whether pid %i is running. "
258 " Making the likely assumption that it is."%pid
259 )
260 return True
General Comments 0
You need to be logged in to leave comments. Login now