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 -*- 
  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 log.info(processType, "Starting %s '%s'", processType, processName) 43 44 if daemonize: 45 _daemonizeHelper(processType, daemonizeTo, processName) 46 47 log.info(processType, "Started %s '%s'", processType, processName) 48 49 def shutdownStarted(): 50 log.info(processType, "Stopping %s '%s'", processType, processName)
51 def shutdownEnded(): 52 log.info(processType, "Stopped %s '%s'", processType, processName) 53 54 # import inside function so we avoid affecting startup 55 from twisted.internet import reactor 56 reactor.addSystemEventTrigger('before', 'shutdown', 57 shutdownStarted) 58 reactor.addSystemEventTrigger('after', 'shutdown', 59 shutdownEnded) 60
61 -def _daemonize(stdin='/dev/null', stdout='/dev/null', stderr='/dev/null', 62 directory='/'):
63 ''' 64 This forks the current process into a daemon. 65 The stdin, stdout, and stderr arguments are file names that 66 will be opened and be used to replace the standard file descriptors 67 in sys.stdin, sys.stdout, and sys.stderr. 68 These arguments are optional and default to /dev/null. 69 70 The fork will switch to the given directory. 71 ''' 72 # Redirect standard file descriptors. 73 si = open(stdin, 'r') 74 os.dup2(si.fileno(), sys.stdin.fileno()) 75 try: 76 log.outputToFiles(stdout, stderr) 77 except IOError, e: 78 if e.errno == errno.EACCES: 79 log.error('common', 'Permission denied writing to log file %s.', 80 e.filename) 81 82 # first fork 83 try: 84 pid = os.fork() 85 if pid > 0: 86 sys.exit(0) # exit first parent 87 except OSError, e: 88 sys.stderr.write("Failed to fork: (%d) %s\n" % (e.errno, e.strerror)) 89 sys.exit(1) 90 91 # decouple from parent environment 92 try: 93 os.chdir(directory) 94 except OSError, e: 95 from flumotion.common import errors 96 raise errors.FatalError, "Failed to change directory to %s: %s" % ( 97 directory, e.strerror) 98 os.umask(0) 99 os.setsid() 100 101 # do second fork 102 try: 103 pid = os.fork() 104 if pid > 0: 105 sys.exit(0) # exit second parent 106 except OSError, e: 107 sys.stderr.write("Failed to fork: (%d) %s\n" % (e.errno, e.strerror)) 108 sys.exit(1)
109 110 # Now I am a daemon! 111 # don't add stuff here that can fail, because from now on the program 112 # will keep running regardless of tracebacks 113
114 -def _daemonizeHelper(processType, daemonizeTo='/', processName=None):
115 """ 116 Daemonize a process, writing log files and PID files to conventional 117 locations. 118 119 @param processType: The process type, for example 'worker'. Used 120 as part of the log file and PID file names. 121 @type processType: str 122 @param daemonizeTo: The directory that the daemon should run in. 123 @type daemonizeTo: str 124 @param processName: The service name of the process. Used to 125 disambiguate different instances of the same daemon. 126 @type processName: str 127 """ 128 129 ensureDir(configure.logdir, "log dir") 130 ensureDir(configure.rundir, "run dir") 131 ensureDir(configure.cachedir, "cache dir") 132 ensureDir(configure.registrydir, "registry dir") 133 134 pid = getPid(processType, processName) 135 if pid: 136 raise SystemError( 137 "A %s service named '%s' is already running with pid %d" 138 % (processType, processName or processType, pid)) 139 140 log.debug(processType, "%s service named '%s' daemonizing", 141 processType, processName) 142 143 if processName: 144 logPath = os.path.join(configure.logdir, 145 '%s.%s.log' % (processType, processName)) 146 else: 147 logPath = os.path.join(configure.logdir, 148 '%s.log' % (processType,)) 149 log.debug(processType, 'Further logging will be done to %s', logPath) 150 151 pidFile = _acquirePidFile(processType, processName) 152 153 # here we daemonize; so we also change our pid 154 _daemonize(stdout=logPath, stderr=logPath, directory=daemonizeTo) 155 156 log.debug(processType, 'Started daemon') 157 158 # from now on I should keep running until killed, whatever happens 159 path = writePidFile(processType, processName, file=pidFile) 160 log.debug(processType, 'written pid file %s', path) 161 162 # import inside function so we avoid affecting startup 163 from twisted.internet import reactor 164 def _deletePidFile(): 165 log.debug(processType, 'deleting pid file') 166 deletePidFile(processType, processName)
167 reactor.addSystemEventTrigger('after', 'shutdown', 168 _deletePidFile) 169 170
171 -def _getPidPath(type, name=None):
172 """ 173 Get the full path to the pid file for the given process type and name. 174 """ 175 path = os.path.join(configure.rundir, '%s.pid' % type) 176 if name: 177 path = os.path.join(configure.rundir, '%s.%s.pid' % (type, name)) 178 log.debug('common', 'getPidPath for type %s, name %r: %s' % ( 179 type, name, path)) 180 return path
181
182 -def writePidFile(type, name=None, file=None):
183 """ 184 Write a pid file in the run directory, using the given process type 185 and process name for the filename. 186 187 @rtype: str 188 @returns: full path to the pid file that was written 189 """ 190 # don't shadow builtin file 191 pidFile = file 192 if pidFile is None: 193 ensureDir(configure.rundir, "rundir") 194 filename = _getPidPath(type, name) 195 pidFile = open(filename, 'w') 196 else: 197 filename = pidFile.name 198 pidFile.write("%d\n" % (os.getpid(),)) 199 pidFile.close() 200 os.chmod(filename, 0644) 201 return filename
202
203 -def _acquirePidFile(type, name=None):
204 """ 205 Open a PID file for writing, using the given process type and 206 process name for the filename. The returned file can be then passed 207 to writePidFile after forking. 208 209 @rtype: str 210 @returns: file object, open for writing 211 """ 212 ensureDir(configure.rundir, "rundir") 213 path = _getPidPath(type, name) 214 return open(path, 'w')
215
216 -def deletePidFile(type, name=None):
217 """ 218 Delete the pid file in the run directory, using the given process type 219 and process name for the filename. 220 221 @rtype: str 222 @returns: full path to the pid file that was written 223 """ 224 path = _getPidPath(type, name) 225 os.unlink(path) 226 return path
227
228 -def getPid(type, name=None):
229 """ 230 Get the pid from the pid file in the run directory, using the given 231 process type and process name for the filename. 232 233 @returns: pid of the process, or None if not running or file not found. 234 """ 235 236 pidPath = _getPidPath(type, name) 237 log.log('common', 'pidfile for %s %s is %s' % (type, name, pidPath)) 238 if not os.path.exists(pidPath): 239 return 240 241 pidFile = open(pidPath, 'r') 242 pid = pidFile.readline() 243 pidFile.close() 244 if not pid or int(pid) == 0: 245 return 246 247 return int(pid)
248
249 -def signalPid(pid, signum):
250 """ 251 Send the given process a signal. 252 253 @returns: whether or not the process with the given pid was running 254 """ 255 try: 256 os.kill(pid, signum) 257 return True 258 except OSError, e: 259 # see man 2 kill 260 if e.errno == errno.EPERM: 261 # exists but belongs to a different user 262 return True 263 if e.errno == errno.ESRCH: 264 # pid does not exist 265 return False 266 raise
267
268 -def termPid(pid):
269 """ 270 Send the given process a TERM signal. 271 272 @returns: whether or not the process with the given pid was running 273 """ 274 return signalPid(pid, signal.SIGTERM)
275
276 -def killPid(pid):
277 """ 278 Send the given process a KILL signal. 279 280 @returns: whether or not the process with the given pid was running 281 """ 282 return signalPid(pid, signal.SIGKILL)
283
284 -def checkPidRunning(pid):
285 """ 286 Check if the given pid is currently running. 287 288 @returns: whether or not a process with that pid is active. 289 """ 290 return signalPid(pid, 0)
291
292 -def waitPidFile(type, name=None):
293 """ 294 Wait for the given process type and name to have started and created 295 a pid file. 296 297 Return the pid. 298 """ 299 # getting it from the start avoids an unneeded time.sleep 300 pid = getPid(type, name) 301 302 while not pid: 303 time.sleep(0.1) 304 pid = getPid(type, name) 305 306 return pid
307
308 -def waitForTerm():
309 """ 310 Wait until we get killed by a TERM signal (from someone else). 311 """ 312 313 class Waiter: 314 def __init__(self): 315 self.sleeping = True 316 import signal 317 self.oldhandler = signal.signal(signal.SIGTERM, 318 self._SIGTERMHandler)
319 320 def _SIGTERMHandler(self, number, frame): 321 self.sleeping = False 322 323 def sleep(self): 324 while self.sleeping: 325 time.sleep(0.1) 326 327 waiter = Waiter() 328 waiter.sleep() 329