Skip to content

Commit 7f652d2

Browse files
committed
[GR-31824] [GR-31872] Bulk release shared finalizers rather than queueing many shared actions
PullRequest: graalpython/1828
2 parents 2048525 + 6b668fa commit 7f652d2

File tree

10 files changed

+131
-25
lines changed

10 files changed

+131
-25
lines changed

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/GraalPythonModuleBuiltins.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import static com.oracle.graal.python.runtime.exception.PythonErrorType.ImportError;
4646
import static com.oracle.graal.python.runtime.exception.PythonErrorType.NotImplementedError;
4747

48+
import java.io.IOException;
4849
import java.io.PrintWriter;
4950
import java.util.List;
5051
import java.util.logging.Level;
@@ -196,6 +197,13 @@ public void postInitialize(Python3Core core) {
196197
PList executableList = PythonObjectFactory.getUncached().createList(arr);
197198
mod.setAttribute("executable_list", executableList);
198199
mod.setAttribute("ForeignType", core.lookupType(PythonBuiltinClassType.ForeignObject));
200+
201+
if (!context.getOption(PythonOptions.EnableDebuggingBuiltins)) {
202+
mod.setAttribute("dump_truffle_ast", PNone.NO_VALUE);
203+
mod.setAttribute("tdebug", PNone.NO_VALUE);
204+
mod.setAttribute("set_storage_strategy", PNone.NO_VALUE);
205+
mod.setAttribute("dump_heap", PNone.NO_VALUE);
206+
}
199207
}
200208

201209
private static Object[] convertToObjectArray(String[] arr) {
@@ -683,4 +691,21 @@ Object doIt(Object value) {
683691
}
684692
}
685693

694+
@Builtin(name = "dump_heap", minNumOfPositionalArgs = 0)
695+
@GenerateNodeFactory
696+
abstract static class DumpHeapNode extends PythonBuiltinNode {
697+
@Specialization
698+
String doit(VirtualFrame frame, @CachedContext(PythonLanguage.class) PythonContext context) {
699+
TruffleFile tempFile;
700+
try {
701+
tempFile = context.getEnv().createTempFile(context.getEnv().getCurrentWorkingDirectory(), "graalpython", ".hprof");
702+
tempFile.delete();
703+
} catch (IOException e) {
704+
throw raiseOSError(frame, e);
705+
}
706+
PythonUtils.dumpHeap(tempFile.getPath());
707+
return tempFile.getPath();
708+
}
709+
}
710+
686711
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/ThreadModuleBuiltins.java

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,13 @@
4949
import com.oracle.graal.python.PythonLanguage;
5050
import com.oracle.graal.python.builtins.Builtin;
5151
import com.oracle.graal.python.builtins.CoreFunctions;
52+
import com.oracle.graal.python.builtins.Python3Core;
5253
import com.oracle.graal.python.builtins.PythonBuiltinClassType;
5354
import com.oracle.graal.python.builtins.PythonBuiltins;
5455
import com.oracle.graal.python.builtins.objects.PNone;
5556
import com.oracle.graal.python.builtins.objects.exception.PBaseException;
5657
import com.oracle.graal.python.builtins.objects.function.PKeyword;
58+
import com.oracle.graal.python.builtins.objects.module.PythonModule;
5759
import com.oracle.graal.python.builtins.objects.thread.PLock;
5860
import com.oracle.graal.python.builtins.objects.thread.PRLock;
5961
import com.oracle.graal.python.builtins.objects.thread.PThread;
@@ -67,21 +69,26 @@
6769
import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode;
6870
import com.oracle.graal.python.runtime.GilNode;
6971
import com.oracle.graal.python.runtime.PythonContext;
70-
import com.oracle.graal.python.builtins.Python3Core;
7172
import com.oracle.graal.python.runtime.exception.PException;
7273
import com.oracle.graal.python.runtime.exception.PythonThreadKillException;
7374
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
75+
import com.oracle.truffle.api.CompilerDirectives;
7476
import com.oracle.truffle.api.TruffleLanguage;
7577
import com.oracle.truffle.api.dsl.Cached;
7678
import com.oracle.truffle.api.dsl.CachedContext;
7779
import com.oracle.truffle.api.dsl.GenerateNodeFactory;
7880
import com.oracle.truffle.api.dsl.NodeFactory;
7981
import com.oracle.truffle.api.dsl.Specialization;
8082
import com.oracle.truffle.api.frame.VirtualFrame;
83+
import com.oracle.truffle.api.nodes.UnexpectedResultException;
84+
import com.oracle.truffle.api.object.DynamicObjectLibrary;
85+
import com.oracle.truffle.api.object.HiddenKey;
8186
import com.oracle.truffle.api.profiles.ConditionProfile;
8287

8388
@CoreFunctions(defineModule = "_thread")
8489
public class ThreadModuleBuiltins extends PythonBuiltins {
90+
private static final HiddenKey THREAD_COUNT = new HiddenKey("thread_count");
91+
8592
@Override
8693
protected List<? extends NodeFactory<? extends PythonBuiltinBaseNode>> getNodeFactories() {
8794
return ThreadModuleBuiltinsFactory.getFactories();
@@ -91,6 +98,7 @@ protected List<? extends NodeFactory<? extends PythonBuiltinBaseNode>> getNodeFa
9198
public void initialize(Python3Core core) {
9299
builtinConstants.put("error", core.lookupType(PythonBuiltinClassType.RuntimeError));
93100
builtinConstants.put("TIMEOUT_MAX", TIMEOUT_MAX);
101+
builtinConstants.put(THREAD_COUNT, 0);
94102
super.initialize(core);
95103
}
96104

@@ -140,13 +148,17 @@ public static long getId() {
140148
}
141149
}
142150

143-
@Builtin(name = "_count", minNumOfPositionalArgs = 0)
151+
@Builtin(name = "_count", minNumOfPositionalArgs = 1, declaresExplicitSelf = true)
144152
@GenerateNodeFactory
145-
abstract static class GetThreadCountNode extends PythonBuiltinNode {
153+
abstract static class GetThreadCountNode extends PythonUnaryBuiltinNode {
146154
@Specialization
147155
@TruffleBoundary
148-
long getCount() {
149-
return getContext().getThreadGroup().activeCount();
156+
long getCount(PythonModule self) {
157+
try {
158+
return DynamicObjectLibrary.getUncached().getIntOrDefault(self, THREAD_COUNT, 0);
159+
} catch (UnexpectedResultException e) {
160+
throw CompilerDirectives.shouldNotReachHere();
161+
}
150162
}
151163
}
152164

@@ -189,6 +201,7 @@ long start(VirtualFrame frame, Object cls, Object callable, Object args, Object
189201
@Cached ExpandKeywordStarargsNode getKwArgsNode) {
190202
PythonContext context = getContext();
191203
TruffleLanguage.Env env = context.getEnv();
204+
PythonModule threadModule = context.getCore().lookupBuiltinModule("_thread");
192205

193206
// TODO: python thread stack size != java thread stack size
194207
// ignore setting the stack size for the moment
@@ -197,11 +210,31 @@ long start(VirtualFrame frame, Object cls, Object callable, Object args, Object
197210
PKeyword[] keywords = getKwArgsNode.execute(kwargs);
198211

199212
try (GilNode.UncachedAcquire gil = GilNode.uncachedAcquire()) {
200-
// n.b.: It is important to pass 'null' frame here because each thread has it's
201-
// own stack and if we would pass the current frame, this would be connected as
202-
// a caller which is incorrect. However, the thread-local 'topframeref' is
203-
// initialized with EMPTY which will be picked up.
204-
callNode.execute(null, callable, arguments, keywords);
213+
// the increment is protected by the gil
214+
DynamicObjectLibrary lib = DynamicObjectLibrary.getUncached();
215+
int curCount = 0;
216+
try {
217+
curCount = lib.getIntOrDefault(threadModule, THREAD_COUNT, 0);
218+
} catch (UnexpectedResultException ure) {
219+
throw CompilerDirectives.shouldNotReachHere();
220+
}
221+
lib.putInt(threadModule, THREAD_COUNT, curCount + 1);
222+
try {
223+
// n.b.: It is important to pass 'null' frame here because each thread has
224+
// it's own stack and if we would pass the current frame, this would be
225+
// connected as a caller which is incorrect. However, the thread-local
226+
// 'topframeref' is initialized with EMPTY which will be picked up.
227+
callNode.execute(null, callable, arguments, keywords);
228+
} finally {
229+
// the catch blocks run ofter the gil is released, so we decrement the
230+
// threadcount here while still protected by the gil
231+
try {
232+
curCount = lib.getIntOrDefault(threadModule, THREAD_COUNT, 1);
233+
} catch (UnexpectedResultException ure) {
234+
throw CompilerDirectives.shouldNotReachHere();
235+
}
236+
lib.putInt(threadModule, THREAD_COUNT, curCount - 1);
237+
}
205238
} catch (PythonThreadKillException e) {
206239
return;
207240
} catch (PException e) {

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/call/special/CallBinaryMethodNode.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ Object callMethodSingleContext(VirtualFrame frame, @SuppressWarnings("unused") P
347347
@Specialization(guards = {"func == cachedFunc", "builtinNode != null", "takesSelfArg",
348348
"frame != null || unusedFrame"}, limit = "getCallSiteInlineCacheMaxDepth()", assumptions = "singleContextAssumption()")
349349
Object callSelfMethodSingleContext(VirtualFrame frame, @SuppressWarnings("unused") PBuiltinMethod func, Object arg1, Object arg2,
350-
@SuppressWarnings("unused") @Cached("func") PBuiltinMethod cachedFunc,
350+
@SuppressWarnings("unused") @Cached(value = "func", weak = true) PBuiltinMethod cachedFunc,
351351
@SuppressWarnings("unused") @Cached("takesSelfArg(func)") boolean takesSelfArg,
352352
@Cached("getTernary(frame, func.getFunction())") PythonTernaryBuiltinNode builtinNode,
353353
@SuppressWarnings("unused") @Cached("frameIsUnused(builtinNode)") boolean unusedFrame) {

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/call/special/CallQuaternaryMethodNode.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ Object call(VirtualFrame frame, @SuppressWarnings("unused") PBuiltinFunction fun
8181
@Specialization(guards = {"func == cachedFunc", "builtinNode != null", "!takesSelfArg",
8282
"frame != null || unusedFrame"}, limit = "getCallSiteInlineCacheMaxDepth()", assumptions = "singleContextAssumption()")
8383
Object callMethodSingle(VirtualFrame frame, @SuppressWarnings("unused") PBuiltinMethod func, Object arg1, Object arg2, Object arg3, Object arg4,
84-
@SuppressWarnings("unused") @Cached("func") PBuiltinMethod cachedFunc,
84+
@SuppressWarnings("unused") @Cached(value = "func", weak = true) PBuiltinMethod cachedFunc,
8585
@SuppressWarnings("unused") @Cached("takesSelfArg(func)") boolean takesSelfArg,
8686
@Cached("getQuaternary(frame, func.getFunction())") PythonQuaternaryBuiltinNode builtinNode,
8787
@SuppressWarnings("unused") @Cached("frameIsUnused(builtinNode)") boolean unusedFrame) {

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/call/special/CallTernaryMethodNode.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ static Object doBuiltinMethodCtCached(VirtualFrame frame, @SuppressWarnings("unu
211211
@Specialization(guards = {"func == cachedFunc", "builtinNode != null", "takesSelfArg",
212212
"frame != null || unusedFrame"}, limit = "getCallSiteInlineCacheMaxDepth()", assumptions = "singleContextAssumption()")
213213
static Object callSelfMethodSingleContext(VirtualFrame frame, @SuppressWarnings("unused") PBuiltinMethod func, Object arg1, Object arg2, Object arg3,
214-
@SuppressWarnings("unused") @Cached("func") PBuiltinMethod cachedFunc,
214+
@SuppressWarnings("unused") @Cached(value = "func", weak = true) PBuiltinMethod cachedFunc,
215215
@SuppressWarnings("unused") @Cached("takesSelfArg(func)") boolean takesSelfArg,
216216
@Cached("getQuaternary(frame, func.getFunction())") PythonQuaternaryBuiltinNode builtinNode,
217217
@SuppressWarnings("unused") @Cached("frameIsUnused(builtinNode)") boolean unusedFrame) {

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/call/special/CallUnaryMethodNode.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ Object callMethodSingleContext(VirtualFrame frame, @SuppressWarnings("unused") P
225225
@Specialization(guards = {"func == cachedFunc", "builtinNode != null", "takesSelfArg",
226226
"frame != null || unusedFrame"}, limit = "getCallSiteInlineCacheMaxDepth()", assumptions = "singleContextAssumption()")
227227
Object callSelfMethodSingleContext(VirtualFrame frame, @SuppressWarnings("unused") PBuiltinMethod func, Object arg,
228-
@SuppressWarnings("unused") @Cached("func") PBuiltinMethod cachedFunc,
228+
@SuppressWarnings("unused") @Cached(value = "func", weak = true) PBuiltinMethod cachedFunc,
229229
@SuppressWarnings("unused") @Cached("takesSelfArg(func)") boolean takesSelfArg,
230230
@Cached("getBinary(frame, func.getFunction())") PythonBinaryBuiltinNode builtinNode,
231231
@SuppressWarnings("unused") @Cached("frameIsUnused(builtinNode)") boolean unusedFrame) {

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/AsyncHandler.java

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import java.lang.ref.Reference;
4545
import java.lang.ref.ReferenceQueue;
4646
import java.lang.ref.WeakReference;
47+
import java.util.ArrayList;
4748
import java.util.Arrays;
4849
import java.util.concurrent.ConcurrentHashMap;
4950
import java.util.concurrent.ConcurrentMap;
@@ -363,10 +364,10 @@ public final void markReleased() {
363364
* This implements the proper way to free the allocated resources associated with the
364365
* reference.
365366
*/
366-
public abstract AsyncHandler.AsyncAction release();
367+
public abstract AsyncAction release();
367368
}
368369

369-
static class SharedFinalizerErrorCallback implements AsyncHandler.AsyncAction {
370+
static class SharedFinalizerErrorCallback implements AsyncAction {
370371

371372
private final Exception exception;
372373
private final FinalizableReference referece; // problematic reference
@@ -382,6 +383,24 @@ public void execute(PythonContext context) {
382383
}
383384
}
384385

386+
private static final class AsyncActionsList implements AsyncAction {
387+
private final AsyncAction[] array;
388+
389+
public AsyncActionsList(AsyncAction[] array) {
390+
this.array = array;
391+
}
392+
393+
public void execute(PythonContext context) {
394+
for (AsyncAction action : array) {
395+
try {
396+
action.execute(context);
397+
} catch (RuntimeException e) {
398+
ExceptionUtils.printPythonLikeStackTrace(e);
399+
}
400+
}
401+
}
402+
}
403+
385404
/**
386405
* We register the Async action once on the first encounter of a creation of
387406
* {@link FinalizableReference}. This will reduce unnecessary Async thread load when there
@@ -395,17 +414,26 @@ public void registerAsyncAction() {
395414
} catch (InterruptedException e) {
396415
Thread.currentThread().interrupt();
397416
}
398-
if (reference instanceof FinalizableReference) {
399-
FinalizableReference object = (FinalizableReference) reference;
400-
try {
401-
liveReferencesSet.remove(object);
402-
if (object.isReleased()) {
403-
return null;
417+
ArrayList<AsyncAction> actions = new ArrayList<>();
418+
do {
419+
if (reference instanceof FinalizableReference) {
420+
FinalizableReference object = (FinalizableReference) reference;
421+
try {
422+
liveReferencesSet.remove(object);
423+
if (!object.isReleased()) {
424+
AsyncAction action = object.release();
425+
if (action != null) {
426+
actions.add(action);
427+
}
428+
}
429+
} catch (Exception e) {
430+
actions.add(new SharedFinalizerErrorCallback(object, e));
404431
}
405-
return object.release();
406-
} catch (Exception e) {
407-
return new SharedFinalizerErrorCallback(object, e);
408432
}
433+
reference = queue.poll();
434+
} while (reference != null);
435+
if (!actions.isEmpty()) {
436+
return new AsyncActionsList(actions.toArray(new AsyncAction[0]));
409437
}
410438
return null;
411439
});

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonOptions.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,9 @@ private PythonOptions() {
244244
@Option(category = OptionCategory.EXPERT, help = "Set by the launcher to true (false means that GraalPython is being embedded in an application).") //
245245
public static final OptionKey<Boolean> RunViaLauncher = new OptionKey<>(false);
246246

247+
@Option(category = OptionCategory.EXPERT, help = "Enable built-in functions on the __graalpython__ module that are useful for debugging.") //
248+
public static final OptionKey<Boolean> EnableDebuggingBuiltins = new OptionKey<>(false);
249+
247250
public static final OptionDescriptors DESCRIPTORS = new PythonOptionsOptionDescriptors();
248251

249252
@CompilationFinal(dimensions = 1) private static final OptionKey<?>[] ENGINE_OPTION_KEYS;

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/util/PythonUtils.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,22 @@ public static void forceFullGC() {
297297
Runtime.getRuntime().freeMemory();
298298
}
299299

300+
@TruffleBoundary
301+
public static void dumpHeap(String path) {
302+
if (SERVER != null) {
303+
try {
304+
Class<?> mxBeanClass = Class.forName("com.sun.management.HotSpotDiagnosticMXBean");
305+
Object mxBean = ManagementFactory.newPlatformMXBeanProxy(SERVER,
306+
"com.sun.management:type=HotSpotDiagnostic",
307+
mxBeanClass);
308+
mxBeanClass.getMethod("dumpHeap", String.class, boolean.class).invoke(mxBean, path, true);
309+
} catch (Throwable e) {
310+
System.err.println("Cannot dump heap: " + e.getMessage());
311+
e.printStackTrace();
312+
}
313+
}
314+
}
315+
300316
/**
301317
* Get the existing or create a new {@link CallTarget} for the provided root node.
302318
*/

mx.graalpython/mx_graalpython.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,7 @@ def run_python_unittests(python_binary, args=None, paths=None, aot_compatible=Fa
593593

594594
args = args or []
595595
args = ["--experimental-options=true",
596+
"--python.EnableDebuggingBuiltins",
596597
"--python.CatchAllExceptions=true"] + args
597598
exclude = exclude or []
598599
if env is None:

0 commit comments

Comments
 (0)