Changeset 1690
- Timestamp:
- 06/24/07 00:37:24
- Files:
-
- trunk/cherrypy/__init__.py (modified) (2 diffs)
- trunk/cherrypy/_cpconfig.py (modified) (1 diff)
- trunk/cherrypy/_cpmodpy.py (modified) (1 diff)
- trunk/cherrypy/lib/sessions.py (modified) (1 diff)
- trunk/cherrypy/restsrv/__init__.py (modified) (1 diff)
- trunk/cherrypy/restsrv/plugins.py (modified) (22 diffs)
- trunk/cherrypy/restsrv/win32.py (modified) (2 diffs)
- trunk/cherrypy/restsrv/wspbus.py (moved) (moved from trunk/cherrypy/restsrv/base.py) (6 diffs)
- trunk/cherrypy/test/test_states.py (modified) (16 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/cherrypy/__init__.py
r1671 r1690 171 171 from cherrypy._cptree import Application 172 172 from cherrypy import _cpwsgi as wsgi 173 from cherrypy import _cpserver174 server = _cpserver.Server()175 173 176 174 from cherrypy import restsrv 177 engine = restsrv.engine 175 try: 176 from cherrypy.restsrv import win32 as restsrvwin 177 engine = restsrvwin.Win32Bus() 178 except ImportError: 179 engine = restsrv.bus 180 178 181 179 182 # Timeout monitor … … 203 206 restsrv.plugins.Reexec(engine) 204 207 _thread_manager = restsrv.plugins.ThreadManager(engine) 208 209 from cherrypy import _cpserver 210 server = _cpserver.Server() 205 211 206 212 trunk/cherrypy/_cpconfig.py
r1648 r1690 303 303 engine.publish('CherryPy Timeout Monitor', 'frequency', v) 304 304 elif k == 'reexec_retry': 305 engine.publish('re exec', 'retry', v)305 engine.publish('restart', 'retry', v) 306 306 elif k == 'SIGHUP': 307 307 engine.listeners['SIGHUP'] = set([v]) trunk/cherrypy/_cpmodpy.py
r1689 r1690 90 90 }) 91 91 92 cherrypy.engine.start() 93 cherrypy.engine.wait() 92 engine = cherrypy.engine 93 engine.start() 94 engine.block(engine.states.STARTED) 94 95 95 96 def cherrypy_cleanup(data): trunk/cherrypy/lib/sessions.py
r1673 r1690 113 113 if not cls.clean_thread: 114 114 # clean_up is in instancemethod and not a classmethod, 115 # so tool config can be accessed inside the method. 116 from cherrypy import restsrv 117 t = restsrv.plugins.Monitor(cherrypy.engine, self.clean_up, 118 "CP Session Cleanup") 115 # so that tool config can be accessed inside the method. 116 t = cherrypy.restsrv.plugins.Monitor( 117 cherrypy.engine, self.clean_up, "CP Session Cleanup") 119 118 t.frequency = self.clean_freq 120 119 cls.clean_thread = t trunk/cherrypy/restsrv/__init__.py
r1642 r1690 1 """ Manage an HTTP server process via an extensible Engine object.1 """Site container for an HTTP server. 2 2 3 An Engine object is used to contain and manage site-wide behavior: 4 daemonization, HTTP server instantiation, autoreload, signal handling, 5 drop privileges, initial logging, PID file management, etc. 3 A Web Site Process Bus object is used to connect applications, servers, 4 and frameworks with site-wide services such as daemonization, process 5 reload, signal handling, drop privileges, PID file management, logging 6 for all of these, and many more. 6 7 7 In addition, an Engine object provides a place for each web framework 8 to hook in custom code that runs in response to site-wide events (like 9 process start and stop), or which controls or otherwise interacts with 10 the site-wide components mentioned above. For example, a framework which 11 uses file-based templates would add known template filenames to the 12 autoreload component. 13 14 Ideally, an Engine object will be flexible enough to be useful in a variety 15 of invocation scenarios: 16 17 1. The deployer starts a site from the command line via a framework- 18 neutral deployment script; applications from multiple frameworks 19 are mixed in a single site. Command-line arguments and configuration 20 files are used to define site-wide components such as the HTTP server, 21 autoreload behavior, signal handling, etc. 22 2. The deployer starts a site via some other process, such as Apache; 23 applications from multiple frameworks are mixed in a single site. 24 Autoreload and signal handling (from Python at least) are disabled. 25 3. The deployer starts a site via a framework-specific mechanism; 26 for example, when running tests, exploring tutorials, or deploying 27 single applications from a single framework. The framework controls 28 which site-wide components are enabled as it sees fit. 29 30 The Engine object in this package uses topic-based publish-subscribe 31 messaging to accomplish all this. A few topic channels are built in 32 ('start', 'stop', 'restart' and 'graceful'). The 'plugins' module 33 defines a few others which are specific to each tool. Frameworks are 34 free to define their own. If a message is sent to a channel that has 35 not been defined or has no listeners, there is no effect. 36 37 In general, there should only ever be a single Engine object per process. 38 Frameworks share a single Engine object by publishing messages and 39 registering (subscribing) listeners. 8 The 'plugins' module defines a few abstract and concrete services for 9 use with the bus. Some use tool-specific channels; see the documentation 10 for each class. 40 11 """ 41 12 13 from cherrypy.restsrv.wspbus import bus 42 14 from cherrypy.restsrv import plugins 43 44 try:45 from cherrypy.restsrv import win3246 engine = win32.Engine()47 del win3248 except ImportError:49 from cherrypy.restsrv import base50 engine = base.Engine()51 del basetrunk/cherrypy/restsrv/plugins.py
r1684 r1690 1 """ Plugins for a restsrv Engine."""1 """Site services for use with a Web Site Process Bus.""" 2 2 3 3 import os … … 20 20 21 21 getattr: 22 >>> values = engine.publish('thing', 'attr')22 >>> values = bus.publish('thing', 'attr') 23 23 Note that the 'publish' method will return a list of values 24 24 (from potentially multiple subscribed objects). 25 25 26 26 setattr: 27 >>> engine.publish('thing', 'attr', value)28 29 call :30 >>> engine.publish('thing', 'attr()', *a, **kw)31 """ 32 33 def __init__(self, engine, channel):34 self. engine = engine27 >>> bus.publish('thing', 'attr', value) 28 29 call an attribute: 30 >>> bus.publish('thing', 'attr()', *a, **kw) 31 """ 32 33 def __init__(self, bus, channel): 34 self.bus = bus 35 35 self.channel = channel 36 engine.subscribe(self.channel, self.handle)36 bus.subscribe(self.channel, self.handle) 37 37 38 38 def handle(self, attr, *args, **kwargs): … … 50 50 51 51 class SignalHandler(object): 52 53 def __init__(self, engine): 54 # Make a map from signal numbers to names 55 self.signals = {} 56 for k, v in vars(_signal).items(): 57 if k.startswith('SIG') and not k.startswith('SIG_'): 58 self.signals[v] = k 59 60 self.engine = engine 61 62 def set_handler(self, signal, callback=None): 52 """Register bus channels (and listeners) for system signals. 53 54 By default, instantiating this object registers the following signals 55 and listeners: 56 57 TERM: bus.exit 58 HUP : bus.restart 59 USR1: bus.graceful 60 """ 61 62 # Map from signal numbers to names 63 signals = {} 64 for k, v in vars(_signal).items(): 65 if k.startswith('SIG') and not k.startswith('SIG_'): 66 signals[v] = k 67 del k, v 68 69 def __init__(self, bus): 70 self.bus = bus 71 72 # Set default handlers 73 for sig, func in [('SIGTERM', bus.exit), 74 ('SIGHUP', bus.restart), 75 ('SIGUSR1', bus.graceful)]: 76 try: 77 self.set_handler(sig, func) 78 except ValueError: 79 pass 80 81 def set_handler(self, signal, listener=None): 63 82 """Register a handler for the given signal (number or name). 64 83 65 If the optional callback is included, it will be registered 66 as a listener for the given signal. 84 If the optional 'listener' argument is provided, it will be 85 registered as a listener for the given signal's channel. 86 87 If the given signal name or number is not available on the current 88 platform, ValueError is raised. 67 89 """ 68 90 if isinstance(signal, basestring): 69 91 signum = getattr(_signal, signal, None) 70 92 if signum is None: 71 r eturn # ?93 raise ValueError("No such signal: %r" % signal) 72 94 signame = signal 73 95 else: 96 try: 97 signame = self.signals[signal] 98 except KeyError: 99 raise ValueError("No such signal: %r" % signal) 74 100 signum = signal 75 signame = self.signals[signal]76 101 77 102 # Should we do something with existing signal handlers? 78 103 # cur = _signal.getsignal(signum) 79 104 _signal.signal(signum, self._handle_signal) 80 if callbackis not None:81 self. engine.subscribe(signame, callback)105 if listener is not None: 106 self.bus.subscribe(signame, listener) 82 107 83 108 def _handle_signal(self, signum=None, frame=None): 84 109 """Python signal handler (self.set_handler registers it for you).""" 85 self. engine.publish(self.signals[signum])110 self.bus.publish(self.signals[signum]) 86 111 87 112 88 113 class Reexec(SubscribedObject): 89 90 def __init__(self, engine, retry=2): 91 self.engine = engine 114 """A process restarter (using execv) for the 'restart' WSPBus channel. 115 116 retry: the number of seconds to wait for all parent threads to stop. 117 This is only necessary for platforms like OS X which error if all 118 threads are not absolutely terminated before calling execv. 119 """ 120 121 def __init__(self, bus, retry=2): 122 self.bus = bus 92 123 self.retry = retry 93 engine.subscribe('reexec', self)124 bus.subscribe('restart', self) 94 125 95 126 def __call__(self): 96 127 """Re-execute the current process.""" 97 128 args = sys.argv[:] 98 self. engine.log('Re-spawning %s' % ' '.join(args))129 self.bus.log('Re-spawning %s' % ' '.join(args)) 99 130 args.insert(0, sys.executable) 100 131 … … 124 155 """ 125 156 126 def __init__(self, engine):127 self. engine = engine157 def __init__(self, bus): 158 self.bus = bus 128 159 129 160 try: … … 143 174 if umask is not None: 144 175 old_umask = os.umask(umask) 145 self. engine.log('umask old: %03o, new: %03o' %176 self.bus.log('umask old: %03o, new: %03o' % 146 177 (old_umask, umask)) 147 178 else: … … 172 203 return name, group 173 204 174 self. engine.log('Started as %r/%r' % names())205 self.bus.log('Started as %r/%r' % names()) 175 206 if gid is not None: 176 207 os.setgid(gid) 177 208 if uid is not None: 178 209 os.setuid(uid) 179 self. engine.log('Running as %r/%r' % names())210 self.bus.log('Running as %r/%r' % names()) 180 211 181 212 if umask is not None: 182 213 old_umask = os.umask(umask) 183 self. engine.log('umask old: %03o, new: %03o' %214 self.bus.log('umask old: %03o, new: %03o' % 184 215 (old_umask, umask)) 185 216 __call__.priority = 70 … … 189 220 """Daemonize the running script. 190 221 222 Use this with a Web Site Process Bus via: 223 224 bus.subscribe('start', daemonize) 225 191 226 When this method returns, the process is completely decoupled from the 192 parent environment.""" 227 parent environment. 228 """ 193 229 194 230 # See http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16 … … 211 247 except OSError, exc: 212 248 # Python raises OSError rather than returning negative numbers. 213 sys.exit("%s: fork #1 failed: (%d) %s\n" % (sys.argv[0], exc.errno, exc.strerror)) 249 sys.exit("%s: fork #1 failed: (%d) %s\n" 250 % (sys.argv[0], exc.errno, exc.strerror)) 214 251 215 252 os.setsid() … … 222 259 sys.exit(0) # Exit second parent 223 260 except OSError, exc: 224 sys.exit("%s: fork #2 failed: (%d) %s\n" % (sys.argv[0], exc.errno, exc.strerror)) 261 sys.exit("%s: fork #2 failed: (%d) %s\n" 262 % (sys.argv[0], exc.errno, exc.strerror)) 225 263 226 264 os.chdir("/") … … 237 275 238 276 class PIDFile(object): 239 240 def __init__(self, engine, pidfile): 241 self.engine = engine 277 """Maintain a PID file via a WSPBus.""" 278 279 def __init__(self, bus, pidfile): 280 self.bus = bus 242 281 self.pidfile = pidfile 243 engine.subscribe('start', self.start)244 engine.subscribe('stop', self.stop)282 bus.subscribe('start', self.start) 283 bus.subscribe('stop', self.stop) 245 284 246 285 def start(self): … … 258 297 259 298 class PerpetualTimer(threading._Timer): 299 """A subclass of threading._Timer whose run() method repeats.""" 260 300 261 301 def run(self): … … 268 308 269 309 class Monitor(SubscribedObject): 270 """Subscriber which periodically runs a callback in a separate thread.""" 310 """WSPBus listener to periodically run a callback in its own thread. 311 312 bus: a Web Site Process Bus object. 313 callback: the function to call at intervals. 314 channel: optional. If provided, the name of the channel to use 315 for managing this object. Defaults to class.__name__, 316 so either provide a channel name or only use one instance 317 of any given subclass. 318 319 frequency: the time in seconds between callback runs. 320 """ 271 321 272 322 frequency = 60 273 323 274 def __init__(self, engine, callback, channel=None):324 def __init__(self, bus, callback, channel=None): 275 325 self.callback = callback 276 326 self.thread = None … … 278 328 if channel is None: 279 329 channel = self.__class__.__name__ 280 SubscribedObject.__init__(self, engine, channel)330 SubscribedObject.__init__(self, bus, channel) 281 331 282 332 self.listeners = [('start', self.start), … … 287 337 288 338 def attach(self): 339 """Register this monitor as a (multi-channel) listener on the bus.""" 289 340 for point, callback in self.listeners: 290 self. engine.subscribe(point, callback)341 self.bus.subscribe(point, callback) 291 342 292 343 def detach(self): 344 """Unregister this monitor as a listener on the bus.""" 293 345 for point, callback in self.listeners: 294 self. engine.unsubscribe(point, callback)346 self.bus.unsubscribe(point, callback) 295 347 296 348 def start(self): 349 """Start our callback in its own perpetual timer thread.""" 297 350 if self.frequency > 0: 298 351 self.thread = PerpetualTimer(self.frequency, self.callback) … … 301 354 302 355 def stop(self): 356 """Stop our callback's perpetual timer thread.""" 303 357 if self.thread: 304 358 if self.thread is not threading.currentThread(): … … 308 362 309 363 def graceful(self): 364 """Stop the callback's perpetual timer thread and restart it.""" 310 365 self.stop() 311 366 self.start() … … 318 373 match = '.*' 319 374 320 def __init__(self, engine):375 def __init__(self, bus): 321 376 self.mtimes = {} 322 377 self.files = set() 323 Monitor.__init__(self, engine, self.run)378 Monitor.__init__(self, bus, self.run) 324 379 325 380 def add(self, filename): 381 """Add a file to monitor for changes.""" 326 382 self.files.add(filename) 327 383 328 384 def discard(self, filename): 385 """Remove a file to monitor for changes.""" 329 386 self.files.discard(filename) 330 387 331 388 def start(self): 389 """Start our own perpetual timer thread for self.run.""" 332 390 self.mtimes = {} 333 391 self.files = set() … … 367 425 if mtime is None or mtime > oldtime: 368 426 # The file has been deleted or modified. 369 self. engine.restart()427 self.bus.restart() 370 428 371 429 … … 376 434 the 'acquire_thread' and 'release_thread' channels (for each thread). 377 435 This will register/unregister the current thread and publish to 378 'start_thread' and 'stop_thread' listeners in the engineas needed.436 'start_thread' and 'stop_thread' listeners in the bus as needed. 379 437 380 438 If threads are created and destroyed by code you do not control … … 382 440 publish to 'acquire_thread' only. You should not publish to 383 441 'release_thread' in this case, since you do not know whether 384 the thread will be re-used or not. The enginewill call442 the thread will be re-used or not. The bus will call 385 443 'stop_thread' listeners for you when it stops. 386 444 """ 387 445 388 def __init__(self, engine):446 def __init__(self, bus): 389 447 self.threads = {} 390 self. engine = engine391 engine.subscribe('acquire_thread', self.acquire)392 engine.subscribe('release_thread', self.release)393 engine.subscribe('stop', self.release_all)394 engine.subscribe('graceful', self.release_all)448 self.bus = bus 449 bus.subscribe('acquire_thread', self.acquire) 450 bus.subscribe('release_thread', self.release) 451 bus.subscribe('stop', self.release_all) 452 bus.subscribe('graceful', self.release_all) 395 453 396 454 def acquire(self): … … 406 464 i = len(self.threads) + 1 407 465 self.threads[thread_ident] = i 408 self. engine.publish('start_thread', i)466 self.bus.publish('start_thread', i) 409 467 410 468 def release(self): … … 413 471 i = self.threads.pop(thread_ident, None) 414 472 if i is not None: 415 self. engine.publish('stop_thread', i)473 self.bus.publish('stop_thread', i) 416 474 417 475 def release_all(self): 476 """Release all threads and run all 'stop_thread' listeners.""" 418 477 for thread_ident, i in self.threads.iteritems(): 419 self. engine.publish('stop_thread', i)478 self.bus.publish('stop_thread', i) 420 479 self.threads.clear() trunk/cherrypy/restsrv/win32.py
r1644 r1690 8 8 import win32serviceutil 9 9 10 from cherrypy.restsrv import base10 from cherrypy.restsrv import wspbus 11 11 12 12 13 class Engine(base.Engine): 13 class Win32Bus(wspbus.Bus): 14 """A Web Site Process Bus implementation for Win32. 15 16 Instead of using time.sleep for blocking, this bus uses native 17 win32event objects. 18 """ 14 19 15 20 def __init__(self): 16 base.Engine.__init__(self)17 self.stop_event = win32event.CreateEvent(None, 0, 0, None)18 w in32api.SetConsoleCtrlHandler(self.console_event)21 self.events = {} 22 win32api.SetConsoleCtrlHandler(self._console_event) 23 wspbus.Bus.__init__(self) 19 24 20 def console_event(self, event): 25 def _console_event(self, event): 26 """.""" 21 27 if event in (win32con.CTRL_C_EVENT, 22 28 win32con.CTRL_BREAK_EVENT, 23 29 win32con.CTRL_CLOSE_EVENT): 24 self.log('Console event %s: shutting down engine' % event)30 self.log('Console event %s: shutting down bus' % event) 25 31 self.stop() 26 32 return 1 27 33 return 0 28 34 29 def block(self, interval=1):30 """ Block forever (wait for stop(), KeyboardInterrupt or SystemExit)."""35 def _get_state_event(self, state): 36 """Return a win32event for the given state (creating it if needed).""" 31 37 try: 32 win32event.WaitForSingleObject(self.stop_event, win32event.INFINITE) 38 return self.events[state] 39 except KeyError: 40 event = win32event.CreateEvent(None, 0, 0, None) 41 self.events[state] = event 42 return event 43 44 def _get_state(self): 45 return self._state 46 def _set_state(self, value): 47 self._state = value 48 event = self._get_state_event(value) 49 win32event.PulseEvent(event) 50 state = property(_get_state, _set_state) 51 52 def block(self, state=wspbus.states.STOPPED, interval=1): 53 """Wait for the given state, KeyboardInterrupt or SystemExit. 54 55 Since this class uses native win32event objects, the interval 56 argument is ignored. 57 """ 58 event = self._get_state_event(state) 59 try: 60 win32event.WaitForSingleObject(event, win32event.INFINITE) 33 61 except SystemExit: 34 self.log('SystemExit raised: shutting down engine')62 self.log('SystemExit raised: shutting down bus') 35 63 self.stop() 36 64 raise 37 38 def stop(self):39 """Stop the engine."""40 if self.state != base.STOPPED:41 self.log('Engine shutting down')42 self.publish('stop')43 win32event.PulseEvent(self.stop_event)44 self.state = base.STOPPED45 65 46 66 … … 89 109 def SvcDoRun(self): 90 110 from cherrypy import restsrv 91 restsrv. engine.start()92 restsrv. engine.block()111 restsrv.bus.start() 112 restsrv.bus.block() 93 113 94 114 def SvcStop(self): 95 115 from cherrypy import restsrv 96 116 self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) 97 restsrv. engine.stop()117 restsrv.bus.stop() 98 118 99 119 def SvcOther(self, control): 100 restsrv. engine.publish(control_codes.key_for(control))120 restsrv.bus.publish(control_codes.key_for(control)) 101 121 102 122 trunk/cherrypy/restsrv/wspbus.py
r1683 r1690 1 """Base Engine class for restsrv.""" 1 """An implementation of the Web Site Process Bus. 2 3 This module is completely standalone, depending only on the stdlib. 4 5 Web Site Process Bus 6 -------------------- 7 8 A Bus object is used to contain and manage site-wide behavior: 9 daemonization, HTTP server start/stop, process reload, signal handling, 10 drop privileges, PID file management, logging for all of these, 11 and many more. 12 13 In addition, a Bus object provides a place for each web framework 14 to register code that runs in response to site-wide events (like 15 process start and stop), or which controls or otherwise interacts with 16 the site-wide components mentioned above. For example, a framework which 17 uses file-based templates would add known template filenames to an 18 autoreload component. 19 20 Ideally, a Bus object will be flexible enough to be useful in a variety 21 of invocation scenarios: 22 23 1. The deployer starts a site from the command line via a framework- 24 neutral deployment script; applications from multiple frameworks 25 are mixed in a single site. Command-line arguments and configuration 26 files are used to define site-wide components such as the HTTP server, 27 WSGI component graph, autoreload behavior, signal handling, etc. 28 2. The deployer starts a site via some other process, such as Apache; 29 applications from multiple frameworks are mixed in a single site. 30 Autoreload and signal handling (from Python at least) are disabled. 31 3. The deployer starts a site via a framework-specific mechanism; 32 for example, when running tests, exploring tutorials, or deploying 33 single applications from a single framework. The framework controls 34 which site-wide components are enabled as it sees fit. 35 36 The Bus object in this package uses topic-based publish-subscribe 37 messaging to accomplish all this. A few topic channels are built in 38 ('start', 'stop', 'exit', 'restart' and 'graceful'). Frameworks and 39 site containers are free to define their own. If a message is sent to a 40 channel that has not been defined or has no listeners, there is no effect. 41 42 In general, there should only ever be a single Bus object per process. 43 Frameworks and site containers share a single Bus object by publishing 44 messages and registering (subscribing) listeners. 45 46 The Bus object works as a finite state machine which models the current 47 state of the process. Bus methods move it from one state to another; 48 those methods then publish to subscribed listeners on the channel for 49 the new state. 50 """ 2 51 3 52 try: … … 11 60 12 61 13 # Use a flag to indicate the state of the engine. 14 STOPPED = 0 15 STARTING = None 16 STARTED = 1 17 18 19 class Engine(object): 20 """Process controls for HTTP site deployment.""" 21 22 state = STOPPED 62 # Use a flag to indicate the state of the bus. 63 class _StateEnum(object): 64 class State(object): 65 pass 66 states = _StateEnum() 67 states.STOPPED = states.State() 68 states.STARTING = states.State() 69 states.STARTED = states.State() 70 states.STOPPING = states.State() 71 72 73 class Bus(object): 74 """Process state-machine and messenger for HTTP site deployment.""" 75 76 states = states 77 state = states.STOPPED 23 78 24 79 def __init__(self): 25 self.state = STOPPED 26 self.listeners = {} 80 self.state = states.STOPPED 81 self.listeners = dict([(channel, set()) for channel 82 in ('start', 'stop', 'exit', 83 'restart', 'graceful')]) 27 84 self._priorities = {} 28 85 … … 74 131 75 132 def start(self): 76 """Start the engine."""77 self.state = STARTING78 self.log(' Enginestarting')133 """Start all services.""" 134 self.state = states.STARTING 135 self.log('Bus starting') 79 136 self.publish('start') 80 self.state = STARTED 81 82 def wait(self, interval=0.1): 83 """Block the caller until the Engine is in the STARTED state.""" 84 while not (self.state == STARTED): 85 time.sleep(interval) 137 self.state = states.STARTED 86 138 87 139 def exit(self, status=0): 88 """Stop the engineand exit the process."""140 """Stop all services and exit the process.""" 89 141 self.stop() 142 143 self.log('Bus exit') 144 self.publish('exit') 90 145 sys.exit(status) 91 146 … … 93 148 """Restart the process (may close connections).""" 94 149 self.stop() 95 self.log('Engine restart') 96 self.publish('reexec') 150 151 self.log('Bus restart') 152 self.publish('restart') 97 153 98 154 def graceful(self): 99 """ Restart the engine without closing connections."""100 self.log(' Engine graceful restart')155 """Advise all services to reload.""" 156 self.log('Bus graceful') 101 157 self.publish('graceful') 102 158 103 def block(self, interval=1):104 """ Block forever (wait for stop(), KeyboardInterrupt or SystemExit)."""159 def block(self, state=states.STOPPED, interval=0.1): 160 """Wait for the given state, KeyboardInterrupt or SystemExit.""" 105 161 try: 106 while self.state != STOPPED:162 while self.state != state: 107 163 time.sleep(interval) 108 164 except (KeyboardInterrupt, IOError): 109 165 # The time.sleep call might raise 110 # "IOError: [Errno 4] Interrupted function call" .111 self.log('Keyboard Interrupt: shutting down engine')166 # "IOError: [Errno 4] Interrupted function call" on KBInt. 167 self.log('Keyboard Interrupt: shutting down bus') 112 168 self.stop() 113 169 except SystemExit: 114 self.log('SystemExit raised: shutting down engine')170 self.log('SystemExit raised: shutting down bus') 115 171 self.stop() 116 172 raise 117 173 118 174 def stop(self): 119 """Stop the engine."""120 if self.state != STOPPED:121 self.log('Engine shutting down')122 self.publish('stop')123 self.state =STOPPED175 """Stop all services.""" 176 self.state = states.STOPPING 177 self.log('Bus stopping') 178 self.publish('stop') 179 self.state = states.STOPPED 124 180 125 181 def start_with_callback(self, func, args=None, kwargs=None): … … 132 188 133 189 def _callback(func, *a, **kw): 134 self. wait()190 self.block(states.STARTED) 135 191 func(*a, **kw) 136 192 t = threading.Thread(target=_callback, args=args, kwargs=kwargs) 137 t.setName(' EngineCallback ' + t.getName())193 t.setName('Bus Callback ' + t.getName()) 138 194 t.start() 139 195 … … 156 212 return "".join(traceback.format_exception(*exc)) 157 213 214 bus = Bus() 215 trunk/cherrypy/test/test_states.py
r1660 r1690 11 11 12 12 import cherrypy 13 engine = cherrypy.engine 13 14 14 15 … … 23 24 24 25 def graceful(self): 25 cherrypy.engine.graceful()26 engine.graceful() 26 27 return "app was (gracefully) restarted succesfully" 27 28 graceful.exposed = True … … 77 78 def test_0_NormalStateFlow(self): 78 79 if not self.server_class: 79 # Without having called " cherrypy.engine.start()", we should80 # Without having called "engine.start()", we should 80 81 # get a 503 Service Unavailable response. 81 82 self.getPage("/") … … 89 90 # Test server start 90 91 cherrypy.server.quickstart(self.server_class) 91 cherrypy.engine.start()92 self.assertEqual( cherrypy.engine.state, 1)92 engine.start() 93 self.assertEqual(engine.state, engine.states.STARTED) 93 94 94 95 if self.server_class: … … 107 108 108 109 # Test engine stop. This will also stop the HTTP server. 109 cherrypy.engine.stop()110 self.assertEqual( cherrypy.engine.state, 0)110 engine.stop() 111 self.assertEqual(engine.state, engine.states.STOPPED) 111 112 112 113 # Verify that our custom stop function was called … … 125 126 self.getPage("/") 126 127 self.assertBody("Hello World") 127 cherrypy.engine.stop()128 engine.stop() 128 129 cherrypy.server.start() 129 cherrypy.engine.start_with_callback(stoptest)130 cherrypy.engine.block()131 self.assertEqual( cherrypy.engine.state, 0)130 engine.start_with_callback(stoptest) 131 engine.block() 132 self.assertEqual(engine.state, engine.states.STOPPED) 132 133 133 134 def test_1_Restart(self): 134 135 cherrypy.server.start() 135 cherrypy.engine.start()136 engine.start() 136 137 137 138 # The db_connection should be running now … … 144 145 145 146 # Test server restart from this thread 146 cherrypy.engine.graceful()147 self.assertEqual( cherrypy.engine.state, 1)147 engine.graceful() 148 self.assertEqual(engine.state, engine.states.STARTED) 148 149 self.getPage("/") 149 150 self.assertBody("Hello World") … … 154 155 # Test server restart from inside a page handler 155 156 self.getPage("/graceful") 156 self.assertEqual( cherrypy.engine.state, 1)157 self.assertEqual(engine.state, engine.states.STARTED) 157 158 self.assertBody("app was (gracefully) restarted succesfully") 158 159 self.assertEqual(db_connection.running, True) … … 162 163 self.assertEqual(len(db_connection.threads), 0) 163 164 164 cherrypy.engine.stop()165 self.assertEqual( cherrypy.engine.state, 0)165 engine.stop() 166 self.assertEqual(engine.state, engine.states.STOPPED) 166 167 self.assertEqual(db_connection.running, False) 167 168 self.assertEqual(len(db_connection.threads), 0) … … 172 173 # Raise a keyboard interrupt in the HTTP server's main thread. 173 174 # We must start the server in this, the main thread 174 cherrypy.engine.start()175 engine.start() 175 176 cherrypy.server.start() 176 177 … … 184 185 185 186 cherrypy.server.httpservers.keys()[0].interrupt = KeyboardInterrupt 186 while cherrypy.engine.state != 0: 187 time.sleep(0.1) 187 engine.block() 188 188 189 189 self.assertEqual(db_connection.running, False) 190 190 self.assertEqual(len(db_connection.threads), 0) 191 self.assertEqual( cherrypy.engine.state, 0)191 self.assertEqual(engine.state, engine.states.STOPPED) 192 192 finally: 193 193 self.persistent = False … … 197 197 # This should raise a BadStatusLine error, since the worker 198 198 # thread will just die without writing a response. 199 cherrypy.engine.start()199 engine.start() 200 200 cherrypy.server.start() 201 201 … … 208 208 self.fail("AssertionError: BadStatusLine not raised") 209 209 210 while cherrypy.engine.state != 0: 211 time.sleep(0.1) 210 engine.block() 212 211 self.assertEqual(db_connection.running, False) 213 212 self.assertEqual(len(db_connection.threads), 0) … … 216 215 cherrypy.config.update({'response.timeout': 0.2}) 217 216 218 cherrypy.engine.start()217 engine.start() 219 218 cherrypy.server.start() 220 219 try: … … 242 241 self.assertInBody("raise cherrypy.TimeoutError()") 243 242 finally: 244 cherrypy.engine.stop()243 engine.stop() 245 244 246 245 def test_4_Autoreload(self): … … 302 301 ServerStateTests.server_class = server 303 302 suite = helper.CPTestLoader.loadTestsFromTestCase(ServerStateTests) 304 engine = cherrypy.engine305 303 try: 306 304 global db_connection

