Skip to content
This repository was archived by the owner on Aug 19, 2025. It is now read-only.
This repository was archived by the owner on Aug 19, 2025. It is now read-only.

Using ContextVar to hold connections does not guarantee they are always unique to tasks. #230

@trvrm

Description

@trvrm

I think there's a fairly fundamental problem with the way that database connections are managed using ContextVar.

Database.connection() is designed to return a Connection object that is unique to an asyncio Task, but because of the way that context is copied during Task creation, this is not always the case.

Consider the following code:

import asyncio
from databases import Database
database = Database("postgresql://example:password@localhost/example")

async def t1():
    await database.connect()
    c1 = database.connection()

    async def inner():
        c2 = database.connection()
        assert c1 is not c2 # this fails
    await asyncio.gather(inner())


if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(t1())

This raises an assertion, because when gather creates a new task, it copies the current context into the newly created coroutine.

Tasks support the contextvars module. When a Task is created it copies the current context and later runs its coroutine in the copied context.

This is the fundamental problem behind #134 and possibly several other issues. In general it makes it impossible to safely use this library in any code that uses gather or create_task.

Unfortunately I'm not sure what the best way of fixing this is. The only idea I've had so far is storing the connection as an attribute of asyncio.current_task() rather than in a ContextVar, but that feels a bit hacky.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions