Running Gunicorn webserver from Python with state preserving object
This article demonstrates how to configure and run Gunicorn directly from Python 3 code without using configuration files or invoking
gunicorn from the command line.
It also demonstrates how to setup a stateful backend with Gunicorn where a single Python object is kept alive between requests such that it can manage the state of a backend directly in Python code. Graceful start up and shut down of the backend may be done in callbacks registered with Gunicorn.
Note that running a webserver with just one worker isn't generally recommended as it will not allow scaling to higher workloads. However, it may be a convenient and simple solution for cases where the server will not face high demand or where the server implements custom functionality that's expensive to initialize.
The Notes'n'Todos application uses this approach.
Processes and threads
Gunicorn is configured to run only one worker process, but to use multiple threads (although they will not be real individual threads). Having multiple threads is necessary for Gunicorn to provide persistent HTTP connections, which is important when multiple requests are made in sequence.
With multiple threads a lock or some other mechanism must be used to synchronize with single threaded backend logic.
Worker preforking and the need for create and exit callbacks
If the backend needs to do any processing at start up or shutdown, it must be done in callbacks registered with the custom Gunicorn application. It can not simply be done before and after invoking the Gunicorn app
.run() for some interesting reasons:
First, realize that Gunicorn uses a prefork process creation model. It means that the state when Gunicorn was first started will be cloned whenever a worker is created. Now, also realize that Gunicorn WILL eventually restart the worker process, even if it was configured to live forever. The result may be a server that mysteriously resets to its original state every couple of days, if initialization is not done in a create callback. As you may have realized by now, I learned this the hard way...
The bottom line is that it's important that Gunicorn can restart the worker process correctly. In the example below the restart can be tested by uncommenting
self.cfg.set("max_requests", 10) which will force a restart of the worker after 10 requests.
The script serves a demo page with a button that shows a value increasing for each click. The backend persists the value through restarts by saving and loading to a file.
Run the script with
python (Python 3), with
bottle pip installed. Don't run it with
gunicorn from the commandline.
Comments powered by Talkyard