Skip to main content

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

  1. On SIGHUP, the running process spawns a new copy of itself
  2. TCP listener sockets are passed to the new process via fd inheritance
  3. The new process signals readiness after initialization
  4. 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


Download files

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

Source Distribution

tableflip-0.1.0.tar.gz (9.7 kB view details)

Uploaded Source

Built Distribution

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

tableflip-0.1.0-py3-none-any.whl (13.2 kB view details)

Uploaded Python 3

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

Hashes for tableflip-0.1.0.tar.gz
Algorithm Hash digest
SHA256 c64cb74de98db6b93402bf2ac1446bcb30dd33eb87b013b453160b4564db0928
MD5 fe2f54477fd5d051e9e776ba68e10b7f
BLAKE2b-256 7623a01e99e1857a8c0d920ecc28d0987f7e816dfb57d7b6dce0fb1e6f1f3cd5

See more details on using hashes here.

Provenance

The following attestation bundles were made for tableflip-0.1.0.tar.gz:

Publisher: publish.yml on bernardoVale/tableflip-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for tableflip-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 403e456d5a79bd9efde8595a175935088bf0f41fdd8163e595bb40d5b086d5bc
MD5 34b12df8df555fc56ad2613fbacebcaf
BLAKE2b-256 b6add16cb1e5afcee6da8b610d7f59f6e1ec0bc731269123f368bb2a69cc24e9

See more details on using hashes here.

Provenance

The following attestation bundles were made for tableflip-0.1.0-py3-none-any.whl:

Publisher: publish.yml on bernardoVale/tableflip-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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