Package flumotion :: Package common :: Module process
[hide private]

Source Code for Module flumotion.common.process

  1  # -*- Mode: Python; test-case-name: flumotion.test.test_common_process -*- 
  2  # vi:si:et:sw=4:sts=4:ts=4 
  3  # 
  4  # Flumotion - a streaming media server 
  5  # Copyright (C) 2004,2005,2006,2007,2008 Fluendo, S.L. (www.fluendo.com). 
  6  # All rights reserved. 
  7   
  8  # This file may be distributed and/or modified under the terms of 
  9  # the GNU General Public License version 2 as published by 
 10  # the Free Software Foundation. 
 11  # This file is distributed without any warranty; without even the implied 
 12  # warranty of merchantability or fitness for a particular purpose. 
 13  # See "LICENSE.GPL" in the source distribution for more information. 
 14   
 15  # Licensees having purchased or holding a valid Flumotion Advanced 
 16  # Streaming Server license may use this file in accordance with the 
 17  # Flumotion Advanced Streaming Server Commercial License Agreement. 
 18  # See "LICENSE.Flumotion" in the source distribution for more information. 
 19   
 20  # Headers in this file shall remain intact. 
 21   
 22  """utilities for interacting with processes""" 
 23   
 24  import errno 
 25  import os 
 26  import signal 
 27  import sys 
 28  import time 
 29   
 30  from flumotion.common import log 
 31  from flumotion.common.common import ensureDir 
 32  from flumotion.configure import configure 
 33   
 34  __version__ = "$Rev: 6690 $" 
 35   
 36   
37 -def startup(processType, processName, daemonize=False, daemonizeTo='/'):
38 """ 39 Prepare a process for starting, logging appropriate standarised messages. 40 First daemonizes the process, if daemonize is true. 41 42 @param processType: The process type, for example 'worker'. Used 43 as the first part of the log file and PID file names. 44 @type processType: str 45 @param processName: The service name of the process. Used to 46 disambiguate different instances of the same daemon. 47 Used as the second part of log file and PID file names. 48 @type processName: str 49 @param daemonize: whether to daemonize the current process. 50 @type daemonize: bool 51 @param daemonizeTo: The directory that the daemon should run in. 52 @type daemonizeTo: str 53 """ 54 log.info(processType, "Starting %s '%s'", processType, processName) 55 56 if daemonize: 57 _daemonizeHelper(processType, daemonizeTo, processName) 58 59 log.info(processType, "Started %s '%s'", processType, processName) 60 61 def shutdownStarted(): 62 log.info(processType, "Stopping %s '%s'", processType, processName)
63 64 def shutdownEnded(): 65 log.info(processType, "Stopped %s '%s'", processType, processName) 66 67 # import inside function so we avoid affecting startup 68 from twisted.internet import reactor 69 reactor.addSystemEventTrigger('before', 'shutdown', 70 shutdownStarted) 71 reactor.addSystemEventTrigger('after', 'shutdown', 72 shutdownEnded) 73 74
75 -def daemonize(stdin='/dev/null', stdout='/dev/null', stderr='/dev/null', 76 directory='/'):
77 ''' 78 This forks the current process into a daemon. 79 The stdin, stdout, and stderr arguments are file names that 80 will be opened and be used to replace the standard file descriptors 81 in sys.stdin, sys.stdout, and sys.stderr. 82 These arguments are optional and default to /dev/null. 83 84 The fork will switch to the given directory. 85 86 Used by external projects (ft). 87 ''' 88 # Redirect standard file descriptors. 89 si = open(stdin, 'r') 90 os.dup2(si.fileno(), sys.stdin.fileno()) 91 try: 92 log.outputToFiles(stdout, stderr) 93 except IOError, e: 94 if e.errno == errno.EACCES: 95 log.error('common', 'Permission denied writing to log file %s.', 96 e.filename) 97 98 # first fork 99 try: 100 pid = os.fork() 101 if pid > 0: 102 sys.exit(0) # exit first parent 103 except OSError, e: 104 sys.stderr.write("Failed to fork: (%d) %s\n" % (e.errno, e.strerror)) 105 sys.exit(1) 106 107 # decouple from parent environment 108 try: 109 os.chdir(directory) 110 except OSError, e: 111 from flumotion.common import errors 112 raise errors.FatalError, "Failed to change directory to %s: %s" % ( 113 directory, e.strerror) 114 os.umask(0) 115 os.setsid() 116 117 # do second fork 118 try: 119 pid = os.fork() 120 if pid > 0: 121 sys.exit(0) # exit second parent 122 except OSError, e: 123 sys.stderr.write("Failed to fork: (%d) %s\n" % (e.errno, e.strerror)) 124 sys.exit(1)
125 126 # Now I am a daemon! 127 # don't add stuff here that can fail, because from now on the program 128 # will keep running regardless of tracebacks 129 130
131 -def _daemonizeHelper(processType, daemonizeTo='/', processName=None):
132 """ 133 Daemonize a process, writing log files and PID files to conventional 134 locations. 135 136 @param processType: The process type, for example 'worker'. Used 137 as the first part of the log file and PID file names. 138 @type processType: str 139 @param daemonizeTo: The directory that the daemon should run in. 140 @type daemonizeTo: str 141 @param processName: The service name of the process. Used to 142 disambiguate different instances of the same daemon. 143 Used as the second part of log file and PID file names. 144 @type processName: str 145 """ 146 147 ensureDir(configure.logdir, "log dir") 148 ensureDir(configure.rundir, "run dir") 149 ensureDir(configure.cachedir, "cache dir") 150 ensureDir(configure.registrydir, "registry dir") 151 152 pid = getPid(processType, processName) 153 if pid: 154 raise SystemError( 155 "A %s service%s is already running with pid %d" % ( 156 processType, processName and ' named %s' % processName or '', 157 pid)) 158 159 log.debug(processType, "%s service named '%s' daemonizing", 160 processType, processName) 161 162 if processName: 163 logPath = os.path.join(configure.logdir, 164 '%s.%s.log' % (processType, processName)) 165 else: 166 logPath = os.path.join(configure.logdir, 167 '%s.log' % (processType, )) 168 log.debug(processType, 'Further logging will be done to %s', logPath) 169 170 pidFile = _acquirePidFile(processType, processName) 171 172 # here we daemonize; so we also change our pid 173 daemonize(stdout=logPath, stderr=logPath, directory=daemonizeTo) 174 175 log.debug(processType, 'Started daemon') 176 177 # from now on I should keep running until killed, whatever happens 178 path = writePidFile(processType, processName, file=pidFile) 179 log.debug(processType, 'written pid file %s', path) 180 181 # import inside function so we avoid affecting startup 182 from twisted.internet import reactor 183 184 def _deletePidFile(): 185 log.debug(processType, 'deleting pid file') 186 deletePidFile(processType, processName)
187 reactor.addSystemEventTrigger('after', 'shutdown', 188 _deletePidFile) 189 190
191 -def _getPidPath(type, name=None):
192 """ 193 Get the full path to the pid file for the given process type and name. 194 """ 195 path = os.path.join(configure.rundir, '%s.pid' % type) 196 if name: 197 path = os.path.join(configure.rundir, '%s.%s.pid' % (type, name)) 198 log.debug('common', 'getPidPath for type %s, name %r: %s' % ( 199 type, name, path)) 200 return path
201 202
203 -def writePidFile(type, name=None, file=None):
204 """ 205 Write a pid file in the run directory, using the given process type 206 and process name for the filename. 207 208 @rtype: str 209 @returns: full path to the pid file that was written 210 """ 211 # don't shadow builtin file 212 pidFile = file 213 if pidFile is None: 214 ensureDir(configure.rundir, "rundir") 215 filename = _getPidPath(type, name) 216 pidFile = open(filename, 'w') 217 else: 218 filename = pidFile.name 219 pidFile.write("%d\n" % (os.getpid(), )) 220 pidFile.close() 221 os.chmod(filename, 0644) 222 return filename
223 224
225 -def _acquirePidFile(type, name=None):
226 """ 227 Open a PID file for writing, using the given process type and 228 process name for the filename. The returned file can be then passed 229 to writePidFile after forking. 230 231 @rtype: str 232 @returns: file object, open for writing 233 """ 234 ensureDir(configure.rundir, "rundir") 235 path = _getPidPath(type, name) 236 return open(path, 'w')
237 238
239 -def deletePidFile(type, name=None, force=False):
240 """ 241 Delete the pid file in the run directory, using the given process type 242 and process name for the filename. 243 244 @param force: if errors due to the file not existing should be ignored 245 @type force: bool 246 247 @rtype: str 248 @returns: full path to the pid file that was written 249 """ 250 path = _getPidPath(type, name) 251 try: 252 os.unlink(path) 253 except OSError, e: 254 if e.errno == errno.ENOENT and force: 255 pass 256 else: 257 raise 258 return path
259 260
261 -def getPid(type, name=None):
262 """ 263 Get the pid from the pid file in the run directory, using the given 264 process type and process name for the filename. 265 266 @returns: pid of the process, or None if not running or file not found. 267 """ 268 269 pidPath = _getPidPath(type, name) 270 log.log('common', 'pidfile for %s %s is %s' % (type, name, pidPath)) 271 if not os.path.exists(pidPath): 272 return 273 274 pidFile = open(pidPath, 'r') 275 pid = pidFile.readline() 276 pidFile.close() 277 if not pid or int(pid) == 0: 278 return 279 280 return int(pid)
281 282
283 -def signalPid(pid, signum):
284 """ 285 Send the given process a signal. 286 287 @returns: whether or not the process with the given pid was running 288 """ 289 try: 290 os.kill(pid, signum) 291 return True 292 except OSError, e: 293 # see man 2 kill 294 if e.errno == errno.EPERM: 295 # exists but belongs to a different user 296 return True 297 if e.errno == errno.ESRCH: 298 # pid does not exist 299 return False 300 raise
301 302
303 -def termPid(pid):
304 """ 305 Send the given process a TERM signal. 306 307 @returns: whether or not the process with the given pid was running 308 """ 309 return signalPid(pid, signal.SIGTERM)
310 311
312 -def killPid(pid):
313 """ 314 Send the given process a KILL signal. 315 316 @returns: whether or not the process with the given pid was running 317 """ 318 return signalPid(pid, signal.SIGKILL)
319 320
321 -def checkPidRunning(pid):
322 """ 323 Check if the given pid is currently running. 324 325 @returns: whether or not a process with that pid is active. 326 """ 327 return signalPid(pid, 0)
328 329
330 -def waitPidFile(type, name=None):
331 """ 332 Wait for the given process type and name to have started and created 333 a pid file. 334 335 Return the pid. 336 """ 337 # getting it from the start avoids an unneeded time.sleep 338 pid = getPid(type, name) 339 340 while not pid: 341 time.sleep(0.1) 342 pid = getPid(type, name) 343 344 return pid
345 346
347 -def waitForTerm():
348 """ 349 Wait until we get killed by a TERM signal (from someone else). 350 """ 351 352 class Waiter: 353 354 def __init__(self): 355 self.sleeping = True 356 import signal 357 self.oldhandler = signal.signal(signal.SIGTERM, 358 self._SIGTERMHandler)
359 360 def _SIGTERMHandler(self, number, frame): 361 self.sleeping = False 362 363 def sleep(self): 364 while self.sleeping: 365 time.sleep(0.1) 366 367 waiter = Waiter() 368 waiter.sleep() 369