strawberry-graphql / strawberry

A GraphQL library for Python that leverages type annotations 🍓
https://strawberry.rocks
MIT License
3.99k stars 532 forks source link

Extensions execution context can easily overlap between parallel executions. #3571

Open nrbnlulu opened 3 months ago

nrbnlulu commented 3 months ago

Describe the Bug

RN extensions can be singleton instances meaning that the execution context can change during lifepsan hook execution (if you have parallel operaitons running) :

async def test_execution_context_distinct_on_parallel_execution():

    class EnsureDistinctExtension(SchemaExtension):
        def __init__(self) -> None:
            ...
        async def on_operation(self) -> AsyncIterator[None]:
            query = self.execution_context.query
            yield
            assert self.execution_context.query == query

    @strawberry.type
    class Query:
        @strawberry.field
        async def foo(self) -> str:
            await asyncio.sleep(0.01)
            return "pong"

        @strawberry.field
        async def bar(self) -> str:
            await asyncio.sleep(0.02)
            return "pong"

    schema = strawberry.Schema(query=Query, extensions=[EnsureDistinctExtension()])
    query_foo = "query foo { foo }"
    query_bar = "query bar { bar }"
    for _ in range(10):
        results = await asyncio.gather(*[schema.execute(query_foo), schema.execute(query_bar)])

    for res in results:
        assert not res.errors

I want to remove the execution_context from the extension instance and provide it as an argument to each hook what do you guys think? original discussion on discord

Upvote & Fund

Fund with Polar

nburns commented 3 weeks ago

We've seen this in the wild, with an async schema extensions seem to run multiple times since a recent update.

patrick91 commented 3 weeks ago

@nburns I think https://github.com/strawberry-graphql/strawberry/pull/3640 should fix this :)