`collect_subfields` memoization keyed on `id()` of `FieldDetails` that are not kept alive returns another field's subfields
Bewertung
- Schwierigkeit
- 3/5
- Geschätzter Aufwand
- 1-2 Tage
- Anfängerfreundlichkeit
- 72/100
Rechercherichtung
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.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Beschreibung
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.
- Vorherrschende Sprache
- Python
- Sterne
- 532
- Forks
- 154
- Ø Merge
- 1 T. 9 Std.
- Gemergte PRs (30 T.)
- 6
Entwicklungsumgebung
- Kein Dockerfile und keine Docker-Compose-Datei
- Hat eine Pull-Request-Vorlage
- Kein Beitragsleitfaden
Erste Schritte
- Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
- Forken Sie das Repository und arbeiten Sie in einem Branch.
- Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.
Mehr aus graphql-python/graphql-core
-
Memory leak: the shared `validation_aborted_error` keeps every traceback it is raised throughEvtl. vergeben @Fire162 hat das vor 1 Tag übernommen. Offen
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 78/100
graphql-python/graphql-core#287 ·
-
Schwierigkeit 1/5 Unter einer Stunde Anfängerfreundlichkeit 35/100
graphql-python/graphql-core#278 ·
-
Schwierigkeit 3/5 1-2 Tage Anfängerfreundlichkeit 45/100
graphql-python/graphql-core#257 ·
-
Schwierigkeit 3/5 1-2 Tage Anfängerfreundlichkeit 35/100
graphql-python/graphql-core#223 · 1 Kommentar ·
-
documentation good first issue
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 35/100
graphql-python/graphql-core#220 · 4 Kommentare ·
Alle Issues in graphql-python/graphql-core
Ähnliche Issues
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 68/100
Arekkazu/sgpmp-backend#744 ·
Maintainer antworten meist innerhalb von 1 Tag
-
agent/reviewer
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 78/100
projectbluefin/utah-packages#634 ·
Maintainer antworten meist innerhalb von 1 Tag
-
enhancement good first issue help wanted
Schwierigkeit 2/5 1-2 Tage Anfängerfreundlichkeit 78/100
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 68/100
home-assistant/core#188613 ·
Maintainer antworten meist innerhalb von 1 Tag
-
WebsiteOffengood first issue
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 91/100
TanbirRamim/open-source-radar#314 ·
Maintainer antworten meist innerhalb von 1 Tag