Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Race condition when using @transaction() decorator #155

Closed
steinitzu opened this issue Oct 28, 2019 · 1 comment
Closed

Race condition when using @transaction() decorator #155

steinitzu opened this issue Oct 28, 2019 · 1 comment

Comments

@steinitzu
Copy link

steinitzu commented Oct 28, 2019

When using @transaction() as a decorator, all calls to the function use the same connection and therefor concurrent calls share the transaction scope, causing all kinds of race-condition mischief.

This is not an issue when using the transaction as a context manager as then the connection is allotted during function call rather than at import time.

Example, calling update_person multiple times concurrently here causes error:

asyncpg.exceptions._base.InterfaceError: cannot commit; the transaction is already committed
import asyncio
import contextvars

from databases import Database

db = Database("postgresql://dev:dev@localhost:6808/dev")


async def init():
    create_table = """
    CREATE TABLE person
    (
    id INTEGER PRIMARY KEY,
    name VARCHAR(100)
    );
    """
    await db.connect()
    await db.execute(create_table)
    await db.execute("INSERT INTO person (id, name) VALUES (1, 'bob')")


@db.transaction()
async def update_person():
    await db.fetch_one("SELECT * FROM person WHERE id = 1")
    await db.execute("UPDATE person SET name='joe' WHERE id = 1")


async def update_concurrently():
    await init()
    """
    Call `update_person` 6 times concurrently,
    simulating e.g. concurrent requests in a web app
    """
    tasks = []
    for i in range(6):
        ctx = contextvars.Context()
        tasks.append(ctx.run(asyncio.create_task, update_person()))
    await asyncio.gather(*tasks)


loop = asyncio.get_event_loop()

loop.run_until_complete(update_concurrently())
@zevisert
Copy link
Contributor

zevisert commented May 26, 2023

This bug with @db.transaction should be fixed in #546, and be safe for concurrent usage again. When that's released, please let me know if this still affects you.

I have tested your MVE on the branch and I'm not seeing that error anymore. Thank you for a functional and complete example! And props for noticing that is was due to contextvars! 👏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants