Skip to content

imgeaslikok/django-concurrency-safe

Repository files navigation

django-concurrency-safe

PyPI version Python versions

Concurrency guard for Django using PostgreSQL advisory locks.

Prevent race conditions in critical sections using simple, expressive decorators or context managers.


Why?

Race conditions are easy to introduce and hard to detect.

Example:

def withdraw(user, amount):
    if user.balance >= amount:
        user.balance -= amount
        user.save()

Two concurrent requests can both pass the balance check and withdraw twice.

This library prevents that.


Features

  • PostgreSQL advisory lock backend
  • Simple decorator API
  • Context manager support
  • Business-key locking (not limited to database rows)
  • Timeout and conflict handling

Installation

pip install django-concurrency-safe

Quickstart

Import:

from concurrency_safe import concurrency_safe, lock, LockAcquireTimeout

Using the decorator

@concurrency_safe(key="withdraw:user:{user_id}")
def withdraw(user_id, amount):
    ...

Only one execution per key runs at a time.

Using the context manager

with lock("stock:ABC"):
    process_order()

Conflict handling

When the lock cannot be acquired:

@concurrency_safe(key="stock:{sku}")

Raises LockAcquireTimeout by default.

Custom handler:

from django.http import JsonResponse

def busy(*args, **kwargs):
    return JsonResponse({"detail": "busy"}, status=409)

@concurrency_safe(
    key="stock:{sku}",
    on_conflict=busy,
)

Testing

django-concurrency-safe is tested against real PostgreSQL using advisory locks.

Integration tests require a running PostgreSQL instance.

Start PostgreSQL using docker compose:

docker compose up -d

Run tests:

export DATABASE_URL=postgres://postgres:postgres@localhost:5432/concurrency_safe
pytest

Tests will automatically use PostgreSQL via the DATABASE_URL environment variable.


Example project

example/ contains a runnable Django demo showcasing the race condition, row-level locks, and advisory locks (PostgreSQL).


Why advisory locks?

Unlike row-level locking, advisory locks:

  • Work without locking a specific database row
  • Support arbitrary business keys
  • Are fast and lightweight
  • Automatically release on connection close

Requirements

  • Python 3.10+
  • Django 4.2+
  • PostgreSQL

Roadmap

  • Redis backend
  • Async support
  • Metrics hooks

License

MIT

About

Prevent race conditions in Django using PostgreSQL advisory locks.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages