gevent review
gevent 26.8.0 schedules greenlets on a libev or libuv loop so synchronous Python code can make progress while another call waits for I/O. Its monkey module swaps standard socket, SSL, threading, subprocess, and related objects for cooperative equivalents. Pools, bounded queues, events, timeouts, DNS resolvers, and a WSGI server fill out the runtime. The current release adds Python 3.15 wheels and repairs deadlocks around ThreadPoolExecutor and fork, missed semaphore wakeups from native threads, and subprocess cleanup that could hide the original exception.
gevent 26.8.0 installed in 0.3 seconds, used 11 MB, and imported in 0.16 seconds in our sandbox with no audit findings. Keep it for a measured synchronous I/O stack; prefer explicit async code for a new service or any runtime dominated by native blocking calls, CPU work, or Windows.
We installed it
| Install | ✓ · 0.3s | 4 packages on disk · 11 MB |
| Import | ✓ | import gevent in 0.16s · compiled extensions · requires Python >=3.10 |
| Known vulns | 0 | (pip-audit) |
Answers from our run
Does gevent install cleanly?
Yes. In a fresh container with an empty cache, pip install gevent finished in 0.3s, leaving 4 packages and 11 MB on disk. pip-audit reported no known vulnerabilities.
What does gevent need to run?
Python >=3.10, and a platform wheel with compiled extensions. In our run import gevent succeeded in 0.16s.
gevent or eventlet: which should you use?
eventlet: Use it only to maintain software already coupled to Eventlet's green-thread APIs. gevent 26.8.0 installed in 0.3 seconds, used 11 MB, and imported in 0.16 seconds in our sandbox with no audit findings.
When should you not use gevent?
You are starting a new async service. asyncio, AnyIO, or Trio exposes suspension and cancellation in source code instead of replacing imported standard-library functions.
Use it if
- An established synchronous WSGI service needs more concurrent socket work without rewriting every call site to async and await.
- Gunicorn's gevent worker is already part of the stack and every database, DNS, HTTP, and queue client has been tested under patching.
- A crawler or proxy spends most of its lifetime waiting and can limit outstanding calls with Pool or a bounded Queue.
- Legacy functions need an outside deadline even though their own signatures have no timeout or cancellation parameter.
- You are starting a new async service. asyncio, AnyIO, or Trio exposes suspension and cancellation in source code instead of replacing imported standard-library functions.
- A required native extension blocks internally. Replacing Python's socket object cannot make that C call yield; the driver needs a proven adapter or a real-thread boundary.
- The workload spends long stretches in Python computation. One greenlet that does not yield stops the hub and delays every other greenlet in that process.
- Windows is the production target. gevent documents Windows as best-effort support and recommends against using it for production deployments.
- Free-threaded CPython is required. gevent's compiled extensions depend on the GIL and turn it on when imported.
- Package-owned typing is mandatory. Our 26.8.0 wheel had no py.typed marker, so type checkers cannot assume the installed implementation is fully typed.
Setup reality
We installed gevent 26.8.0 in a clean, unprivileged Python 3.12 Bookworm container. It completed in 0.3 seconds, left 4 packages, and occupied 11 MB. Its metadata declares 23 direct requirements across core dependencies, environment markers, and extras. The wheel contains compiled .so files, requires Python 3.10 or newer, lacks py.typed, and imported in 0.16 seconds. pip-audit returned zero known vulnerabilities.
There is no account or application config file. Import order is the setup trap. gevent.monkey.patch_all() must run before code imports and caches socket, ssl, threading, or subprocess objects. When the entrypoint cannot be edited, python -m gevent.monkey can patch before loading the script. The changelog warns that patch_all arguments will become keyword-only in an early-2027 release, so named arguments are the safer form today.
Only Python-level calls covered by gevent become cooperative. A native database or network client needs its own compatibility evidence; psycopg2, for example, commonly uses psycogreen. Put unknown blocking I/O in gevent.threadpool until load tests show safe behavior. CPU work belongs in another process. Even tiny greenlets consume memory, descriptors, and upstream capacity, so cap them with Pool and slow producers with Queue(maxsize=...).
Version 26.8.0 fixes a lock deadlock involving ThreadPoolExecutor and fork, plus a wakeup failure when a hubless native thread released a semaphore. It also preserves the real exception when subprocess pipe cleanup re-enters communicate(). Those fixes do not make late patching safe. Apply patches while the process has one thread, and turn on the monitor thread when a stuck hub otherwise leaves little diagnostic evidence.
Patterns
Replace blocking standard modules early patch-before-imports
from gevent import monkey
monkey.patch_all()
import gevent
import requests
jobs = [gevent.spawn(requests.get, url) for url in urls]
gevent.joinall(jobs)patch_all must precede imports that retain socket, SSL, thread, or subprocess objects. A late call can leave a mixture of blocking and cooperative references.
Read each task's outcome inspect-greenlet-failures
jobs = [gevent.spawn(fetch, item) for item in items]
gevent.joinall(jobs)
for job in jobs:
if job.successful():
consume(job.value)
else:
logger.error('fetch failed', exc_info=job.exception)joinall waits for completion but does not automatically re-raise every child exception. Check successful(), value, and exception yourself.
Set a hard concurrency cap limit-parallel-io
from gevent.pool import Pool
pool = Pool(40)
responses = pool.map(fetch, urls)The Pool limits active greenlets to 40. It protects file descriptors and the upstream service when the input list is much larger.
Bound a call that lacks a timeout enforce-deadline
from gevent import Timeout
try:
with Timeout(5):
payload = client.read()
except Timeout:
payload = NoneTimeout is delivered the next time this greenlet yields. It cannot interrupt a CPU loop or native call that never returns to the hub.
Return a sentinel after five seconds return-timeout-fallback
result = gevent.with_timeout(
5,
client.read,
timeout_value=None,
)None must be impossible as a successful result, or the caller cannot tell a timeout from valid data.
Slow the producer with a bounded queue apply-queue-backpressure
from gevent.queue import JoinableQueue
queue = JoinableQueue(maxsize=100)
def worker():
while True:
item = queue.get()
try:
handle(item)
finally:
queue.task_done()
workers = [gevent.spawn(worker) for _ in range(10)]
for item in source():
queue.put(item)
queue.join()
gevent.killall(workers)queue.put waits when 100 items are pending. task_done belongs in finally, or one failed item can make queue.join wait forever.
Pass a value or exception to a waiter share-one-result
from gevent.event import AsyncResult
outcome = AsyncResult()
def produce():
try:
outcome.set(compute())
except Exception as exc:
outcome.set_exception(exc)
gevent.spawn(produce)
value = outcome.get(timeout=10)AsyncResult.get re-raises an exception supplied with set_exception, so failure is not mistaken for an empty result.
Run WSGI with gevent workers configure-gunicorn-worker
# gunicorn.conf.py
worker_class = 'gevent'
workers = 4
worker_connections = 1000
# gunicorn -c gunicorn.conf.py app:applicationGunicorn owns worker startup and patching here. The 1,000-connection setting is safe only after database pools and external clients have matching limits.
Cap handlers in the built-in WSGI server serve-wsgi-directly
from gevent.pool import Pool
from gevent.pywsgi import WSGIServer
server = WSGIServer(('0.0.0.0', 8080), app, spawn=Pool(500))
server.serve_forever()Pool(500) bounds active handlers. A production deployment still needs process supervision, restart policy, TLS termination, and request limits.
Move an uncooperative call to real threads isolate-blocking-extension
from gevent.threadpool import ThreadPool
native_calls = ThreadPool(4)
result = native_calls.apply(client.call, (payload,))This protects the hub when the extension performs blocking I/O. Threads do not speed up Python computation that holds the GIL.
Report a greenlet that stops yielding diagnose-blocked-hub
import gevent
gevent.config.monitor_thread = True
gevent.config.max_blocking_time = 0.5
from gevent.util import print_run_info
print_run_info()The monitor runs in a native thread and can report stacks while the cooperative loop is stuck. Configure it before starting application work.
Read a subprocess cooperatively run-child-process
from gevent.subprocess import PIPE, Popen
process = Popen(command, stdout=PIPE, stderr=PIPE)
out, err = process.communicate(timeout=60)
if process.returncode != 0:
raise RuntimeError(err.decode())Use gevent.subprocess or patch before importing subprocess. Version 26.8.0 fixes exception handling when interrupted pipe readers are cleaned up.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| eventlet | PyPI | Use it only to maintain software already coupled to Eventlet's green-thread APIs. |
| anyio | PyPI | Use it for structured async code that can operate on asyncio or Trio backends. |
| uvloop | PyPI | Use it when the program already uses asyncio and only needs a libuv-based event loop. |
| trio | PyPI | Use it when nurseries and cancellation scopes should make task ownership explicit. |
More web backend guides
urllib3 · requests · ws · anyio · httpx · undici · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.

