Understanding Eventlet
Eventlet is a Python concurrent network programming library created nearly 18 years ago. Its intent is to propose asynchronous feature at destination of the Python ecosystem. Eventlet has been created at a time where the Python standard library was not designed to support async. Since then, a lot of water has flowed under the bridge, and AsyncIO emerged from async waves.
Table of Contents
Common Usages of Eventlet 🔗
Here is a list of the common Eventlet usages:
WSGI Server
Eventlet can be used to create a WSGI server, allowing Python web applications to be served asynchronously. This is particularly useful for handling a large number of simultaneous connections with low latency.
Asynchronous Network Calls
Eventlet enables asynchronous network calls, such as HTTP requests to REST APIs. This prevents the main program execution from being blocked while waiting for network responses.
Background Execution of Long Tasks
You can use Eventlet to run long tasks in the background without blocking the main thread. This is useful for operations like processing large files or performing intensive calculations.
Deferred Task Management
Eventlet allows the creation of deferred tasks that can be executed later or in response to certain events. This is useful for managing complex workflows where some tasks depend on the completion of others.
Green Thread Management
Eventlet uses green threads (or coroutines) to manage concurrency. This allows switching between different tasks without the need to create new system threads, which is more resource-efficient.
Socket Compatibility
Eventlet provides an API compatible with Python's standard sockets, making it easier to migrate existing applications to an asynchronous model.
WebSocket Support
Eventlet can be used to manage WebSocket connections, enabling real-time, bidirectional communication between the server and the client.
Integration with Other Libraries
Eventlet can be integrated with other Python libraries to provide asynchronous functionality, such as databases or queue systems.
The promises of Eventlet 🔗
Magic Asynchronicity
Eventlet was created with good intentions. The main promise of Eventlet was to allow people to transform existing synchronous code in an asynchronous code without even rewriting one line of that existing code. Making asynchronous implicit. The magic behind this behavior is due to monkey patching.
This trick, monkey patching, is used to transform all the blocking IO of the stack loaded at runtime into non blocking IO. This is done by Eventlet by modifying the internal of the standard library through specific monkey patches.
The problem is that it makes Eventlet sensitive to each new version of Python. In parallel of that, one version of Eventlet needs to be compatible with several versions of Python at the same time, between 4 or 5 versions. CPython is daily maintained by hundred of developers where Eventlet is maintained by 2 or 3 persons. That's not the same scale. In other words it is almost impossible to keep Eventlet synced with CPython.
Python advocate for explicit. Eventlet on the other hand, by using monkey patching, introduce lot of implicit behaviours. Unfortunatelly, with time, all these implicit tricks finish by lot of incidence on existing code, that should not have been impacted. Implicit async transformations became explicit bug fixes here and there.
The promise is not kept.
Cooperative Multitasking
Eventlet is presented as a cooperative multitasking library, but in practice, its behavior is not entirely cooperative in all situations.
Eventlet is supposed to be cooperative, it utilizes green threads (or greenlets), which are lightweight execution units managed in user space. These green threads explicitly yield control through monkey patching of standard libraries (socket, time, threading, etc.). It is based on an event loop similar to AsyncIO, but with an approach based on lightweight threads rather than coroutines.
In the example below, eventlet.sleep()
explicitly allows other tasks to run:
import eventlet
def task1():
print("Task 1 starts")
eventlet.sleep(2) # Yields control here
print("Task 1 ends")
def task2():
print("Task 2 starts")
eventlet.sleep(1) # Yields control here
print("Task 2 ends")
eventlet.spawn(task1)
eventlet.spawn(task2)
eventlet.sleep(3) # Waits for tasks to execute
But in reality, Eventlet is not always truly cooperative. Indeed, unpatched blocking calls can block the entire program. If an external library uses network or I/O calls without being "monkey patched," these calls will block all green threads. Example of unpatched code that will block the entire program:
import eventlet
import time
def blocking_task():
print("Starting a blocking task")
time.sleep(3) # Uses standard time.sleep, blocks all of Eventlet
print("Blocking task ends")
eventlet.spawn(blocking_task)
eventlet.sleep(1) # This line does not allow other tasks to run
Eventlet do not provide any automatic preemption. Unlike classic threads or AsyncIO coroutines, a green thread does not yield control unless it performs a cooperative operation (eventlet.sleep()
, socket.recv()
, etc.). If a task runs a long loop without ever yielding control, it blocks all others.
Conclusion, Eventlet is theoretically cooperative, but in practice, it heavily relies on the proper behavior of the code and libraries used. It is not truly cooperative in all circumstances: if a blocking call or an infinite loop without sleep()
occurs, the entire program can freeze.