Skip to content

Prevent subscription completion from dropping shared Channels groups #942

Description

@moritz89

Prevent one GraphQL subscription completion from unintentionally dropping sibling subscriptions sharing the same Channels group.

Describe the Bug

Strawberry adds and discards groups with the consumer's channel name, which every subscription on the connection shares. Without counting, the first subscription to end discards the shared channel and silently stops delivery for any other subscription still holding that group.

System Information

  • Operating system: Ubuntu 26.04
  • Python version: 3.14
  • Strawberry version (if applicable):
    • strawberry-graphql: 0.323.2
    • strawberry-graphql-django: 0.86.5

Additional Context

Our workaround to that issue:

class RefCountedChannelGroupsMixin:
    """Ref-count Channels group membership for one WebSocket connection.

    Strawberry adds and discards groups with the consumer's channel name, which
    every subscription on the connection shares. Without counting, the first
    subscription to end discards the shared channel and silently stops delivery
    for any other subscription still holding that group.
    """

    def __init__(self, *args: Any, **kwargs: Any) -> None:
        self._group_refs: Counter[str] = Counter()
        super().__init__(*args, **kwargs)

    @contextlib.asynccontextmanager
    async def listen_to_channel(
        self,
        type: str,
        *,
        timeout: float | None = None,
        groups: Sequence[str] = (),
    ) -> AsyncGenerator[Any, None]:
        async with super().listen_to_channel(type, timeout=timeout) as generator:
            for group in groups:
                if not self._group_refs[group]:
                    await self.channel_layer.group_add(group, self.channel_name)
                self._group_refs[group] += 1
            try:
                yield generator
            finally:
                for group in groups:
                    self._group_refs[group] -= 1
                    if self._group_refs[group] <= 0:
                        del self._group_refs[group]
                        with contextlib.suppress(Exception):
                            await self.channel_layer.group_discard(
                                group, self.channel_name
                            )

--

from strawberry.channels import GraphQLWSConsumer

class AuthGraphQLWSConsumer(RefCountedChannelGroupsMixin, GraphQLWSConsumer):
    async def receive(
        self, text_data: str | None = None, bytes_data: bytes | None = None
    ) -> None:
        ...

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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