Hacktoberfest 2026: as issues que os mantenedores marcaram para outubro, abertas e boas para iniciantes. Ver issues do Hacktoberfest

`collect_subfields` memoization keyed on `id()` of `FieldDetails` that are not kept alive returns another field's subfields

Aberta
#284 1 comentário 0 reações 0 responsáveis Ver no GitHub

@jadenfix já está trabalhando nisso.

Desde 18/10/2026.

  • #285 de @jadenfix — aberto

Avaliação

Dificuldade
3/5
Tempo estimado
1-2 dias
Facilidade para iniciantes
72/100
Tipo de issue
Bug
Clareza
Claramente especificada
Status de atividade
Ativa
Stack de tecnologia
graphql, python
Domínio
backend

Direção de pesquisa

Start with graphql.execution.executor.Executor.collect_subfields and graphql.execution.collect_fields.FieldDetails; reproduce the stale id() reuse using the issue’s CPython script. Trace how _relevant_sub_fields entries are formed and retained, then update the cache design so entries cannot return another field’s subfields. Run the reproduction again, including the gc.collect() variant, and add or run regression coverage that verifies separate fields retain their requested selections.

Escrita pelo modelo de indexação a partir do texto da issue.

Descrição

In 3.3.0, Executor.collect_subfields memoizes per execution in self._relevant_sub_fields, keyed on the return type and the id() of each FieldDetails in the list:

key = (
    (return_type, id(field_details_list[0]))
    if len(field_details_list) == 1
    else (return_type, *map(id, field_details_list))
)

The cache keeps the collected subfields as values, but nothing keeps the FieldDetails of the key alive. Once a field has finished, its FieldDetails (a 3-tuple) can be freed. CPython then often reuses that address for the next tuple of the same size, and if that tuple is the FieldDetails of another field returning the same object type, the lookup hits the stale entry: the second field is answered with the first field's selection set.

We saw it intermittently (three times in a few hundred full runs) with queries like:

{
  items { ... on ItemPage { edges { node { key kind } } } }
  item(id: "…") { name }
}

where every edges.node came back with only name, the selection of item. A client can thus receive fields it did not ask for and miss the ones it did.

Deterministic reproduction on CPython (freeing one FieldDetails and building the next, which CPython's allocator places at the same address; a gc.collect() in between allocates and hides it):

from graphql import GraphQLField, GraphQLObjectType, GraphQLSchema, GraphQLString, parse
from graphql.execution.collect_fields import FieldDetails
from graphql.execution.executor import Executor

item = GraphQLObjectType("Item", {"key": GraphQLField(GraphQLString), "name": GraphQLField(GraphQLString)})
schema = GraphQLSchema(GraphQLObjectType("Query", {"a": GraphQLField(item), "b": GraphQLField(item)}))
document = parse("{ a { key } b { name } }")
executor = Executor.build(schema, document)
a_node, b_node = document.definitions[0].selection_set.selections

first = [FieldDetails(a_node, None)]
executor.collect_subfields(item, first)
address = id(first[0])
del first
second = [FieldDetails(b_node, None)]

assert id(second[0]) == address  # usually true on CPython
collected = executor.collect_subfields(item, second)
print(list(collected.grouped_field_set))  # ['key'] — expected ['name']

This looks related to #91. The comment above the key notes that list ids may be reused, but the same holds for the ids of the items once they are freed.

Possible fixes: keep the key's FieldDetails alive with the cached value (for example store (field_details_list, collected_fields) and compare by identity on hit), or key on the FieldNodes (id(fd.node)), which live as long as the document, plus whatever else of FieldDetails affects the collection (defer_usage, fragment_variable_values).

Our workaround meanwhile is an execution context whose collect_subfields keeps every field_details_list it is given referenced until the operation ends.

Linguagem predominante
Python
Estrelas
532
Forks
154
Merge médio
1d 9h
PRs com merge (30d)
6

Preparar o ambiente

  • Sem Dockerfile nem arquivo Docker Compose
  • Tem um modelo de pull request
  • Sem guia de contribuição

Primeiros passos

  1. Leia a issue inteira e depois o guia de contribuição do projeto.
  2. Comente na issue dizendo que vai assumir — evita que duas pessoas façam o mesmo trabalho.
  3. Faça um fork do repositório e trabalhe em uma branch.
  4. Abra um pull request que referencie o número da issue.

Mais de graphql-python/graphql-core

Todas as issues de graphql-python/graphql-core

Issues semelhantes

Mais issues de Python

Receba novas issues na sua caixa de entrada

Um resumo curto de issues do GitHub para quem está começando.