Skip to content

Commit d9f4c96

Browse files
committed
Fix leak of BuiltinMethodDescriptors
1 parent fbfac54 commit d9f4c96

File tree

3 files changed

+22
-7
lines changed

3 files changed

+22
-7
lines changed

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/function/BuiltinMethodDescriptor.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
import com.oracle.graal.python.nodes.function.builtins.PythonTernaryBuiltinNode;
5757
import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode;
5858
import com.oracle.truffle.api.CompilerAsserts;
59+
import com.oracle.truffle.api.dsl.GeneratedBy;
5960
import com.oracle.truffle.api.dsl.NodeFactory;
6061

6162
/**
@@ -251,6 +252,11 @@ public boolean forceSplitDirectCalls() {
251252

252253
private static BuiltinMethodDescriptor get(String name, NodeFactory<? extends PythonBuiltinBaseNode> factory, PythonBuiltinClassType type, Builtin builtinAnnotation) {
253254
CompilerAsserts.neverPartOfCompilation();
255+
if (factory.getClass().getAnnotation(GeneratedBy.class) == null) {
256+
// For non-generated factories, we do not assume that they are singletons, so we cannot
257+
// use them for our cache that must be bounded in size
258+
return null;
259+
}
254260
Class<? extends PythonBuiltinBaseNode> nodeClass = factory.getNodeClass();
255261
BuiltinMethodDescriptor result = null;
256262
if (PythonUnaryBuiltinNode.class.isAssignableFrom(nodeClass)) {
@@ -349,6 +355,10 @@ public final boolean equals(Object o) {
349355
return false;
350356
}
351357
BuiltinMethodDescriptor that = (BuiltinMethodDescriptor) o;
358+
// Rudimentary check of the assumption that builtin node factories are singletons, if we
359+
// create&cache BuiltinMethodDescriptors with non-singleton factories, we do not have the
360+
// guarantee that the cache size is bounded
361+
assert (factory.getNodeClass() == that.factory.getNodeClass()) == (factory == that.factory) : name;
352362
return factory == that.factory && type == that.type && name.equals(that.name);
353363
}
354364

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/tuple/StructSequence.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,13 +230,12 @@ public boolean equals(Object o) {
230230
if (this == o) {
231231
return true;
232232
}
233-
if (!(o instanceof BuiltinTypeDescriptor)) {
233+
if (!(o instanceof BuiltinTypeDescriptor that)) {
234234
return false;
235235
}
236236
if (!super.equals(o)) {
237237
return false;
238238
}
239-
BuiltinTypeDescriptor that = (BuiltinTypeDescriptor) o;
240239
return type == that.type;
241240
}
242241

@@ -258,6 +257,12 @@ public static void initType(PythonLanguage language, Object klass, Descriptor de
258257

259258
@TruffleBoundary
260259
public static void initType(PythonObjectSlowPathFactory factory, PythonLanguage language, Object klass, Descriptor desc) {
260+
// TODO: (GR-54701) some builtin nodes created here close over the "desc" object - it should
261+
// be their key for the call targets cache in language, but one can create new Descriptor
262+
// object at runtime via C API and those are context specific. There are no runtime data, so
263+
// we can cache Descriptor instances in language, but it should be weak cache and we should
264+
// make sure that once the type that is initialized is GC'ed, they can be GC'ed too.
265+
261266
assert IsSubtypeNode.getUncached().execute(klass, PythonBuiltinClassType.PTuple);
262267

263268
long flags = TypeNodes.GetTypeFlagsNode.executeUncached(klass);

mx.graalpython/mx_graalpython.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -411,13 +411,13 @@ def __str__(self):
411411
return
412412

413413
# test leaks with Python code only
414-
run_leak_launcher(["--code", "pass", ]),
415-
run_leak_launcher(["--repeat-and-check-size", "--code", "--null-stdout", "print('hello')"]),
414+
run_leak_launcher(["--code", "pass", ])
415+
run_leak_launcher(["--repeat-and-check-size", "250", "--null-stdout", "--code", "print('hello')"])
416416
# test leaks when some C module code is involved
417-
run_leak_launcher(["--code", 'import _testcapi, mmap, bz2; print(memoryview(b"").nbytes)']),
417+
run_leak_launcher(["--code", 'import _testcapi, mmap, bz2; print(memoryview(b"").nbytes)'])
418418
# test leaks with shared engine Python code only
419-
run_leak_launcher(["--shared-engine", "--code", "pass"]),
420-
run_leak_launcher(["--shared-engine", "--repeat-and-check-size", "--code", "--null-stdout", "print('hello')"]),
419+
run_leak_launcher(["--shared-engine", "--code", "pass"])
420+
# TODO: (GR-54727) run_leak_launcher(["--shared-engine", "--repeat-and-check-size", "250", "--null-stdout", "--code", "print('hello')"]),
421421
# test leaks with shared engine when some C module code is involved
422422
run_leak_launcher(["--shared-engine", "--code", 'import _testcapi, mmap, bz2; print(memoryview(b"").nbytes)'])
423423

0 commit comments

Comments
 (0)