Zero-downtime process upgrades for Python, inspired by cloudflare/tableflip
Project description
tableflip — Graceful process restarts in Python
Zero-downtime upgrades for Python network services. Update running code or configuration without dropping existing connections.
This is a Python port of Cloudflare's tableflip Go library. The core design — fd inheritance, IPC protocol, and state machine — follows the original closely, adapted to Python's asyncio runtime.
Works on Linux and macOS. Raises NotSupportedError on Windows (use tableflip.testing stubs instead).
How it works
- On
SIGHUP, the running process spawns a new copy of itself - TCP listener sockets are passed to the new process via fd inheritance
- The new process signals readiness after initialization
- The old process stops accepting new connections and exits
Only one upgrade runs at a time. If the new process crashes during init, the old one keeps serving.
Installation
uv add tableflip
# or
pip install tableflip
Requires Python 3.13+.
Usage
import asyncio
import signal
from tableflip import Upgrader, Options
async def main():
upg = await Upgrader.new(Options(pid_file="/tmp/myapp.pid"))
# Trigger upgrade on SIGHUP
loop = asyncio.get_running_loop()
loop.add_signal_handler(signal.SIGHUP, lambda: asyncio.create_task(do_upgrade(upg)))
# Listen must be called before ready()
sock = await upg.fds.listen("127.0.0.1", 8080)
server = await asyncio.start_server(handle_conn, sock=sock)
await upg.ready()
# Block until an upgrade completes or stop() is called
await upg.exit().wait()
# Graceful shutdown
server.close()
await server.wait_closed()
await upg.wait_for_parent()
upg.stop()
async def do_upgrade(upg: Upgrader):
try:
await upg.upgrade()
except Exception as e:
print(f"Upgrade failed: {e}")
async def handle_conn(reader, writer):
writer.write(b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nok")
await writer.drain()
writer.close()
asyncio.run(main())
Trigger an upgrade:
kill -HUP $(cat /tmp/myapp.pid)
Integration with systemd
[Unit]
Description=My Python service
[Service]
ExecStart=/path/to/venv/bin/python app.py
ExecReload=/bin/kill -HUP $MAINPID
PIDFile=/tmp/myapp.pid
Testing
Use the tableflip.testing module for unit tests or unsupported platforms:
from tableflip.testing import Upgrader, Fds
upg = Upgrader() # never upgrades, upgrade() raises NotSupportedError
await upg.ready() # no-op
assert not upg.has_parent()
API
| Method | Description |
|---|---|
await Upgrader.new(opts) |
Create an upgrader (one per process) |
await upg.fds.listen(addr, port) |
Get an inherited or new TCP listener |
await upg.ready() |
Signal readiness, notify parent, write PID file |
await upg.upgrade() |
Spawn new process and wait for it to become ready |
upg.exit() |
Returns asyncio.Event set when the process should exit |
upg.stop() |
Prevent further upgrades and trigger exit |
await upg.wait_for_parent() |
Block until parent process exits |
upg.has_parent() |
True if spawned by a tableflip upgrade |
Acknowledgments
This is a Python port of cloudflare/tableflip by Cloudflare. The Go library was created by Lorenz Bauer and the Cloudflare team. All credit for the design and protocol goes to them.
License
See LICENSE (BSD 3-Clause, same as the original Go library).
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tableflip-0.1.0.tar.gz.
File metadata
- Download URL: tableflip-0.1.0.tar.gz
- Upload date:
- Size: 9.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c64cb74de98db6b93402bf2ac1446bcb30dd33eb87b013b453160b4564db0928
|
|
| MD5 |
fe2f54477fd5d051e9e776ba68e10b7f
|
|
| BLAKE2b-256 |
7623a01e99e1857a8c0d920ecc28d0987f7e816dfb57d7b6dce0fb1e6f1f3cd5
|
Provenance
The following attestation bundles were made for tableflip-0.1.0.tar.gz:
Publisher:
publish.yml on bernardoVale/tableflip-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tableflip-0.1.0.tar.gz -
Subject digest:
c64cb74de98db6b93402bf2ac1446bcb30dd33eb87b013b453160b4564db0928 - Sigstore transparency entry: 1280138642
- Sigstore integration time:
-
Permalink:
bernardoVale/tableflip-py@2dcc44839be383fe81a845d7ee0a3509ffe2dbe7 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/bernardoVale
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@2dcc44839be383fe81a845d7ee0a3509ffe2dbe7 -
Trigger Event:
release
-
Statement type:
File details
Details for the file tableflip-0.1.0-py3-none-any.whl.
File metadata
- Download URL: tableflip-0.1.0-py3-none-any.whl
- Upload date:
- Size: 13.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
403e456d5a79bd9efde8595a175935088bf0f41fdd8163e595bb40d5b086d5bc
|
|
| MD5 |
34b12df8df555fc56ad2613fbacebcaf
|
|
| BLAKE2b-256 |
b6add16cb1e5afcee6da8b610d7f59f6e1ec0bc731269123f368bb2a69cc24e9
|
Provenance
The following attestation bundles were made for tableflip-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on bernardoVale/tableflip-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tableflip-0.1.0-py3-none-any.whl -
Subject digest:
403e456d5a79bd9efde8595a175935088bf0f41fdd8163e595bb40d5b086d5bc - Sigstore transparency entry: 1280138710
- Sigstore integration time:
-
Permalink:
bernardoVale/tableflip-py@2dcc44839be383fe81a845d7ee0a3509ffe2dbe7 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/bernardoVale
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@2dcc44839be383fe81a845d7ee0a3509ffe2dbe7 -
Trigger Event:
release
-
Statement type: