Download Install Tutorial Docs FAQ Tools WikiLicense Team IRC Planet Involvement Shop Book

root/branches/cp3-wsgi-remix/_cpengine.py

Revision 1247 (checked in by dowski, 2 years ago)

Merged _cpengine fix in [1246] into cp3-wsgi-remix branch.

  • Property svn:eol-style set to native
Line 
1 """Create and manage the CherryPy application engine."""
2
3 import cgi
4 import os
5 import signal
6 import sys
7 import threading
8 import time
9
10 import cherrypy
11 from cherrypy import _cprequest
12
13 # Use a flag to indicate the state of the application engine.
14 STOPPED = 0
15 STARTING = None
16 STARTED = 1
17
18
19 def fileattr(m):
20     if hasattr(m, "__loader__"):
21         if hasattr(m.__loader__, "archive"):
22             return m.__loader__.archive
23     return getattr(m, "__file__", None)
24
25
26 try:
27     if hasattr(signal, "SIGHUP"):
28         def SIGHUP(signum=None, frame=None):
29             cherrypy.engine.reexec()
30         signal.signal(signal.SIGHUP, SIGHUP)
31
32     if hasattr(signal, "SIGTERM"):
33         def SIGTERM(signum=None, frame=None):
34             cherrypy.server.stop()
35             cherrypy.engine.stop()
36         signal.signal(signal.SIGTERM, SIGTERM)
37 except ValueError, _signal_exc:
38     if _signal_exc.args[0] != "signal only works in main thread":
39         raise
40
41
42 class Engine(object):
43     """The application engine, which exposes a request interface to (HTTP) servers."""
44    
45     request_class = _cprequest.Request
46     response_class = _cprequest.Response
47    
48     def __init__(self):
49         self.state = STOPPED
50        
51         # Startup/shutdown hooks
52         self.on_start_engine_list = []
53         self.on_stop_engine_list = []
54         self.on_start_thread_list = []
55         self.on_stop_thread_list = []
56         self.seen_threads = {}
57        
58         self.servings = []
59        
60         self.mtimes = {}
61         self.reload_files = []
62        
63         self.monitor_thread = None
64    
65     def start(self, blocking=True):
66         """Start the application engine."""
67         self.state = STARTING
68        
69         conf = cherrypy.config.get
70        
71         # Output config options to log
72         if conf("log_config", True):
73             cherrypy.config.log_config()
74        
75         for func in self.on_start_engine_list:
76             func()
77        
78         self.state = STARTED
79        
80         freq = float(cherrypy.config.get('deadlock_poll_freq', 60))
81         if freq > 0:
82             self.monitor_thread = threading.Timer(freq, self.monitor)
83             self.monitor_thread.start()
84        
85         if blocking:
86             self.block()
87    
88     def block(self):
89         """Block forever (wait for stop(), KeyboardInterrupt or SystemExit)."""
90         try:
91             autoreload = cherrypy.config.get('autoreload.on', False)
92             if autoreload:
93                 i = 0
94                 freq = cherrypy.config.get('autoreload.frequency', 1)
95            
96             while self.state != STOPPED:
97                 time.sleep(.1)
98                
99                 # Autoreload
100                 if autoreload:
101                     i += .1
102                     if i > freq:
103                         i = 0
104                         self.autoreload()
105         except KeyboardInterrupt:
106             cherrypy.log("<Ctrl-C> hit: shutting down app engine", "ENGINE")
107             cherrypy.server.stop()
108             self.stop()
109         except SystemExit:
110             cherrypy.log("SystemExit raised: shutting down app engine", "ENGINE")
111             cherrypy.server.stop()
112             self.stop()
113             raise
114         except:
115             # Don't bother logging, since we're going to re-raise.
116             # Note that we don't stop the HTTP server here.
117             self.stop()
118             raise
119    
120     def reexec(self):
121         """Re-execute the current process."""
122         cherrypy.server.stop()
123         self.stop()
124        
125         args = sys.argv[:]
126         cherrypy.log("Re-spawning %s" % " ".join(args), "ENGINE")
127         args.insert(0, sys.executable)
128        
129         if sys.platform == "win32":
130             args = ['"%s"' % arg for arg in args]
131         os.execv(sys.executable, args)
132    
133     def autoreload(self):
134         """Reload the process if registered files have been modified."""
135         for filename in map(fileattr, sys.modules.values()) + self.reload_files:
136             if filename:
137                 if filename.endswith(".pyc"):
138                     filename = filename[:-1]
139                
140                 oldtime = self.mtimes.get(filename, 0)
141                 if oldtime is None:
142                     # Module with no .py file. Skip it.
143                     continue
144                
145                 try:
146                     mtime = os.stat(filename).st_mtime
147                 except OSError:
148                     # Either a module with no .py file, or it's been deleted.
149                     mtime = None
150                
151                 if filename not in self.mtimes:
152                     # If a module has no .py file, this will be None.
153                     self.mtimes[filename] = mtime
154                 else:
155                     if mtime is None or mtime > oldtime:
156                         # The file has been deleted or modified.
157                         self.reexec()
158    
159     def stop(self):
160         """Stop the application engine."""
161         if self.state != STOPPED:
162             for thread_ident, i in self.seen_threads.iteritems():
163                 for func in self.on_stop_thread_list:
164                     func(i)
165             self.seen_threads.clear()
166            
167             for func in self.on_stop_engine_list:
168                 func()
169            
170             if self.monitor_thread:
171                 self.monitor_thread.cancel()
172                 self.monitor_thread = None
173            
174             self.state = STOPPED
175             cherrypy.log("CherryPy shut down", "ENGINE")
176    
177     def restart(self):
178         """Restart the application engine (doesn't block)."""
179         self.stop()
180         self.start(blocking=False)
181    
182     def wait(self):
183         """Block the caller until ready to receive requests (or error)."""
184         while not self.ready:
185             time.sleep(.1)
186    
187     def _is_ready(self):
188         return bool(self.state == STARTED)
189     ready = property(_is_ready, doc="Return True if the engine is ready to"
190                                     " receive requests, False otherwise.")
191    
192     def request(self, local_host, remote_host, scheme="http"):
193         """Obtain an HTTP Request object.
194         
195         local_host should be an http.Host object with the server info.
196         remote_host should be an http.Host object with the client info.
197         scheme: either "http" or "https"; defaults to "http"
198         """
199         if self.state == STOPPED:
200             req = NotReadyRequest("The CherryPy engine has stopped.")
201         elif self.state == STARTING:
202             req = NotReadyRequest("The CherryPy engine could not start.")
203         else:
204             # Only run on_start_thread_list if the engine is running.
205             threadID = threading._get_ident()
206             if threadID not in self.seen_threads:
207                 i = len(self.seen_threads) + 1
208                 self.seen_threads[threadID] = i
209                
210                 for func in self.on_start_thread_list:
211                     func(i)
212             req = self.request_class(local_host, remote_host, scheme)
213         cherrypy.serving.request = req
214         cherrypy.serving.response = resp = self.response_class()
215         self.servings.append((req, resp))
216         return req
217    
218     def monitor(self):
219         """Check timeout on all responses."""
220         if self.state == STARTED:
221             for req, resp in self.servings:
222                 resp.check_timeout()
223             freq = float(cherrypy.config.get('deadlock_poll_freq', 60))
224             self.monitor_thread = threading.Timer(freq, self.monitor)
225             self.monitor_thread.start()
226    
227     def start_with_callback(self, func, args=None, kwargs=None):
228         """Start, then callback the given func in a new thread."""
229        
230         if args is None:
231             args = ()
232         if kwargs is None:
233             kwargs = {}
234         args = (func,) + args
235        
236         def _callback(func, *a, **kw):
237             self.wait()
238             func(*a, **kw)
239         t = threading.Thread(target=_callback, args=args, kwargs=kwargs)
240         t.setName("CPEngine Callback " + t.getName())
241         t.start()
242        
243         self.start()
244
245
246 class NotReadyRequest:
247    
248     def __init__(self, msg):
249         self.msg = msg
250         self.protocol = (1,1)
251    
252     def close(self):
253         pass
254    
255     def run(self, method, path, query_string, protocol, headers, rfile):
256         self.method = "GET"
257         cherrypy.HTTPError(503, self.msg).set_response()
258         cherrypy.response.finalize()
259         return cherrypy.response
260
261
262 def drop_privileges(new_user='nobody', new_group='nogroup'):
263     """Drop privileges. UNIX only."""
264     # Special thanks to Gavin Baker: http://antonym.org/node/100.
265    
266     import pwd, grp
267    
268     def names():
269         return pwd.getpwuid(os.getuid())[0], grp.getgrgid(os.getgid())[0]
270     name, group = names()
271     cherrypy.log('Started as %r/%r' % (name, group), "PRIV")
272    
273     if os.getuid() != 0:
274         # We're not root so, like, whatever dude.
275         cherrypy.log("Already running as %r" % name, "PRIV")
276         return
277    
278     # Try setting the new uid/gid (from new_user/new_group).
279     try:
280         os.setgid(grp.getgrnam(new_group)[2])
281     except OSError, e:
282         cherrypy.log('Could not set effective group id: %r' % e, "PRIV")
283    
284     try:
285         os.setuid(pwd.getpwnam(new_user)[2])
286     except OSError, e:
287         cherrypy.log('Could not set effective user id: %r' % e, "PRIV")
288    
289     # Ensure a very convervative umask
290     old_umask = os.umask(077)
291     cherrypy.log('Old umask: %o, new umask: 077' % old_umask, "PRIV")
292     cherrypy.log('Running as %r/%r' % names(), "PRIV")
293
Note: See TracBrowser for help on using the browser.

Hosted by WebFaction

Log in as guest/cpguest to create tickets