Skip to main content

Redis-backed idempotent task primitives: fencing tokens and distributed locks

Project description

fencekit

Redis-backed idempotency and fenced locks for background jobs. A destination that atomically checks the fencing token can reject writes from a stale lock holder.

Why

ChessMate (chess-mate.online) runs imported games through Stockfish before writing a coaching report. With Redis as the Celery broker and late acknowledgements enabled, a worker crash can cause the same job to be delivered again. I saw batches progress twice. That wasted Stockfish CPU and left the recorded progress ambiguous.

Celery requires late-ack tasks to be idempotent, but each task still needs code to enforce that property. fencekit collects the Redis operations I used in ChessMate. Its tests reproduce worker death and lock-expiry races.

Install

pip install fencekit
# or
uv add fencekit

Requires Redis 6+ (tested with Redis 7) and Python 3.10+.

30-second example

from datetime import timedelta
from redis import Redis

from fencekit import (
    DistributedLock,
    FenceGate,
    IdempotencyGuard,
    idempotency_key,
)

r = Redis.from_url("redis://localhost:6379/0", decode_responses=True)
guard = IdempotencyGuard(r)
lock = DistributedLock(r)
fence = FenceGate(r)

def analyze_batch() -> None:
    key = idempotency_key(
        {"game_ids": ["abc", "def"], "engine": "sf16"},
        namespace="analysis",
    )
    if not guard.try_begin(key, ttl=timedelta(hours=24)):
        return  # already started or completed

    handle = lock.acquire("analysis:batch-42", ttl=timedelta(minutes=5))
    try:
        # Atomic fence check + Redis progress write:
        fence.set_if_fresh(handle.token, "analysis:batch-42:status", "running")
        # Extend the lock on heartbeats and fence every durable write.
        guard.mark_done(key)
    finally:
        lock.release(handle)

Guarantees

Claim Status
At-most-once start within the idempotency TTL (Redis available) Yes (SET NX)
Mutual exclusion while lock TTL held (single Redis primary) Best-effort lease
Stale holder cannot overwrite via atomic FenceGate.set_if_fresh Yes
Exactly-once delivery No
Safety under Redis failover / split brain No (v0.1)
Safety if the app writes without presenting the token No

See DESIGN.md for the threat model, Lua algorithms, TTL guidance, and crash/restart semantics. fencekit does not implement Redlock.

Fencing covers stale overwrites. Exactly-once external effects also require the destination to enforce the fence token and a unique business-operation key in one atomic operation.

Running tests

REM CMD (Windows), no uv required
cd fencekit
python -m pip install -e ".[dev]"
python -m ruff check --fix src tests
python -m ruff check src tests
python -m mypy src
python -m pytest -m "not integration" -q

The full suite needs Redis on localhost:6379:

set FENCEKIT_REDIS_URL=redis://localhost:6379/15
python -m pytest -q

Integration tests skip cleanly when Redis is unreachable or FENCEKIT_REDIS_URL is unset.

# Linux/macOS with uv + Docker
uv sync --extra dev
uv run pytest -m "not integration"
docker run -d -p 6379:6379 --name fencekit-redis redis:7
export FENCEKIT_REDIS_URL=redis://localhost:6379/15
uv run pytest

v0.1 has no Celery adapter. A later release may add one as an optional extra.

License

MIT

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

fencekit-0.1.0.tar.gz (18.6 kB view details)

Uploaded Source

Built Distribution

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

fencekit-0.1.0-py3-none-any.whl (14.7 kB view details)

Uploaded Python 3

File details

Details for the file fencekit-0.1.0.tar.gz.

File metadata

  • Download URL: fencekit-0.1.0.tar.gz
  • Upload date:
  • Size: 18.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for fencekit-0.1.0.tar.gz
Algorithm Hash digest
SHA256 8ac12cd19f45169b7f578aafe31ee0f2b07f01c3b2eea1fe4973f4a28dd7b1d9
MD5 c5ceaa7f27e72026eee795316d039694
BLAKE2b-256 9d6fa05d0cebcca82262a9766614b50c35f9ae3e7ab446865c16be76a038f993

See more details on using hashes here.

Provenance

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

Publisher: release.yml on ahmed5145/fencekit

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

File details

Details for the file fencekit-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: fencekit-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 14.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for fencekit-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1b957e927f73f5d06ffa6ae5a55d8b4a27ad9fbe6f342ef652a093f86caa4271
MD5 072aedad806acc49211bdda85f4c6cbd
BLAKE2b-256 fae3c1b3f67e45a2e167a015f764fb6f7069368a7678a193485b0ec932769d2f

See more details on using hashes here.

Provenance

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

Publisher: release.yml on ahmed5145/fencekit

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