You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							246 lines
						
					
					
						
							7.2 KiB
						
					
					
				
			
		
		
	
	
							246 lines
						
					
					
						
							7.2 KiB
						
					
					
				| # -*- coding: utf-8 -
 | |
| #
 | |
| # This file is part of gunicorn released under the MIT license.
 | |
| # See the NOTICE for more information.
 | |
| 
 | |
| import errno
 | |
| import os
 | |
| import sys
 | |
| from datetime import datetime
 | |
| from functools import partial
 | |
| import time
 | |
| 
 | |
| _socket = __import__("socket")
 | |
| 
 | |
| # workaround on osx, disable kqueue
 | |
| if sys.platform == "darwin":
 | |
|     os.environ['EVENT_NOKQUEUE'] = "1"
 | |
| 
 | |
| try:
 | |
|     import gevent
 | |
| except ImportError:
 | |
|     raise RuntimeError("You need gevent installed to use this worker.")
 | |
| from gevent.pool import Pool
 | |
| from gevent.server import StreamServer
 | |
| from gevent.socket import wait_write, socket
 | |
| from gevent import pywsgi
 | |
| 
 | |
| import gunicorn
 | |
| from gunicorn.http.wsgi import base_environ
 | |
| from gunicorn.workers.async import AsyncWorker
 | |
| from gunicorn.http.wsgi import sendfile as o_sendfile
 | |
| 
 | |
| VERSION = "gevent/%s gunicorn/%s" % (gevent.__version__, gunicorn.__version__)
 | |
| 
 | |
| def _gevent_sendfile(fdout, fdin, offset, nbytes):
 | |
|     while True:
 | |
|         try:
 | |
|             return o_sendfile(fdout, fdin, offset, nbytes)
 | |
|         except OSError as e:
 | |
|             if e.args[0] == errno.EAGAIN:
 | |
|                 wait_write(fdout)
 | |
|             else:
 | |
|                 raise
 | |
| 
 | |
| def patch_sendfile():
 | |
|     from gunicorn.http import wsgi
 | |
| 
 | |
|     if o_sendfile is not None:
 | |
|         setattr(wsgi, "sendfile", _gevent_sendfile)
 | |
| 
 | |
| 
 | |
| class GeventWorker(AsyncWorker):
 | |
| 
 | |
|     server_class = None
 | |
|     wsgi_handler = None
 | |
| 
 | |
|     def patch(self):
 | |
|         from gevent import monkey
 | |
|         monkey.noisy = False
 | |
| 
 | |
|         # if the new version is used make sure to patch subprocess
 | |
|         if gevent.version_info[0] == 0:
 | |
|             monkey.patch_all()
 | |
|         else:
 | |
|             monkey.patch_all(subprocess=True)
 | |
| 
 | |
|         # monkey patch sendfile to make it none blocking
 | |
|         patch_sendfile()
 | |
| 
 | |
|         # patch sockets
 | |
|         sockets = []
 | |
|         for s in self.sockets:
 | |
|             if sys.version_info[0] == 3:
 | |
|                 sockets.append(socket(s.FAMILY, _socket.SOCK_STREAM,
 | |
|                     fileno=s.sock.fileno()))
 | |
|             else:
 | |
|                 sockets.append(socket(s.FAMILY, _socket.SOCK_STREAM,
 | |
|                     _sock=s))
 | |
|         self.sockets = sockets
 | |
| 
 | |
|     def notify(self):
 | |
|         super(GeventWorker, self).notify()
 | |
|         if self.ppid != os.getppid():
 | |
|             self.log.info("Parent changed, shutting down: %s", self)
 | |
|             sys.exit(0)
 | |
| 
 | |
|     def timeout_ctx(self):
 | |
|         return gevent.Timeout(self.cfg.keepalive, False)
 | |
| 
 | |
|     def run(self):
 | |
|         servers = []
 | |
|         ssl_args = {}
 | |
| 
 | |
|         if self.cfg.is_ssl:
 | |
|             ssl_args = dict(server_side=True, **self.cfg.ssl_options)
 | |
| 
 | |
|         for s in self.sockets:
 | |
|             s.setblocking(1)
 | |
|             pool = Pool(self.worker_connections)
 | |
|             if self.server_class is not None:
 | |
|                 environ = base_environ(self.cfg)
 | |
|                 environ.update({
 | |
|                     "wsgi.multithread": True,
 | |
|                     "SERVER_SOFTWARE": VERSION,
 | |
|                 })
 | |
|                 server = self.server_class(
 | |
|                     s, application=self.wsgi, spawn=pool, log=self.log,
 | |
|                     handler_class=self.wsgi_handler, environ=environ,
 | |
|                     **ssl_args)
 | |
|             else:
 | |
|                 hfun = partial(self.handle, s)
 | |
|                 server = StreamServer(s, handle=hfun, spawn=pool, **ssl_args)
 | |
| 
 | |
|             server.start()
 | |
|             servers.append(server)
 | |
| 
 | |
|         while self.alive:
 | |
|             self.notify()
 | |
|             gevent.sleep(1.0)
 | |
| 
 | |
|         try:
 | |
|             # Stop accepting requests
 | |
|             for server in servers:
 | |
|                 if hasattr(server, 'close'):  # gevent 1.0
 | |
|                     server.close()
 | |
|                 if hasattr(server, 'kill'):  # gevent < 1.0
 | |
|                     server.kill()
 | |
| 
 | |
|             # Handle current requests until graceful_timeout
 | |
|             ts = time.time()
 | |
|             while time.time() - ts <= self.cfg.graceful_timeout:
 | |
|                 accepting = 0
 | |
|                 for server in servers:
 | |
|                     if server.pool.free_count() != server.pool.size:
 | |
|                         accepting += 1
 | |
| 
 | |
|                 # if no server is accepting a connection, we can exit
 | |
|                 if not accepting:
 | |
|                     return
 | |
| 
 | |
|                 self.notify()
 | |
|                 gevent.sleep(1.0)
 | |
| 
 | |
|             # Force kill all active the handlers
 | |
|             self.log.warning("Worker graceful timeout (pid:%s)" % self.pid)
 | |
|             for server in servers:
 | |
|                 server.stop(timeout=1)
 | |
|         except:
 | |
|             pass
 | |
| 
 | |
|     def handle(self, listener, client, addr):
 | |
|         # Connected socket timeout defaults to socket.getdefaulttimeout().
 | |
|         # This forces to blocking mode.
 | |
|         client.setblocking(1)
 | |
|         super(GeventWorker, self).handle(listener, client, addr)
 | |
| 
 | |
|     def handle_request(self, listener_name, req, sock, addr):
 | |
|         try:
 | |
|             super(GeventWorker, self).handle_request(listener_name, req, sock,
 | |
|                                                      addr)
 | |
|         except gevent.GreenletExit:
 | |
|             pass
 | |
|         except SystemExit:
 | |
|             pass
 | |
| 
 | |
|     def handle_quit(self, sig, frame):
 | |
|         # Move this out of the signal handler so we can use
 | |
|         # blocking calls. See #1126
 | |
|         gevent.spawn(super(GeventWorker, self).handle_quit, sig, frame)
 | |
| 
 | |
|     def handle_usr1(self, sig, frame):
 | |
|         # Make the gevent workers handle the usr1 signal
 | |
|         # by deferring to a new greenlet. See #1645
 | |
|         gevent.spawn(super(GeventWorker, self).handle_usr1, sig, frame)
 | |
| 
 | |
|     if gevent.version_info[0] == 0:
 | |
| 
 | |
|         def init_process(self):
 | |
|             # monkey patch here
 | |
|             self.patch()
 | |
| 
 | |
|             # reinit the hub
 | |
|             import gevent.core
 | |
|             gevent.core.reinit()
 | |
| 
 | |
|             #gevent 0.13 and older doesn't reinitialize dns for us after forking
 | |
|             #here's the workaround
 | |
|             gevent.core.dns_shutdown(fail_requests=1)
 | |
|             gevent.core.dns_init()
 | |
|             super(GeventWorker, self).init_process()
 | |
| 
 | |
|     else:
 | |
| 
 | |
|         def init_process(self):
 | |
|             # monkey patch here
 | |
|             self.patch()
 | |
| 
 | |
|             # reinit the hub
 | |
|             from gevent import hub
 | |
|             hub.reinit()
 | |
| 
 | |
|             # then initialize the process
 | |
|             super(GeventWorker, self).init_process()
 | |
| 
 | |
| 
 | |
| class GeventResponse(object):
 | |
| 
 | |
|     status = None
 | |
|     headers = None
 | |
|     sent = None
 | |
| 
 | |
|     def __init__(self, status, headers, clength):
 | |
|         self.status = status
 | |
|         self.headers = headers
 | |
|         self.sent = clength
 | |
| 
 | |
| 
 | |
| class PyWSGIHandler(pywsgi.WSGIHandler):
 | |
| 
 | |
|     def log_request(self):
 | |
|         start = datetime.fromtimestamp(self.time_start)
 | |
|         finish = datetime.fromtimestamp(self.time_finish)
 | |
|         response_time = finish - start
 | |
|         resp_headers = getattr(self, 'response_headers', {})
 | |
|         resp = GeventResponse(self.status, resp_headers, self.response_length)
 | |
|         if hasattr(self, 'headers'):
 | |
|             req_headers = self.headers.items()
 | |
|         else:
 | |
|             req_headers = []
 | |
|         self.server.log.access(resp, req_headers, self.environ, response_time)
 | |
| 
 | |
|     def get_environ(self):
 | |
|         env = super(PyWSGIHandler, self).get_environ()
 | |
|         env['gunicorn.sock'] = self.socket
 | |
|         env['RAW_URI'] = self.path
 | |
|         return env
 | |
| 
 | |
| 
 | |
| class PyWSGIServer(pywsgi.WSGIServer):
 | |
|     pass
 | |
| 
 | |
| 
 | |
| class GeventPyWSGIWorker(GeventWorker):
 | |
|     "The Gevent StreamServer based workers."
 | |
|     server_class = PyWSGIServer
 | |
|     wsgi_handler = PyWSGIHandler
 | |
| 
 |