Skip to main content

Go-style stackful coroutines in Python (transparent-blocking I/O via socket monkey-patch).

Project description

Runloom

Go-style stackful coroutines for Python. Write blocking code — fiber(fn), plain recv/send, no async/await — and run a million of them across every core in one process. Hand-rolled asm context switch + C work-stealing scheduler + netpoll, built for free-threaded Python 3.14t (GIL off).

import threading, runloom
from urllib.request import urlopen
runloom.monkey.patch()

def crawl(url):
    # urlopen() looks blocking -- but monkey.patch() parks the goroutine on the
    # socket instead of the OS thread, so all 64 fetches overlap on real cores.
    body = urlopen(url, timeout=10).read()
    print(threading.get_native_id(), len(body))

def main():
    for _ in range(64):
        runloom.fiber(crawl, "http://example.com")

runloom.run(8, main)   # 8 hub threads -> real cores on 3.14t (GIL off)

Runloom vs Go

Same box (64c, free-threaded CPython 3.13t), 8 hubs / GOMAXPROCS=8, warm steady-state. Go ≈ 2.1 M spawn/s here.

metric runloom Go verdict
spawn — pure C (c_entry) 2.29 M/s 2.10 M/s beats Go
spawn — Python (runloom.fiber) 1.35 M/s 2.10 M/s 0.65×
context switch ~75 ns yield · ~560 ns chan RT ~50 ns Gosched ~parity
conn/s — churn (new conn per req) ~75–78 k/s ~75–78 k/s parity
req/s — keep-alive echo, Python handler 596 k/s 603 k/s 0.99× — parity (C handler beats Go)
memory — empty parked fiber 8.8 KB 2.7 KB 3.3× (the one real gap)

The short story: on spawn, scheduling, and throughput, runloom trades blows with Go and beats it on raw spawn — a stackful coroutine runtime on CPython matching a compiled language even with a Python handler (596 k vs 603 k req/s at saturation; a C handler beats Go). The one honest gap left is memory: a suspended fiber carries a CPython eval frame, ~3.3× Go's per-fiber RSS. Full cross-runtime numbers + cold spawn-vs-N curves: benchmark report · perf summary.

runloom.optimize("throughput")   # runloom.fiber -> max spawn rate (fiber_fast)
runloom.optimize("memory")       # runloom.fiber -> small right-sized stacks (default)

Install

pip install runloom
import runloom      # scheduler + channels, plus monkey/time/context/sync/aio

Prebuilt wheels (no compiler needed) for CPython 3.11–3.14 on Linux (x86_64/aarch64), macOS (arm64/x86_64), Windows (AMD64); source build elsewhere. No runtime dependencies.

What it is

  • Hand-rolled asm context switch (x86_64 SysV, aarch64) — ~80 ns/swap, no syscall; Windows Fibers / POSIX ucontext fallback.
  • M:N work-stealing scheduler (3.13t) — Chase-Lev deque per hub, per-hub MPSC submission, woken goroutines routed back to their origin hub.
  • Per-goroutine PyThreadState snapshot — cframe, datastack, exc_info, contextvars, recursion; a million yielded goroutines share their hub threads with no frame-chain cliff.
  • netpoll — epoll / kqueue / IOCP / WSAPoll / select; goroutines park transparently on fd readiness, lost-wake-free 3-state park-commit.
  • Go-style channelsChan(capacity), select, for v in ch.
  • Stall isolation + recovery — one unanticipated blocking call stalls only its hub, and the runtime detects + recovers it (default on, 3.13t).
  • monkey.patch() makes blocking stdlib (socket, time, threading, …) cooperative, so existing blocking code runs unchanged.

Already have async def code? The runloom.aio bridge runs it on the single-threaded scheduler (runloom.aio.run(main())asyncio.run) — a zero-rewrite port path, not a multi-core speedup (use the sync API with run(n>1, main) for that).

Honest limitations

  • The multi-core win needs free-threaded CPython 3.13t (3.11+ for the frame snapshot at all). On a GIL build runloom still runs — cheap spawn, the goroutine model, netpoll — but single-core like asyncio.
  • runloom doesn't make Python faster per core. CPython's ~80 k pure-Python ops/s/core is a constant it can't raise; it lets one process hit that on every core at once with a blocking model. The scheduler itself is Go-class.
  • Higher memory per goroutine than Go (~3.3× for an empty fiber — the CPython eval frame; a C handler closes most of it).
  • Preemption fires only at Python bytecode boundaries — a goroutine inside a tight pure-C call (e.g. numpy) holds its hub until it returns (same as Go + cgo).
  • Linux x86_64 / 3.13t is the primary, heavily-validated target (2 M-conn runs, fuzzing, sanitizers, formal models); other backends are maintained in-step but less deeply exercised.

Platform support

OS / arch switch netpoll tested
Linux x86_64 fcontext-asm epoll yes — hw, 3.11 / 3.12 / 3.13t / 3.14t (primary)
Linux aarch64 fcontext-asm epoll qemu
macOS x86_64 / arm64 fcontext-asm kqueue hw, 3.14t
FreeBSD / GhostBSD fcontext-asm kqueue hw, 3.12
Windows 10/11 / Server 2022 Fibers IOCP→WSAPoll→select hw, 3.14t
Solaris / Android / other BSD ucontext / asm select / epoll / kqueue review

Docs & layout

Full guide in docs/: Quickstart · Asyncio bridge · Sync API · Channels · M:N parallelism · Cookbook · API reference

Dir Contents
src/runloom_c/ C extension: scheduler, channels, netpoll, asm backends, M:N hubs, stall recovery
src/runloom/ Python layers: aio, sync, monkey, time, runtime
tests/ · examples/ · benchmark/ · docs/ tests · runnable examples · benchmarks + perf harness · docs

Build from source (contributors): pip install -e . from a clone (needs a C compiler; scripts/install.sh / scripts\install.bat bootstrap one). To hack on runloom against free-threaded CPython, use a 3.13t interpreter.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

runloom-0.2.0.tar.gz (1.6 MB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

runloom-0.2.0-cp314-cp314t-manylinux_2_38_x86_64.whl (975.6 kB view details)

Uploaded CPython 3.14tmanylinux: glibc 2.38+ x86-64

runloom-0.2.0-cp313-cp313t-manylinux_2_38_x86_64.whl (966.8 kB view details)

Uploaded CPython 3.13tmanylinux: glibc 2.38+ x86-64

File details

Details for the file runloom-0.2.0.tar.gz.

File metadata

  • Download URL: runloom-0.2.0.tar.gz
  • Upload date:
  • Size: 1.6 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.4

File hashes

Hashes for runloom-0.2.0.tar.gz
Algorithm Hash digest
SHA256 75d09d2a1fa6d3da4db1207a58e0ef8a89099797e20ec4c1e3d9fc35bb274f2c
MD5 8d7240f97e084a399e9fc8e8802b5ab8
BLAKE2b-256 34f9fd66979261e4e663b9179b6aedf6fb5f690154df9ef63998de915e051916

See more details on using hashes here.

File details

Details for the file runloom-0.2.0-cp314-cp314t-manylinux_2_38_x86_64.whl.

File metadata

File hashes

Hashes for runloom-0.2.0-cp314-cp314t-manylinux_2_38_x86_64.whl
Algorithm Hash digest
SHA256 9c1d533142ec0b11cc7453a7d1b1c9bbfbebbe0962a5d0d67154da8f8aeb4e5b
MD5 dffd07a984cbeb3ed0f389479b20d567
BLAKE2b-256 9f7897e98d4e3076ee1cdf9a425516f0c46c93667eeb06f9ea471aa0bd940fc0

See more details on using hashes here.

File details

Details for the file runloom-0.2.0-cp313-cp313t-manylinux_2_38_x86_64.whl.

File metadata

File hashes

Hashes for runloom-0.2.0-cp313-cp313t-manylinux_2_38_x86_64.whl
Algorithm Hash digest
SHA256 146d9ccd69ccd98a30864d67fbf68f58395a5a495d663a70a18a9df5f8a0ecac
MD5 979b4e5c88be15003e43462e2a49de18
BLAKE2b-256 d5009c5f9ad0f29cedac2987a29dc24a3c24396756896349bb751203f813e4e5

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page