Filament is a greenlet-based cooperative concurrency library for Python — an
efficient alternative to gevent and
eventlet built around a small C core. It
gives you lightweight "greenthreads" that yield to a scheduler on I/O and
synchronization instead of blocking OS threads, plus cooperative drop-in
replacements for the standard library (socket, ssl, select, time,
os, subprocess, threading, queue) and near drop-in compatibility shims
for both gevent and eventlet.
It runs on CPython 2.7 and 3.9–3.15 (the same source, one set of C extensions), and it does not have the cross-thread greenlet-switch bug that still bites gevent and eventlet today (see below).
Filament grew out of a bug that was hit in 2013 in the OpenStack project, which was built on eventlet: logging from inside a thread pool could deadlock the whole process, because eventlet's monkey-patched locks aren't safe across OS threads. A real fix inside eventlet would have destroyed its performance — but moving the right pieces into C made it possible to fix the design and keep (even improve) the speed. The full story is in HISTORY.md.
greenlet binds every greenlet to the OS thread that first switched into it;
switching into it from another thread raises
greenlet.error: cannot switch to a different thread. gevent and eventlet
each run a per-thread "hub", but their synchronization primitives can end up
trying to wake a greenlet that lives on a different thread. The classic
trigger is the standard logging module: it holds a module-level mutex, and
once that mutex has been monkey-patched into a green lock, logging from inside
a real OS-thread pool while the hub runs greenthreads on the main thread
deadlocks (or crashes with the error above).
Filament's answer is structural:
- One scheduler per OS thread, stored in thread-specific data. A scheduler is itself a greenlet running an event loop with the GIL released.
- Waiters bind (scheduler, greenlet) together at the moment they wait.
- Signalling never switches a greenlet across threads. Waking a waiter
only enqueues the switch onto that greenlet's home scheduler and pokes its
condition variable; the actual
greenlet.switch()happens later, on the owning thread, from that scheduler's own loop.
So a wakeup originating in the I/O thread, a thread-pool worker, or any other
OS thread is always deferred into the correct thread — there is no
cross-thread switch to get wrong. Logging from a thread pool "just works"
(in the included benchmark, filament runs that workload at ~15–17k msg/s while
both gevent and eventlet deadlock — even with gevent's documented
mitigations), and a single filament Queue can be shared freely between
greenthreads and native threading.Thread workers — a pattern that is
undefined behavior on gevent and eventlet.
Requirements:
- Python 2.7 or 3.9–3.15
- greenlet (use the 1.1.x line for
Python 2.7, 3.x otherwise). On Python 3.10+ filament builds and prefers its
own vendored, performance-tuned greenlet fork (
_fil_greenlet) — the installed greenlet is still used for headers at build time and as a runtime fallback on 2.7/3.9. - A C compiler (C++ for the vendored greenlet) and
libeventdevelopment headers (Debian/Ubuntu:sudo apt-get install libevent-dev).libbluetooth-devis optional (Bluetooth socket support; compiled in only when the header is present).
Build the extensions in place:
pip install greenlet
python setup.py build_ext --inplaceFilament ships seven C extension modules under the _filament package
(core, io, socket, queue, locking, timer, thrpool), plus the
vendored greenlet extension on 3.10+; the user-facing API is the pure-Python
filament package layered on top.
On Python 3.10+ the vendored greenlet uses a private-stack fiber core by
default (each greenthread gets its own guard-paged stack; switches are a
minimal assembly path — no per-switch stack copying). Set FIL_FIBER_CORE=0
at build time to keep classic greenlet stack-slicing instead; tune
FIL_FIBER_STACK_SIZE / FIL_FIBER_POOL_MAX at runtime for unusual
concurrency/memory profiles.
Native API:
import filament
def worker(n):
filament.sleep(0.01)
return n * n
# spawn returns a greenthread; .wait() joins it and returns the value
# (or re-raises the exception raised inside it).
gts = [filament.spawn(worker, i) for i in range(1000)]
results = [gt.wait() for gt in gts]
# Pools bound concurrency:
pool = filament.GreenPool(50)
for i in range(1000):
pool.spawn(worker, i)
pool.waitall()
# Events, results, timeouts:
ev = filament.Event()
ar = filament.AsyncResult()
with filament.Timeout(5.0):
...
# Run a blocking call in a real OS-thread pool without blocking the hub:
filament.tpool.execute(some_blocking_function, arg)Cooperative sockets:
from filament import socket
srv = socket.socket()
srv.bind(("127.0.0.1", 0)); srv.listen(128)
def serve():
while True:
conn, _ = srv.accept()
filament.spawn(handle, conn) # one greenthread per connectionMake the standard library cooperative (like gevent.monkey /
eventlet.monkey_patch):
import filament.patcher
filament.patcher.patch_all() # socket, ssl, select, os, time,
# thread, threading, subprocess, queueGranular patches are available too (patch_socket, patch_ssl,
patch_select, patch_os, patch_time, patch_thread, patch_subprocess,
patch_queue), plus get_original, is_module_patched, and
is_object_patched.
patch_thread(logging=True, existing_locks=True) converts the already-created
logging locks (the module lock and every handler lock) to cooperative locks
while the process is still single-threaded — this, together with the scheduler
design above, is what keeps logging-from-a-thread-pool safe.
Filament can masquerade as gevent or eventlet without shadowing the real
packages on disk. Install the shim before importing under the target name:
import filament.gevent_compat as gevent_compat
gevent_compat.install() # registers sys.modules['gevent'], etc.
import gevent
from gevent import monkey; monkey.patch_all()
from gevent.pool import Pool
from gevent.pywsgi import WSGIServer...and similarly:
import filament.eventlet_compat as eventlet_compat
eventlet_compat.install()
import eventlet
from eventlet.green import socket
pool = eventlet.GreenPool()The shims cover the common surface: spawn/spawn_n/spawn_later,
Greenlet/GreenThread, joinall/killall, Event/AsyncResult,
Timeout/with_timeout, Semaphore/lock, Pool/Group/GreenPool/
GreenPile, queue (incl. Channel), monkey/monkey_patch, the
green.* / gevent.socket etc. modules, tpool/threadpool,
hubs.trampoline, a working StreamServer and a minimal pywsgi/wsgi WSGI
server. See the module docstrings for the handful of documented stubs.
Implemented, mapped onto filament's C core and native primitives:
- Greenthreads:
spawn,spawn_n,spawn_later/spawn_after,kill/killall,joinall,wait/iwait,getcurrent,sleep,yield_thread. - Sync/result:
Event,AsyncResult,Lock,RLock,Condition,Semaphore,Timeout/with_timeout. - Pools:
Group,Pool,GreenPool,GreenPile. - Queues:
Queue,SimpleQueue(C), plus pure-PythonPriorityQueue/LifoQueueand gevent'sChannel. Filament queues are safe to share between greenthreads and native OS threads simultaneously. - Native-thread offload:
tpool.execute/tpool.Proxy(and a gevent-shapedThreadPool). - Cooperative stdlib:
socket,ssl(modernSSLContext),select(select();pollraises a clear error),time,os(read/write),subprocess(cooperativewait/communicate),threading(cooperativeThread, greenlet-locallocal),queue. - Servers:
StreamServerand a minimal WSGI server via the compat shims.
By default (on 3.12+), switches skip eagerly materializing frame state and
gr_frame-style introspection is reconstructed lazily on access — tracebacks
and postmortems always work. For live debugging, filament.set_debug(True)
(or FILAMENT_DEBUG=1, or simply installing a trace/profile hook — it
auto-arms) restores fully eager frame exposure, at a small per-switch cost.
The same source builds and passes the full test suite on CPython 2.7.18,
3.9, 3.10, 3.11, 3.12, 3.13, 3.14, and 3.15 — 505 tests on 3.12/3.13,
499 (+6 lazy-debug-only skips) elsewhere. Python 2 vs 3
differences are centralized in include/core/pyversion.h (string/int APIs,
module init, greenlet parent-reference ownership) rather than scattered
through the C.
benchmarks/ contains a filament-vs-gevent-vs-eventlet suite (spawn
throughput, context-switch rate, semaphore/queue ops, a mixed
greenthread+native-thread shared queue, thread-pool round-trip, echo-server
req/s + latency, and the #137 logging test), each framework run in a fresh
subprocess. Run it with:
python benchmarks/run_all.py [--python /path/to/venv/bin/python]Full numbers are in benchmarks/RESULTS.md, which is tables only; what each benchmark does, which numbers may be compared with which, and how to re-run the matrix are in benchmarks/METHODOLOGY.md. As of the latest full matrix, filament leads gevent and eventlet on every benchmark on every supported interpreter (2.7 through 3.15). Headlines (within-version ratios vs gevent):
- Context switches: 1.9–3.0× (up to 4.7M switches/s with the fiber core).
- Spawn throughput: 2.3–4.2×, widest on 2.7 and 3.9.
- Semaphore ops: 2.2–3.2×; queue: 1.1–1.4×.
- Thread-pool round-trip: 1.1–1.9× on Python 3 (read a single cell loosely — it is thread-placement sensitive); on 2.7 gevent's threadpool stalls outright.
- Echo server: 1.2–2.4× gevent's req/s at concurrency 100 and 1.3–2.2× at concurrency 1000, on both machines, with far better p50/p99 throughout.
- Mixed green+native shared queue: 1–5.5M items/s; gevent silently loses items and eventlet deadlocks on the same workload.
- #137 logging-from-threadpool: filament completes on every interpreter and both machines; eventlet always deadlocks, and gevent deadlocks on macOS while completing on Linux. filament's throughput is 1.6–2.9k msg/s on macOS against 114–164k on Linux — that gap is GIL handoff, and on a free-threaded build macOS reaches 142–153k.
python -m pytest tests/The suite covers the native API, the cooperative stdlib, the patcher, both
compat shims, cross-thread queue sharing, and the runtime debug modes.
tests/test_cross_thread_137.py is the regression test for the bug described
above: it logs from thread-pool workers while the hub runs greenthreads and
asserts there is no greenlet.error and no deadlock.
MIT. Copyright (c) 2013–2026, Chris Behrens. See LICENSE.
vendor/greenlet/ contains a vendored copy of
greenlet 3.5.4 with
filament-specific modifications; it remains under greenlet's own licenses
(MIT-style, plus the PSF license for its Stackless-derived platform files) —
see vendor/greenlet/LICENSE, vendor/greenlet/LICENSE.PSF, and
vendor/greenlet/VENDORED.md for provenance and the list of local changes.
A few small portions of filament itself are derived from CPython's standard
library under the PSF License (LICENSE.PSF); see THIRD_PARTY_NOTICES.md
for the complete list.