`collect_subfields` memoization keyed on `id()` of `FieldDetails` that are not kept alive returns another field's subfields
Avaliação
- Dificuldade
- 3/5
- Tempo estimado
- 1-2 dias
- Facilidade para iniciantes
- 72/100
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
- Leia a issue inteira e depois o guia de contribuição do projeto.
- Comente na issue dizendo que vai assumir — evita que duas pessoas façam o mesmo trabalho.
- Faça um fork do repositório e trabalhe em uma branch.
- Abra um pull request que referencie o número da issue.
Mais de graphql-python/graphql-core
-
Memory leak: the shared `validation_aborted_error` keeps every traceback it is raised throughTalvez já em andamento @Fire162 assumiu há 2 dias. Aberta
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 78/100
graphql-python/graphql-core#287 ·
-
Dificuldade 1/5 Menos de uma hora Facilidade para iniciantes 35/100
graphql-python/graphql-core#278 ·
-
Dificuldade 3/5 1-2 dias Facilidade para iniciantes 45/100
graphql-python/graphql-core#257 ·
-
Dificuldade 3/5 1-2 dias Facilidade para iniciantes 35/100
graphql-python/graphql-core#223 · 1 comentário ·
-
documentation good first issue
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 35/100
graphql-python/graphql-core#220 · 4 comentários ·
Todas as issues de graphql-python/graphql-core
Issues semelhantes
-
The changelog workflow excludes otelbot but not dependabot, so every Dependabot pull request fails the changelog checkTalvez já em andamento @ocelotl assumiu hoje. Aberta
Dificuldade 1/5 Menos de uma hora Facilidade para iniciantes 82/100
open-telemetry/opentelemetry-python#5825 ·
Mantenedores costumam responder em até 2 dias
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 72/100
cuga-project/cuga-agent#1020 ·
Mantenedores costumam responder em até 2 dias
-
Dificuldade 1/5 Menos de uma hora Facilidade para iniciantes 70/100
microsoft/playwright#43640 ·
Mantenedores costumam responder em até 1 dia
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 84/100
stac-utils/stac-fastapi-elasticsearch-opensearch#965 ·
Mantenedores costumam responder em até 2 dias
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 70/100
Mantenedores costumam responder em até 1 dia