Hacktoberfest 2026: những issue maintainer đã đánh dấu cho tháng Mười, đang mở và phù hợp người mới. Xem issue Hacktoberfest

Resolving `MissingGreenlet` error when using async drivers.

Đang mở
#419 3 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

Đánh giá

Độ khó
5/5
Thời gian dự kiến
Hơn một tuần
Mức phù hợp với người mới
25/100
Loại issue
Lỗi
Độ rõ ràng
Cần làm rõ
Mức độ hoạt động
Đình trệ
Công nghệ
graphql, postgresql, python, sqlalchemy
Lĩnh vực
api, backend, databases

Hướng nghiên cứu

Bắt đầu với graphene_sqlalchemy/batching.py, đặc biệt là RelationshipLoader.batch_load_fn tại các dòng được liên kết, và tái hiện lỗi MissingGreenlet bằng thiết lập aiohttp, asyncpg, PostgreSQL và GraphQL được mô tả. So sánh đường đi của SQLAlchemy 2 với workaround greenlet_spawn được cung cấp; công việc được xem là hoàn tất khi có một bản sửa lỗi phổ quát được thống nhất hoặc tài liệu rõ ràng về hành vi của các async driver được hỗ trợ.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

I'm not sure if other people have run into this problem - I searched for days and couldn't find a clear answer. But eventually I figured out a solution, so wanted to document it here in case other people do run into it.

Background

Our setup uses a Postgres database which provides data for a GraphQL API, for which we initially used Flask, following the example. However, our schema uses a lot of 1:N relationships, so our performance was degraded due to the N+1 Round Trip Problem. The fix for this is, supposedly, very simple: just set batching=True in the meta class. However, doing this triggers the use of dataloaders, which operate asynchronously. So we switched from Flask to using the aiohttp web framework and asyncpg database driver, which are better suited for asynchronous tasks. This is when we first stumbled on the MissingGreenlet error: sqlalchemy.exc.MissingGreenlet: greenlet_spawn has not been called; can't call await_only() here. Was IO attempted in an unexpected place?

Issue

The problem here comes from the RelationshipLoader.batch_load_fn() (link). As the function comment explains, that function has to "piggyback on some internal APIs" of sqlalchemy. As a result (I believe), the necessary greenlet_spawn function never gets called, and the above error is thrown. (Greenlets, AFAICT, are small thread-like objects that allow synchronous requests to be performed in an asynchronous environment. In this situation, the dataloader is what actually materializes the expected attributes, so it is necessary to make these calls synchronously)

Solution

So on to the fix: we just need to call greenlet_spawn ourselves. This can be done by simply redefining the batch_load_fn attribute on the RelationshipLoader class, so that all invocations call the corrected function rather than the incorrect code. My stripped down code is below (I removed some extraneous comments/checks/conditions from batch_load_fn for simplicitly, make sure that you compare to the original code and update the correct path for your situation):

from graphene_sqlalchemy.batching import RelationshipLoader
from sqlalchemy.orm import Session
from sqlalchemy.util import immutabledict
from sqlalchemy.util._concurrency_py3k import greenlet_spawn


def patch_relationship_loader() -> None:
    """
CALL THIS FUNCTION ONCE AS PART OF SETUP CODE
    """
    RelationshipLoader.batch_load_fn = batch_load_fn


async def batch_load_fn(self: RelationshipLoader, parents: Any) -> list[Any]:
    """
FOLLOWS THE `SQL_VERSION_HIGHER_EQUAL_THAN_2` PATH
    """
    child_mapper = self.relationship_prop.mapper
    parent_mapper = self.relationship_prop.parent
    session = Session.object_session(parents[0])

    for parent in parents:
        assert session is Session.object_session(parent)
        assert session and parent not in session.dirty

    states = [(sqlalchemy.inspect(parent), True) for parent in parents]

    query_context = None
    if session:
        parent_mapper_query = session.query(parent_mapper.entity)
        query_context = parent_mapper_query._compile_context()
# CALL greenlet_spawn HERE RATHER THAN CALLING _load_for_path DIRECTLY
        await greenlet_spawn(
            self.selectin_loader._load_for_path,
            query_context,
            parent_mapper._path_registry,
            states,
            None,
            child_mapper,
            None,
            None,  # recursion depth can be none
            immutabledict(),  # default value for selectinload->lazyload
        )
    result = [
        getattr(parent, self.relationship_prop.key) for parent in parents
    ]
    return result

Conclusion

I don't have enough knowledge of graphene-sqlalchemy or other database drivers to know what a universal solution would look like (or even if this is a universal problem - the dearth of information about this issue suggests not), so I don't want to submit a PR for this change, but at least in our situation this was the best fix. Hope it helps someone else out there!

Ngôn ngữ chính
Python
Star
985
Fork
224
Chỉ số merge pull request
Không có pull request nào được merge trong 30 ngày

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của graphql-python/graphene-sqlalchemy

Tất cả issue của graphql-python/graphene-sqlalchemy

Issue tương tự

Thêm issue về Python

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.