Several users on GitHub reported issues with running Python ASGI apps on Unit with Python 3.11.1 (this would also effect Python 3.10.9) with the following error from Unit 2023/01/15 22:43:22 [alert] 0#77128 [unit] Python failed to call 'asyncio.get_event_loop' TL;DR asyncio.get_event_loop() is currently broken due to the process of deprecating part or all of it. First some history. In Unit we had this commit commit8dcb0b9987Author: Max Romanov <max.romanov@nginx.com> Date: Thu Nov 5 00:04:59 2020 +0300 Python: request processing in multiple threads. One of things this did was to create a new asyncio event loop in each thread using asyncio.new_event_loop(). It's perhaps worth noting that all these asyncio.* functions are Python functions that we call from the C code in Unit. Then we had this commit commitf27fbd9b4dAuthor: Max Romanov <max.romanov@nginx.com> Date: Tue Jul 20 10:37:54 2021 +0300 Python: using default event_loop for main thread for ASGI. This changed things so that Unit calls asyncio.get_event_loop() in the _main_ thread (but still calls asyncio.new_event_loop() in the other threads). asyncio.get_event_loop() up until recently would either return an already running event loop or return a newly created one. This was done for $reasons that the commit message and GitHub issue #560 hint at. But the intimation is that there can already be an event loop running from the application (I assume it's referring to the users application) at this point and if there is we should use it. Now for the Python side of things. On the main branch we had commit 172c0f2752d8708b6dda7b42e6c5a3519420a4e8 Author: Serhiy Storchaka <storchaka@gmail.com> Date: Sun Apr 25 13:40:44 2021 +0300 bpo-39529: Deprecate creating new event loop in asyncio.get_event_loop() (GH-23554) This commit began the deprecating of asyncio.get_event_loop(). commit fd38a2f0ec03b4eec5e3cfd41241d198b1ee555a Author: Serhiy Storchaka <storchaka@gmail.com> Date: Tue Dec 6 19:42:12 2022 +0200 gh-93453: No longer create an event loop in get_event_loop() (#98440) This turned asyncio.get_event_loop() into a RuntimeError _if_ there isn't a current event loop. commit e5bd5ad70d9e549eeb80aadb4f3ccb0f2f23266d Author: Serhiy Storchaka <storchaka@gmail.com> Date: Fri Jan 13 14:40:29 2023 +0200 gh-100160: Restore and deprecate implicit creation of an event loop (GH-100410) This re-creates the event loop if there wasn't one and emits a deprecation warning. After at least the last two commits Unit no longer works with the Python _main_ branch. Meanwhile on the 3.11 branch we had commit 3fae04b10e2655a20a3aadb5e0d63e87206d0c67 Author: Serhiy Storchaka <storchaka@gmail.com> Date: Tue Dec 6 17:15:44 2022 +0200 [3.11] gh-93453: Only emit deprecation warning in asyncio.get_event_loop when a new event loop is created (#99949) which is what caused our breakage, though perhaps unintentionally as we get the following traceback Traceback (most recent call last): File "/usr/lib64/python3.11/asyncio/events.py", line 676, in get_event_loop f = sys._getframe(1) ^^^^^^^^^^^^^^^^ ValueError: call stack is not deep enough 2023/01/18 02:46:10 [alert] 0#180279 [unit] Python failed to call 'asyncio.get_event_loop' However, regardless, it is clear we need to stop using asyncio.get_event_loop(). One option is to switch to the higher level asyncio.run() API, however that is a rather large change. This commit takes the simpler approach of using asyncio.get_running_loop() (which it seems get_event_loop() will eventually be an alias of) in the _main_ thread to return the currently running event loop, or if there is no current event loop, it will call asyncio.new_event_loop() to return a newly created event loop. I believe this mimics the current behaviour. In my testing get_event_loop() seemed to always return a newly created loop, as when just calling get_running_loop() it would return NULL and we would fail out. When running two processes each with 2 threads we would get the following loops with Python 3.11.0 and unpatched Unit <_UnixSelectorEventLoop running=False closed=False debug=False> <_UnixSelectorEventLoop running=False closed=False debug=False> <_UnixSelectorEventLoop running=False closed=False debug=False> <_UnixSelectorEventLoop running=False closed=False debug=False> and with Python 3.11.1 and a patched Unit we would get <_UnixSelectorEventLoop running=False closed=False debug=False> <_UnixSelectorEventLoop running=False closed=False debug=False> <_UnixSelectorEventLoop running=False closed=False debug=False> <_UnixSelectorEventLoop running=False closed=False debug=False> Tested-by: Rafał Safin <rafal.safin12@gmail.com> Reviewed-by: Alejandro Colomar <alx@nginx.com> Signed-off-by: Andrew Clayton <a.clayton@nginx.com>
NGINX Unit
Universal Web App Server
NGINX Unit is a lightweight and versatile open-source server that has three core capabilities:
- it is an HTTP reverse proxy,
- a web server for static media assets,
- and an application server that runs code in seven languages.
We are building a universal tool that compresses several layers of the modern application stack into a potent, coherent solution with a focus on performance, low latency, and scalability. It is intended as a building block for any web architecture regardless of its complexity, from enterprise-scale deployments to your pet's homepage.
Unit's native RESTful JSON API enables dynamic updates with zero interruptions and flexible configuration, while its out-of-the-box productivity reliably scales to production-grade workloads. We achieve that with a complex, asynchronous, multithreading architecture comprising multiple processes to ensure security and robustness while getting the most out of today's computing platforms.
Quick Installation
macOS
$ brew install nginx/unit/unit
For details and available language packages, see the docs.
Docker
$ docker pull docker.io/nginx/unit
For a description of image tags, see the docs.
Amazon Linux, Fedora, RedHat
$ wget https://raw.githubusercontent.com/nginx/unit/master/tools/setup-unit && chmod +x setup-unit
# ./setup-unit repo-config && yum install unit
# ./setup-unit welcome
For details and available language packages, see the docs.
Debian, Ubuntu
$ wget https://raw.githubusercontent.com/nginx/unit/master/tools/setup-unit && chmod +x setup-unit
# ./setup-unit repo-config && apt install unit
# ./setup-unit welcome
For details and available language packages, see the docs.
Running a Hello World App
Suppose you saved a PHP script as /www/helloworld/index.php:
<?php echo "Hello, PHP on Unit!"; ?>
To run it on Unit with the unit-php module installed, first set up an
application object. Let's store our first config snippet in a file called
config.json:
{
"helloworld": {
"type": "php",
"root": "/www/helloworld/"
}
}
Saving it as a file isn't necessary, but can come in handy with larger objects.
Now, PUT it into the /config/applications section of Unit's control API,
usually available by default via a Unix domain socket:
# curl -X PUT --data-binary @config.json --unix-socket \
/path/to/control.unit.sock http://localhost/config/applications
{
"success": "Reconfiguration done."
}
Next, reference the app from a listener object in the /config/listeners
section of the API. This time, we pass the config snippet straight from the
command line:
# curl -X PUT -d '{"127.0.0.1:8000": {"pass": "applications/helloworld"}}' \
--unix-socket /path/to/control.unit.sock http://localhost/config/listeners
{
"success": "Reconfiguration done."
}
Now Unit accepts requests at the specified IP and port, passing them to the application process. Your app works!
$ curl 127.0.0.1:8080
Hello, PHP on Unit!
Finally, query the entire /config section of the control API:
# curl --unix-socket /path/to/control.unit.sock http://localhost/config/
Unit's output should contain both snippets, neatly organized:
{
"listeners": {
"127.0.0.1:8080": {
"pass": "applications/helloworld"
}
},
"applications": {
"helloworld": {
"type": "php",
"root": "/www/helloworld/"
}
}
}
For full details of configuration management, see the docs.
Community
-
The go-to place to start asking questions and share your thoughts is our Slack channel.
-
Our GitHub issues page offers space for a more technical discussion at your own pace.
-
The project map on GitHub sheds some light on our current work and plans for the future.
-
Our official website may provide answers not easily found otherwise.
-
Get involved with the project by contributing! See the contributing guide for details.
-
To reach the team directly, subscribe to the mailing list.
-
For security issues, email us, mentioning NGINX Unit in the subject and following the CVSS v3.1 spec.