How Gunicorn works

Gunicorn “Green Unicorn” is a high performance Python WSGI UNIX HTTP server, ported from Ruby Unicorn project, using pre-fork worker mode, with very simple to use, lightweight resource consumption. And high performance and other characteristics.

As a container for WSGi apps, the Gunicorn server is compatible with various Web frameworks (Flask, Django, etc.). Thanks to gEvent and other technologies, using Gunicorn can significantly improve wsGi app performance without changing much of the WSGi app code.

The overall structure

The Gunicorn pre-fork worker model has one management process and several worker processes. Management process :master; worker process :worker. (In the following codes, some interfering codes have been removed for aspects understanding)

Master creates multiple workers through pre-fork:

def spawn_worker(self):
    self.worker_age += 1
    # create worker. Please note that the app object here is not a real WSGi app object, but the Gunicorn app
    # object. Gunicorn's app object is responsible for importing wsGi app objects we wrote ourselves.
    worker = self.worker_class(self.worker_age, self.pid, self.LISTENERS,
                                self.app, self.timeout / 2.0.self.cfg, self.log) 
    pid = os.fork()
    ifpid ! =0:  # parent process, after returning to continue to create other workers, after no worker into its own message loop
        self.WORKERS[pid] = worker
        return pid
 
    # Process Child
    worker_pid = os.getpid()
    try:. worker.init_process()# child process, initialize woker, enter worker's message loop,
        sys.exit(0)
    except SystemExit:
        raise
Copy the code

In worker.init_process(), the gunicorn app object in the worker will import our WSGi app. That is, each Woker child process individually instantiates our WSGi App object. Swgi APP objects in each worker are independent and do not interfere with each other.

Manager maintains a fixed number of workers:

def manage_workers(self):
        if len(self.WORKERS.keys()) < self.num_workers:
            self.spawn_workers()
        while len(workers) > self.num_workers:
            (pid, _) = workers.pop(0)
            self.kill_worker(pid, signal.SIGQUIT)
Copy the code

After all workers are created, the worker and the master enter their own message cycles. The event loop of master is to receive signals and manage worker processes, while the event loop of worker processes is to monitor network events and process them (such as creating connections, disconnecting connections, processing requests and sending responses, etc.), so the real connection is finally connected to worker processes. (note: this process model in detail, more can reference blog.csdn.net/largetalk/a…

worker

There are many kinds of Wokers, including GGEvent, Geventlet, GTornado and so on. This analysis focuses on GGEvent.

Each ggEvent worker starts with multiple server objects: The worker first creates a server object for each listener. Why a set of Listeners, because Gunicorn can bind a set of addresses, each address for a listener), each server object has to run in a separate GEvent Pool object. The actual waiting for and processing of links takes place in the Server object.

Create a server object for each listener.for s in self.sockets:
        pool = Pool(self.worker_connections) # create gevent poolif selfServer_class is not None: # create server object server =self.server_class(  
                s, application=self.wsgi, spawn=pool, log=self.log,
                handler_class=self.wsgi_handler, **ssl_args) ............. Server.start () # start server, start server, servers.append()Copy the code

Server_class in the above code is actually a subclass of GEvent WSGI SERVER:

class PyWSGIServer(pywsgi.WSGIServer) :
    base_env = BASE_WSGI_ENV
Copy the code

Note the arguments used to construct PyWSGIServer:

self.server_class(
                s, application=self.wsgi, spawn=pool, log=self.log,
                handler_class=self.wsgi_handler, **ssl_args)
Copy the code

Of these parameters, S is the socket that the server listens for links. Spawn is the cooutine pool of gEvents. Application is our WSGi app (colloquially, you write it in flask or Django), which is how gunicorn’s Woker runs it. Handler_class is a PywsGi. WSGIHandler subclass of GEvent.

When all server objects are created, the worker needs to notify the manager periodically, otherwise it will be considered to have hung.

while self.alive:
            self.notify()
            .......
Copy the code

The notify mechanism of this place is designed in an interesting way. Each worker has a TMP file corresponding to it. When notify, the TMP file is operated each time (such as through os.fchmod), and the timestamp of the TMP file’s last update will be updated. The manager checks the last update timestamp of the temp file corresponding to each worker to determine whether the process is suspended.

WSGI SERVER

The actual waiting for and processing of links takes place in gEvent’s WSGIServer and WSGIHandler. Finally, let’s look at the main implementations of gEvent’s WSGIServer and WSGIHandler:

Start_bonuses is called within WSGIServer’s start function to handle incoming links. Do_handle is called after receiving a socket in start_attach for processing the socket:

def do_handle(self, *args):
    spawn = self._spawn
    spawn(self._handle, *args)
Copy the code

As you can see, WSGIServer is actually creating a coroutine to handle the socket, which means that in WSGIServer, a coroutine is solely responsible for an HTTP link. The self._handle function running in the coroutine actually calls WSGIHandler’s handle function to continuously process HTTP requests:

def handle(self) :
    try:
        while self.socket is not None:
            result = self.handle_one_request()# Handle HTTP requests
            if result is None:
                break
            if result is True:
                continue
            self.status, response_body = result
            self.socket.sendall(response_body)# Send a response packet.Copy the code

Inside the handle loop, the handle_one_request function first reads the HTTP request, initializes the WSGI environment, and finally calls the run_application function to handle the request:

def run_application(self):
    self.result = self.application(self.environ, self.start_response)
    self.process_result()
Copy the code

This is where we actually called our app.

Summary: Gunicorn will start a group of worker processes, all worker processes share a group of listeners, and establish a WSGi Server for each listener in each worker. Each time an HTTP link arrives, WSGI Server creates a coroutine to handle the link. When the coroutine handles the link, it initializes the WSGi environment and then calls the user-provided APP object to handle the HTTP request.