Changeset 1204
- Timestamp:
- 07/19/06 17:42:40
- Files:
-
- trunk/cherrypy/_cpengine.py (modified) (10 diffs)
- trunk/cherrypy/_cpserver.py (modified) (3 diffs)
- trunk/cherrypy/_cpwsgiserver.py (modified) (5 diffs)
- trunk/cherrypy/config.py (modified) (3 diffs)
- trunk/cherrypy/lib/autoreload.py (deleted)
- trunk/cherrypy/test/test_states.py (modified) (4 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/cherrypy/_cpengine.py
r1183 r1204 2 2 3 3 import cgi 4 import os 4 5 import sys 5 6 import threading … … 8 9 import cherrypy 9 10 from cherrypy import _cprequest 10 from cherrypy.lib import autoreload11 11 12 12 # Use a flag to indicate the state of the application engine. … … 16 16 17 17 18 def fileattr(m): 19 if hasattr(m, "__loader__"): 20 if hasattr(m.__loader__, "archive"): 21 return m.__loader__.archive 22 return getattr(m, "__file__", None) 23 24 18 25 class Engine(object): 19 26 """The application engine, which exposes a request interface to (HTTP) servers.""" … … 24 31 def __init__(self): 25 32 self.state = STOPPED 26 self.interrupt = None27 33 28 34 # Startup/shutdown hooks … … 32 38 self.on_stop_thread_list = [] 33 39 self.seen_threads = {} 40 41 self.mtimes = {} 42 self.reload_files = [] 34 43 35 44 def start(self, blocking=True): 36 45 """Start the application engine.""" 37 46 self.state = STARTING 38 self.interrupt = None39 47 40 48 conf = cherrypy.config.get … … 44 52 cherrypy.config.log_config() 45 53 46 # Autoreload. Note that, if we're not starting our own HTTP server,47 # autoreload could do Very Bad Things when it calls sys.exit, but48 # deployers will just have to be educated and responsible for it.49 if conf('autoreload.on', False):50 try:51 freq = conf('autoreload.frequency', 1)52 autoreload.main(self._start, args=(blocking,), freq=freq)53 except KeyboardInterrupt:54 cherrypy.log("<Ctrl-C> hit: shutting down autoreloader", "ENGINE")55 cherrypy.server.stop()56 self.stop()57 except SystemExit:58 cherrypy.log("SystemExit raised: shutting down autoreloader", "ENGINE")59 cherrypy.server.stop()60 self.stop()61 # We must raise here: if this is a process spawned by62 # autoreload, then it must return its error code to63 # the parent.64 raise65 return66 67 self._start(blocking)68 69 def _start(self, blocking=True):70 # This is in a separate function so autoreload can call it.71 54 for func in self.on_start_engine_list: 72 55 func() … … 78 61 """Block forever (wait for stop(), KeyboardInterrupt or SystemExit).""" 79 62 try: 63 autoreload = cherrypy.config.get('autoreload.on', False) 64 if autoreload: 65 i = 0 66 freq = cherrypy.config.get('autoreload.frequency', 1) 67 80 68 while self.state != STOPPED: 81 69 time.sleep(.1) 82 if self.interrupt: 83 raise self.interrupt 70 71 # Autoreload 72 if autoreload: 73 i += .1 74 if i > freq: 75 i = 0 76 self.autoreload() 84 77 except KeyboardInterrupt: 85 78 cherrypy.log("<Ctrl-C> hit: shutting down app engine", "ENGINE") … … 93 86 except: 94 87 # Don't bother logging, since we're going to re-raise. 95 self.interrupt = sys.exc_info()[1]96 88 # Note that we don't stop the HTTP server here. 97 89 self.stop() 98 90 raise 91 92 def reexec(self): 93 """Re-execute the current process.""" 94 cherrypy.server.stop() 95 self.stop() 96 97 args = sys.argv[:] 98 cherrypy.log("Re-spawning %s" % " ".join(args), "ENGINE") 99 args.insert(0, sys.executable) 100 101 if sys.platform == "win32": 102 args = ['"%s"' % arg for arg in args] 103 os.execv(sys.executable, args) 104 105 def autoreload(self): 106 """Reload the process if registered files have been modified.""" 107 for filename in map(fileattr, sys.modules.values()) + self.reload_files: 108 if filename: 109 if filename.endswith(".pyc"): 110 filename = filename[:-1] 111 112 try: 113 mtime = os.stat(filename).st_mtime 114 except OSError: 115 if filename in self.mtimes: 116 # The file was probably deleted. 117 self.reexec() 118 119 if filename not in self.mtimes: 120 self.mtimes[filename] = mtime 121 continue 122 123 if mtime > self.mtimes[filename]: 124 # The file has been modified. 125 self.reexec() 99 126 100 127 def stop(self): … … 121 148 while not self.ready: 122 149 time.sleep(.1) 123 if self.interrupt:124 raise self.interrupt125 150 126 151 def _is_ready(self): … … 181 206 self.msg = msg 182 207 208 def close(self): 209 pass 210 183 211 def run(self, request_line, headers, rfile): 184 212 self.method = "GET" trunk/cherrypy/_cpserver.py
r1141 r1204 17 17 def start(self, server=None): 18 18 """Main function. MUST be called from the main thread.""" 19 self.interrupt = None 20 19 21 conf = cherrypy.config.get 20 22 if server is None: … … 64 66 def wait(self): 65 67 """Wait until the HTTP server is ready to receive requests.""" 66 while (not getattr(self.httpserver, "ready", True)68 while (not getattr(self.httpserver, "ready", False) 67 69 and not self.interrupt): 68 70 time.sleep(.1) 71 if self.interrupt: 72 raise self.interrupt 69 73 70 74 # Wait for port to be occupied … … 83 87 # httpstop() MUST block until the server is *truly* stopped. 84 88 httpstop() 89 conf = cherrypy.config.get 90 if conf('server.socket_port'): 91 host = conf('server.socket_host') 92 port = conf('server.socket_port') 93 wait_for_free_port(host, port) 85 94 cherrypy.log("HTTP Server shut down", "HTTP") 86 95 trunk/cherrypy/_cpwsgiserver.py
r1192 r1204 215 215 version = "CherryPy/3.0.0alpha" 216 216 ready = False 217 interrupt = None217 _interrupt = None 218 218 RequestHandlerClass = HTTPRequest 219 219 … … 315 315 self.tick() 316 316 if self.interrupt: 317 while self.interrupt is True: 318 # Wait for self.stop() to complete 319 time.sleep(0.1) 317 320 raise self.interrupt 318 321 … … 320 323 try: 321 324 s, addr = self.socket.accept() 325 if not self.ready: 326 return 322 327 if hasattr(s, 'settimeout'): 323 328 s.settimeout(self.timeout) … … 329 334 # accept() by default 330 335 return 336 except socket.error, x: 337 if x.args[1] == "Bad file descriptor": 338 # Our socket was closed 339 return 340 raise 341 342 def _get_interrupt(self): 343 return self._interrupt 344 def _set_interrupt(self, interrupt): 345 self._interrupt = True 346 self.stop() 347 self._interrupt = interrupt 348 interrupt = property(_get_interrupt, _set_interrupt) 331 349 332 350 def stop(self): 333 351 """Gracefully shutdown a server that is serving forever.""" 334 352 self.ready = False 335 s = getattr(self, "socket", None) 336 if s and hasattr(s, "close"): 337 s.close() 353 354 sock = getattr(self, "socket", None) 355 if sock: 356 if not isinstance(self.bind_addr, basestring): 357 # Ping our own socket to make accept() return immediately. 358 try: 359 host, port = sock.getsockname()[:2] 360 except socket.error, x: 361 if x.args[1] != "Bad file descriptor": 362 raise 363 else: 364 for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC, 365 socket.SOCK_STREAM): 366 af, socktype, proto, canonname, sa = res 367 s = None 368 try: 369 s = socket.socket(af, socktype, proto) 370 # See http://groups.google.com/group/cherrypy-users/ 371 # browse_frm/thread/bbfe5eb39c904fe0 372 s.settimeout(1.0) 373 s.connect((host, port)) 374 s.close() 375 except socket.error: 376 if s: 377 s.close() 378 if hasattr(sock, "close"): 379 sock.close() 380 self.socket = None 338 381 339 382 # Must shut down threads here so the code that calls … … 344 387 # Don't join currentThread (when stop is called inside a request). 345 388 current = threading.currentThread() 346 for worker in self._workerThreads: 389 while self._workerThreads: 390 worker = self._workerThreads.pop() 347 391 if worker is not current and worker.isAlive: 348 worker.join() 349 350 self._workerThreads = [] 392 try: 393 worker.join() 394 except AssertionError: 395 pass 396 trunk/cherrypy/config.py
r1201 r1204 7 7 8 8 import cherrypy 9 9 10 10 11 environments = { … … 37 38 """Merge one app config (from a dict, file, or filename) into another.""" 38 39 if isinstance(other, basestring): 39 if other not in cherrypy. lib.autoreload.reloadFiles:40 cherrypy. lib.autoreload.reloadFiles.append(other)40 if other not in cherrypy.engine.reload_files: 41 cherrypy.engine.reload_files.append(other) 41 42 other = Parser().dict_from_file(other) 42 43 elif hasattr(other, 'read'): … … 79 80 """Update globalconf from a dict, file or filename.""" 80 81 if isinstance(conf, basestring): 81 if conf not in cherrypy. lib.autoreload.reloadFiles:82 cherrypy. lib.autoreload.reloadFiles.append(conf)82 if conf not in cherrypy.engine.reload_files: 83 cherrypy.engine.reload_files.append(conf) 83 84 conf = Parser().dict_from_file(conf) 84 85 elif hasattr(conf, 'read'): trunk/cherrypy/test/test_states.py
r1183 r1204 1 import os 2 import sys 3 import threading 4 import time 5 1 6 import test 2 7 test.prefer_parent_path() 3 4 import threading5 8 6 9 import cherrypy … … 148 151 149 152 # Raise a keyboard interrupt in the HTTP server's main thread. 150 def interrupt():151 cherrypy.server.wait()152 cherrypy.server.httpserver.interrupt = KeyboardInterrupt153 threading.Thread(target=interrupt).start()154 155 153 # We must start the server in this, the main thread 154 cherrypy.engine.start(blocking=False) 156 155 cherrypy.server.start(self.server_class) 157 # Time passes... 156 cherrypy.server.httpserver.interrupt = KeyboardInterrupt 157 while cherrypy.engine.state != 0: 158 time.sleep(0.1) 159 158 160 self.assertEqual(db_connection.running, False) 159 161 self.assertEqual(len(db_connection.threads), 0) 162 self.assertEqual(cherrypy.engine.state, 0) 160 163 161 164 # Raise a keyboard interrupt in a page handler; on multithreaded … … 163 166 # This should raise a BadStatusLine error, since the worker 164 167 # thread will just die without writing a response. 165 def interrupt(): 166 cherrypy.server.wait() 167 from httplib import BadStatusLine 168 self.assertRaises(BadStatusLine, self.getPage, "/ctrlc") 169 threading.Thread(target=interrupt).start() 170 168 cherrypy.engine.start(blocking=False) 171 169 cherrypy.server.start(self.server_class) 172 # Time passes... 170 171 from httplib import BadStatusLine 172 try: 173 self.getPage("/ctrlc") 174 except BadStatusLine: 175 pass 176 else: 177 print self.body 178 self.fail("AssertionError: BadStatusLine not raised") 179 180 while cherrypy.engine.state != 0: 181 time.sleep(0.1) 173 182 self.assertEqual(db_connection.running, False) 174 183 self.assertEqual(len(db_connection.threads), 0) 184 185 def test_3_Autoreload(self): 186 if self.server_class: 187 demoscript = os.path.join(os.getcwd(), os.path.dirname(__file__), 188 "test_states_demo.py") 189 190 # Start the demo script in a new process 191 host = cherrypy.config.get("server.socket_host") 192 port = cherrypy.config.get("server.socket_port") 193 cherrypy._cpserver.wait_for_free_port(host, port) 194 os.spawnl(os.P_NOWAIT, sys.executable, sys.executable, 195 demoscript, host, str(port)) 196 cherrypy._cpserver.wait_for_occupied_port(host, port) 197 198 try: 199 self.getPage("/pid") 200 pid = self.body 201 202 # Give the autoreloader time to cache the file time. 203 time.sleep(2) 204 205 # Touch the file 206 f = open(demoscript, 'ab') 207 f.write(" ") 208 f.close() 209 210 # Give the autoreloader time to re-exec the process 211 time.sleep(2) 212 cherrypy._cpserver.wait_for_occupied_port(host, port) 213 214 self.getPage("/pid") 215 self.assertNotEqual(self.body, pid) 216 finally: 217 # Shut down the spawned process 218 self.getPage("/stop") 175 219 176 220 … … 189 233 cherrypy.engine.on_stop_thread_list.append(db_connection.stopthread) 190 234 191 helper.CPTestRunner.run(suite) 235 import pyconquer 236 tr = pyconquer.Logger("cherrypy") 237 tr.out = open(os.path.join(os.path.dirname(__file__), "state.log"), "wb") 238 try: 239 tr.start() 240 helper.CPTestRunner.run(suite) 241 finally: 242 tr.stop() 243 tr.out.close() 192 244 finally: 193 cherrypy.server.stop() 245 if cherrypy.server.httpserver.ready: 246 cherrypy.server.stop() 194 247 cherrypy.engine.stop() 195 248

