--- myst: html_meta: "description": "" "property=og:description": "" "property=og:title": "" "keywords": "" --- # Gunicorn [Gunicorn](https://gunicorn.org/) is another widely used WSGI server. ## Possible Worker models Gunicorn offers a wide range of possible worker models. The default is the `sync` worker model which uses "traditional" multi-threading based on the standard library. Unlike waitress Gunicorn doesn't use an `asyncore` dispatcher to clear the request queue. Each worker is using [select](https://github.com/benoitc/gunicorn/blob/e147feaf8b12267ff9bb3c06ad45a2738a4027df/gunicorn/workers/sync.py#L34) by itself to check for incoming client requests. The official [Gunicorn documentation for worker types](https://docs.gunicorn.org/en/latest/design.html#choosing-a-worker-type) recommends to put a buffering proxy in front of a default configuration Gunicorn. Other possible worker types are asynchronous workers based on [greenlets](https://greenlet.readthedocs.io/en/latest/), [AsyncIO](https://docs.python.org/3/library/asyncio.html#module-asyncio) workers and a [Tornado](https://www.tornadoweb.org/en/stable/) worker class. The different worker types and how to choose one suitable for your application is covered in detail in the [Gunicorn docs](https://docs.gunicorn.org/en/latest/design.html). ## Use gunicorn in our buildout ````{sidebar} Build now Run buildout for this section: ```shell buildout -c gunicorn.cfg ``` ```` Gunicorn has a built-in PasteDeploy entry point, so we don't need a shim package like the one we used for `bjoern`. On the downside, there is no easy way of passing `plone.recipe.zope2instances http-address` parameter to gunicorn since the `bind` directive doesn't seem to work in the `ini` file. The PasteDeploy entry point is covered in the [gunicorn configuration documentation](https://docs.gunicorn.org/en/stable/configure.html). We resolve to hard code the socket in the `ini` template. From `templates/gunicorn.ini.in`: ```ini [server:main] use = egg:gunicorn#main host = 0.0.0.0 port = 8080 proc_name = plone [app:zope] use = egg:Zope#main ... ``` We use this template in our buildout and add `gunicorn` to our list of eggs: ```ini [instance] recipe = plone.recipe.zope2instance user = admin:admin zeo-client = on zeo-address = 8100 shared-blob = on blob-storage = ${buildout:directory}/var/blobstorage eggs = Plone wsgitraining.site gunicorn wsgi-ini-template = ${buildout:directory}/templates/gunicorn.ini.in ``` ## Alternative method for using gunicorn ````{sidebar} Build now Run buildout for this section: ```shell buildout -c gunicorn-alt.cfg ``` ```` An alternative method for using gunicorn with Plone is taken from the [Plone Core Development Buildout](https://github.com/plone/buildout.coredev) bypasses `plone.recipe.zope2instances` wsgi-ini-template option and builds three more parts instead. These parts are working together to create the gunicorn configuration and startup scripts. We do not use an `ini` template in this case but rather use inline templates to render the gunicorn command line and the WSGI application entry point in two scripts: ```ini [buildout] extends = base.cfg parts += gunicorn gunicornapp gunicorn-instance [instance] recipe = plone.recipe.zope2instance user = admin:admin zeo-client = on zeo-address = 8100 shared-blob = on blob-storage = ${buildout:directory}/var/blobstorage eggs = Plone wsgitraining.site [gunicornapp] recipe = collective.recipe.template input = inline: from Zope2.Startup.run import make_wsgi_app wsgiapp = make_wsgi_app({}, '${buildout:parts-directory}/instance/etc/zope.conf') def application(*args, **kwargs):return wsgiapp(*args, **kwargs) output = ${buildout:bin-directory}/gunicornapp.py [gunicorn] recipe = zc.recipe.egg eggs = gunicorn ${instance:eggs} scripts = gunicorn [gunicorn-instance] recipe = collective.recipe.template input = inline: #!/bin/sh ${buildout:directory}/bin/gunicorn -b localhost:8080 --threads 4 gunicornapp:application output = ${buildout:bin-directory}/gunicorn-instance mode = 755 ``` Note that in this case we still create the default instance (using waitress). But for starting up Plone with gunicorn we use the new `gunicorn-instance` script instead, without any parameters: ```shell (wsgitraining) $ bin/gunicorn-instance [2019-10-01 11:55:41 +0200] [11048] [INFO] Starting gunicorn 19.9.0 [2019-10-01 11:55:41 +0200] [11048] [INFO] Listening at: http://127.0.0.1:8080 (11048) [2019-10-01 11:55:41 +0200] [11048] [INFO] Using worker: threads [2019-10-01 11:55:41 +0200] [11051] [INFO] Booting worker with pid: 11051 ``` As a side effect we get rid of the deprecation warning for not starting gunicorn with `--paste`. ```{note} The Zope documentations reports several performance issues with gunicorn, s. for details. ``` ### Exercise 1 Modify `gunicorn-alt.cfg` so it uses the `eventlet` worker class. Check the number of database connections in the ZMI. What do you notice? ````{dropdown} Solution :animate: fade-in-slide-down :icon: question You need to add `eventlet` to the list of eggs of the `[gunicorn]` part and modify the command line for `[gunicorn-instance]` ```{code-block} ini :emphasize-lines: 6,15 ... [gunicorn] recipe = zc.recipe.egg eggs = gunicorn eventlet ${instance:eggs} scripts = gunicorn [gunicorn-instance] recipe = collective.recipe.template input = inline: #!/bin/sh ${buildout:directory}/bin/gunicorn -b localhost:8080 --workers 4 gunicornapp:application --worker-class eventlet output = ${buildout:bin-directory}/gunicorn-instance mode = 755 ... ``` After running `buildout -c gunicorn-alt.cfg`, you can start the instance with `gunicorn-instance`. Open the [database controlpanel](http://localhost:8080/Control_Panel/Database/main/manage_main) in a browser to check the number of database connection. You will see only one connection despite the 4 workers. ZODB connections are [not thread safe](https://zodb.org/en/latest/guide/transactions-and-threading.html#concurrency-threads-and-processes) so this is not a recommended configuration. The [asyncio](https://docs.python.org/3/library/asyncio.html#module-asyncio) based `gthread` worker class (doesn't need additional packages) will show one database connection per worker. ````