plugins.py (15004B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\carbon\common\lib\cherrypy\process\plugins.py 2 import os 3 import re 4 import signal as _signal 5 import sys 6 import time 7 import threading 8 from cherrypy._cpcompat import basestring, get_daemon, get_thread_ident, ntob, set 9 _module__file__base = os.getcwd() 10 11 class SimplePlugin(object): 12 bus = None 13 14 def __init__(self, bus): 15 self.bus = bus 16 17 def subscribe(self): 18 for channel in self.bus.listeners: 19 method = getattr(self, channel, None) 20 if method is not None: 21 self.bus.subscribe(channel, method) 22 23 def unsubscribe(self): 24 for channel in self.bus.listeners: 25 method = getattr(self, channel, None) 26 if method is not None: 27 self.bus.unsubscribe(channel, method) 28 29 30 class SignalHandler(object): 31 handlers = {} 32 signals = {} 33 for k, v in vars(_signal).items(): 34 if k.startswith('SIG') and not k.startswith('SIG_'): 35 signals[v] = k 36 37 del k 38 del v 39 40 def __init__(self, bus): 41 self.bus = bus 42 self.handlers = {'SIGTERM': self.bus.exit, 43 'SIGHUP': self.handle_SIGHUP, 44 'SIGUSR1': self.bus.graceful} 45 if sys.platform[:4] == 'java': 46 del self.handlers['SIGUSR1'] 47 self.handlers['SIGUSR2'] = self.bus.graceful 48 self.bus.log('SIGUSR1 cannot be set on the JVM platform. Using SIGUSR2 instead.') 49 self.handlers['SIGINT'] = self._jython_SIGINT_handler 50 self._previous_handlers = {} 51 52 def _jython_SIGINT_handler(self, signum = None, frame = None): 53 self.bus.log('Keyboard Interrupt: shutting down bus') 54 self.bus.exit() 55 56 def subscribe(self): 57 for sig, func in self.handlers.items(): 58 try: 59 self.set_handler(sig, func) 60 except ValueError: 61 pass 62 63 def unsubscribe(self): 64 for signum, handler in self._previous_handlers.items(): 65 signame = self.signals[signum] 66 if handler is None: 67 self.bus.log('Restoring %s handler to SIG_DFL.' % signame) 68 handler = _signal.SIG_DFL 69 else: 70 self.bus.log('Restoring %s handler %r.' % (signame, handler)) 71 try: 72 our_handler = _signal.signal(signum, handler) 73 if our_handler is None: 74 self.bus.log('Restored old %s handler %r, but our handler was not registered.' % (signame, handler), level=30) 75 except ValueError: 76 self.bus.log('Unable to restore %s handler %r.' % (signame, handler), level=40, traceback=True) 77 78 def set_handler(self, signal, listener = None): 79 if isinstance(signal, basestring): 80 signum = getattr(_signal, signal, None) 81 if signum is None: 82 raise ValueError('No such signal: %r' % signal) 83 signame = signal 84 else: 85 try: 86 signame = self.signals[signal] 87 except KeyError: 88 raise ValueError('No such signal: %r' % signal) 89 90 signum = signal 91 prev = _signal.signal(signum, self._handle_signal) 92 self._previous_handlers[signum] = prev 93 if listener is not None: 94 self.bus.log('Listening for %s.' % signame) 95 self.bus.subscribe(signame, listener) 96 97 def _handle_signal(self, signum = None, frame = None): 98 signame = self.signals[signum] 99 self.bus.log('Caught signal %s.' % signame) 100 self.bus.publish(signame) 101 102 def handle_SIGHUP(self): 103 if os.isatty(sys.stdin.fileno()): 104 self.bus.log('SIGHUP caught but not daemonized. Exiting.') 105 self.bus.exit() 106 else: 107 self.bus.log('SIGHUP caught while daemonized. Restarting.') 108 self.bus.restart() 109 110 111 try: 112 import pwd, grp 113 except ImportError: 114 pwd, grp = (None, None) 115 116 class DropPrivileges(SimplePlugin): 117 118 def __init__(self, bus, umask = None, uid = None, gid = None): 119 SimplePlugin.__init__(self, bus) 120 self.finalized = False 121 self.uid = uid 122 self.gid = gid 123 self.umask = umask 124 125 def _get_uid(self): 126 return self._uid 127 128 def _set_uid(self, val): 129 if val is not None: 130 if pwd is None: 131 self.bus.log('pwd module not available; ignoring uid.', level=30) 132 val = None 133 elif isinstance(val, basestring): 134 val = pwd.getpwnam(val)[2] 135 self._uid = val 136 137 uid = property(_get_uid, _set_uid, doc='The uid under which to run. Availability: Unix.') 138 139 def _get_gid(self): 140 return self._gid 141 142 def _set_gid(self, val): 143 if val is not None: 144 if grp is None: 145 self.bus.log('grp module not available; ignoring gid.', level=30) 146 val = None 147 elif isinstance(val, basestring): 148 val = grp.getgrnam(val)[2] 149 self._gid = val 150 151 gid = property(_get_gid, _set_gid, doc='The gid under which to run. Availability: Unix.') 152 153 def _get_umask(self): 154 return self._umask 155 156 def _set_umask(self, val): 157 if val is not None: 158 try: 159 os.umask 160 except AttributeError: 161 self.bus.log('umask function not available; ignoring umask.', level=30) 162 val = None 163 164 self._umask = val 165 166 umask = property(_get_umask, _set_umask, doc='The default permission mode for newly created files and directories.\n \n Usually expressed in octal format, for example, ``0644``.\n Availability: Unix, Windows.\n ') 167 168 def start(self): 169 170 def current_ids(): 171 name, group = (None, None) 172 if pwd: 173 name = pwd.getpwuid(os.getuid())[0] 174 if grp: 175 group = grp.getgrgid(os.getgid())[0] 176 return (name, group) 177 178 if self.finalized: 179 if not (self.uid is None and self.gid is None): 180 self.bus.log('Already running as uid: %r gid: %r' % current_ids()) 181 elif self.uid is None and self.gid is None: 182 if pwd or grp: 183 self.bus.log('uid/gid not set', level=30) 184 else: 185 self.bus.log('Started as uid: %r gid: %r' % current_ids()) 186 if self.gid is not None: 187 os.setgid(self.gid) 188 os.setgroups([]) 189 if self.uid is not None: 190 os.setuid(self.uid) 191 self.bus.log('Running as uid: %r gid: %r' % current_ids()) 192 if self.finalized: 193 if self.umask is not None: 194 self.bus.log('umask already set to: %03o' % self.umask) 195 elif self.umask is None: 196 self.bus.log('umask not set', level=30) 197 else: 198 old_umask = os.umask(self.umask) 199 self.bus.log('umask old: %03o, new: %03o' % (old_umask, self.umask)) 200 self.finalized = True 201 202 start.priority = 77 203 204 205 class Daemonizer(SimplePlugin): 206 207 def __init__(self, bus, stdin = '/dev/null', stdout = '/dev/null', stderr = '/dev/null'): 208 SimplePlugin.__init__(self, bus) 209 self.stdin = stdin 210 self.stdout = stdout 211 self.stderr = stderr 212 self.finalized = False 213 214 def start(self): 215 if self.finalized: 216 self.bus.log('Already deamonized.') 217 if threading.activeCount() != 1: 218 self.bus.log('There are %r active threads. Daemonizing now may cause strange failures.' % threading.enumerate(), level=30) 219 sys.stdout.flush() 220 sys.stderr.flush() 221 try: 222 pid = os.fork() 223 if pid == 0: 224 pass 225 else: 226 self.bus.log('Forking once.') 227 os._exit(0) 228 except OSError: 229 exc = sys.exc_info()[1] 230 sys.exit('%s: fork #1 failed: (%d) %s\n' % (sys.argv[0], exc.errno, exc.strerror)) 231 232 os.setsid() 233 try: 234 pid = os.fork() 235 if pid > 0: 236 self.bus.log('Forking twice.') 237 os._exit(0) 238 except OSError: 239 exc = sys.exc_info()[1] 240 sys.exit('%s: fork #2 failed: (%d) %s\n' % (sys.argv[0], exc.errno, exc.strerror)) 241 242 os.chdir('/') 243 os.umask(0) 244 si = open(self.stdin, 'r') 245 so = open(self.stdout, 'a+') 246 se = open(self.stderr, 'a+') 247 os.dup2(si.fileno(), sys.stdin.fileno()) 248 os.dup2(so.fileno(), sys.stdout.fileno()) 249 os.dup2(se.fileno(), sys.stderr.fileno()) 250 self.bus.log('Daemonized to PID: %s' % os.getpid()) 251 self.finalized = True 252 253 start.priority = 65 254 255 256 class PIDFile(SimplePlugin): 257 258 def __init__(self, bus, pidfile): 259 SimplePlugin.__init__(self, bus) 260 self.pidfile = pidfile 261 self.finalized = False 262 263 def start(self): 264 pid = os.getpid() 265 if self.finalized: 266 self.bus.log('PID %r already written to %r.' % (pid, self.pidfile)) 267 else: 268 open(self.pidfile, 'wb').write(ntob('%s' % pid, 'utf8')) 269 self.bus.log('PID %r written to %r.' % (pid, self.pidfile)) 270 self.finalized = True 271 272 start.priority = 70 273 274 def exit(self): 275 try: 276 os.remove(self.pidfile) 277 self.bus.log('PID file removed: %r.' % self.pidfile) 278 except (KeyboardInterrupt, SystemExit): 279 raise 280 except: 281 pass 282 283 284 class PerpetualTimer(threading._Timer): 285 286 def run(self): 287 while True: 288 self.finished.wait(self.interval) 289 if self.finished.isSet(): 290 return 291 try: 292 self.function(*self.args, **self.kwargs) 293 except Exception: 294 self.bus.log('Error in perpetual timer thread function %r.' % self.function, level=40, traceback=True) 295 raise 296 297 298 class BackgroundTask(threading.Thread): 299 300 def __init__(self, interval, function, args = [], kwargs = {}): 301 threading.Thread.__init__(self) 302 self.interval = interval 303 self.function = function 304 self.args = args 305 self.kwargs = kwargs 306 self.running = False 307 308 def cancel(self): 309 self.running = False 310 311 def run(self): 312 self.running = True 313 while self.running: 314 time.sleep(self.interval) 315 if not self.running: 316 return 317 try: 318 self.function(*self.args, **self.kwargs) 319 except Exception: 320 self.bus.log('Error in background task thread function %r.' % self.function, level=40, traceback=True) 321 raise 322 323 def _set_daemon(self): 324 return True 325 326 327 class Monitor(SimplePlugin): 328 callback = None 329 frequency = 60 330 thread = None 331 332 def __init__(self, bus, callback, frequency = 60, name = None): 333 SimplePlugin.__init__(self, bus) 334 self.callback = callback 335 self.frequency = frequency 336 self.thread = None 337 self.name = name 338 339 def start(self): 340 if self.frequency > 0: 341 threadname = self.name or self.__class__.__name__ 342 if self.thread is None: 343 self.thread = BackgroundTask(self.frequency, self.callback) 344 self.thread.bus = self.bus 345 self.thread.setName(threadname) 346 self.thread.start() 347 self.bus.log('Started monitor thread %r.' % threadname) 348 else: 349 self.bus.log('Monitor thread %r already started.' % threadname) 350 351 start.priority = 70 352 353 def stop(self): 354 if self.thread is None: 355 self.bus.log('No thread running for %s.' % self.name or self.__class__.__name__) 356 else: 357 if self.thread is not threading.currentThread(): 358 name = self.thread.getName() 359 self.thread.cancel() 360 if not get_daemon(self.thread): 361 self.bus.log('Joining %r' % name) 362 self.thread.join() 363 self.bus.log('Stopped thread %r.' % name) 364 self.thread = None 365 366 def graceful(self): 367 self.stop() 368 self.start() 369 370 371 class Autoreloader(Monitor): 372 files = None 373 frequency = 1 374 match = '.*' 375 376 def __init__(self, bus, frequency = 1, match = '.*'): 377 self.mtimes = {} 378 self.files = set() 379 self.match = match 380 Monitor.__init__(self, bus, self.run, frequency) 381 382 def start(self): 383 if self.thread is None: 384 self.mtimes = {} 385 Monitor.start(self) 386 387 start.priority = 70 388 389 def sysfiles(self): 390 files = set() 391 for k, m in sys.modules.items(): 392 if re.match(self.match, k): 393 if hasattr(m, '__loader__') and hasattr(m.__loader__, 'archive'): 394 f = m.__loader__.archive 395 else: 396 f = getattr(m, '__file__', None) 397 if f is not None and not os.path.isabs(f): 398 f = os.path.normpath(os.path.join(_module__file__base, f)) 399 files.add(f) 400 401 return files 402 403 def run(self): 404 for filename in self.sysfiles() | self.files: 405 if filename: 406 if filename.endswith('.pyc'): 407 filename = filename[:-1] 408 oldtime = self.mtimes.get(filename, 0) 409 if oldtime is None: 410 continue 411 try: 412 mtime = os.stat(filename).st_mtime 413 except OSError: 414 mtime = None 415 416 if filename not in self.mtimes: 417 self.mtimes[filename] = mtime 418 elif mtime is None or mtime > oldtime: 419 self.bus.log('Restarting because %s changed.' % filename) 420 self.thread.cancel() 421 self.bus.log('Stopped thread %r.' % self.thread.getName()) 422 self.bus.restart() 423 return 424 425 426 class ThreadManager(SimplePlugin): 427 threads = None 428 429 def __init__(self, bus): 430 self.threads = {} 431 SimplePlugin.__init__(self, bus) 432 self.bus.listeners.setdefault('acquire_thread', set()) 433 self.bus.listeners.setdefault('start_thread', set()) 434 self.bus.listeners.setdefault('release_thread', set()) 435 self.bus.listeners.setdefault('stop_thread', set()) 436 437 def acquire_thread(self): 438 thread_ident = get_thread_ident() 439 if thread_ident not in self.threads: 440 i = len(self.threads) + 1 441 self.threads[thread_ident] = i 442 self.bus.publish('start_thread', i) 443 444 def release_thread(self): 445 thread_ident = get_thread_ident() 446 i = self.threads.pop(thread_ident, None) 447 if i is not None: 448 self.bus.publish('stop_thread', i) 449 450 def stop(self): 451 for thread_ident, i in self.threads.items(): 452 self.bus.publish('stop_thread', i) 453 454 self.threads.clear() 455 456 graceful = stop