Skip to content

Commit c3f8459

Browse files
committed
[WIP] OOM
PullRequest: graalpython/3294
2 parents 59a107c + 0f24ef8 commit c3f8459

File tree

7 files changed

+169
-18
lines changed

7 files changed

+169
-18
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved.
2+
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
3+
#
4+
# The Universal Permissive License (UPL), Version 1.0
5+
#
6+
# Subject to the condition set forth below, permission is hereby granted to any
7+
# person obtaining a copy of this software, associated documentation and/or
8+
# data (collectively the "Software"), free of charge and under any and all
9+
# copyright rights in the Software, and any and all patent rights owned or
10+
# freely licensable by each licensor hereunder covering either (i) the
11+
# unmodified Software as contributed to or provided by such licensor, or (ii)
12+
# the Larger Works (as defined below), to deal in both
13+
#
14+
# (a) the Software, and
15+
#
16+
# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
17+
# one is included with the Software each a "Larger Work" to which the Software
18+
# is contributed by such licensors),
19+
#
20+
# without restriction, including without limitation the rights to copy, create
21+
# derivative works of, display, perform, and distribute the Software and make,
22+
# use, sell, offer for sale, import, export, have made, and have sold the
23+
# Software and the Larger Work(s), and to sublicense the foregoing rights on
24+
# either these or other terms.
25+
#
26+
# This license is subject to the following condition:
27+
#
28+
# The above copyright notice and either this complete permission notice or at a
29+
# minimum a reference to the UPL must be included in all copies or substantial
30+
# portions of the Software.
31+
#
32+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
33+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
34+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
35+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
36+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
37+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
38+
# SOFTWARE.
39+
40+
# This micro benchmark checks that the allocation of the bytes array, which is
41+
# internally done in try-catch(OutOfMemoryError) is virtualized in partially
42+
# evaluated code.
43+
#
44+
# Note: for ordinary host Java code compilation Graal never virtualizes or moves
45+
# allocations in try-catch(OutOfMemoryError)) for compatibility reasons.
46+
#
47+
# Note2: we want to catch OutOfMemoryError and translate it to Python MemoryError,
48+
# so that we can attach a precise location to it if possible, but we accept that
49+
# under some circumstances the compiler may move the allocation out of the try-catch
50+
# and we will catch it elsewhere (probably the catch-all in PBytecodeRootNode) and
51+
# attach imprecise location to it. Alternative is to force the allocation using
52+
# CompilerDirectives.ensureAllocatedHere, which would, however, prevent any virtualization,
53+
# which is deemed a price to high to pay for a precise location of MemoryError.
54+
# Possible future improvement: detect try-except(MemoryError) in Python and use
55+
# CompilerDirectives.ensureAllocatedHere only in such try blocks.
56+
57+
58+
def virtualize(ctor):
59+
b = ctor(5000000)
60+
return b is not None
61+
62+
63+
def measure(n):
64+
for i in range(1,n):
65+
virtualize(bytes)
66+
67+
68+
def __benchmark__(num=10000):
69+
return measure(num)
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved.
2+
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
3+
#
4+
# The Universal Permissive License (UPL), Version 1.0
5+
#
6+
# Subject to the condition set forth below, permission is hereby granted to any
7+
# person obtaining a copy of this software, associated documentation and/or
8+
# data (collectively the "Software"), free of charge and under any and all
9+
# copyright rights in the Software, and any and all patent rights owned or
10+
# freely licensable by each licensor hereunder covering either (i) the
11+
# unmodified Software as contributed to or provided by such licensor, or (ii)
12+
# the Larger Works (as defined below), to deal in both
13+
#
14+
# (a) the Software, and
15+
#
16+
# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
17+
# one is included with the Software each a "Larger Work" to which the Software
18+
# is contributed by such licensors),
19+
#
20+
# without restriction, including without limitation the rights to copy, create
21+
# derivative works of, display, perform, and distribute the Software and make,
22+
# use, sell, offer for sale, import, export, have made, and have sold the
23+
# Software and the Larger Work(s), and to sublicense the foregoing rights on
24+
# either these or other terms.
25+
#
26+
# This license is subject to the following condition:
27+
#
28+
# The above copyright notice and either this complete permission notice or at a
29+
# minimum a reference to the UPL must be included in all copies or substantial
30+
# portions of the Software.
31+
#
32+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
33+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
34+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
35+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
36+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
37+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
38+
# SOFTWARE.
39+
40+
41+
# Executed in a subprocess with small max heap size to exercise
42+
# real MemoryError caused by Java level OOM
43+
44+
def alloc():
45+
try: bytes(2140000000)
46+
except MemoryError as e:
47+
pass
48+
else:
49+
print("ERROR: MemoryError not caught!")
50+
import sys
51+
sys.exit(1)
52+
53+
54+
for i in range(1, 10):
55+
alloc()
56+
57+
print("DONE")

graalpython/com.oracle.graal.python.test/src/tests/test_exception.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
# Copyright (c) 2018, 2023, Oracle and/or its affiliates.
1+
# Copyright (c) 2018, 2024, Oracle and/or its affiliates.
22
# Copyright (C) 1996-2017 Python Software Foundation
33
#
44
# Licensed under the PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
5-
5+
import os
66
import unittest
77
import sys
88
import errno
99

10+
GRAALPYTHON = sys.implementation.name == "graalpy"
11+
1012
def fun0(test_obj, expected_error):
1113
typ, val, tb = sys.exc_info()
1214
test_obj.assertEqual(typ, expected_error)
@@ -673,3 +675,16 @@ def __getattr__(self, i): raise AttributeError1
673675
assert list(Iter2()) == []
674676
sentinel = object()
675677
assert getattr(Obj1(), 'does_not_exist', sentinel) is sentinel
678+
679+
680+
# There is no simple way to restrict memory for CPython process
681+
@unittest.skipUnless(GRAALPYTHON)
682+
def test_memory_error():
683+
import subprocess
684+
file = os.path.join(os.path.dirname(__file__), 'memoryerror.py')
685+
result = subprocess.check_output([sys.executable, '-S', '--experimental-options',
686+
'--engine.MultiTier=false', '--engine.BackgroundCompilation=false',
687+
'--engine.CompileImmediately', '--engine.CompileOnly=alloc',
688+
'--vm.Xmx400m', file], text=True)
689+
assert 'ERROR' not in result, result
690+
assert 'DONE' in result, result

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode/PBytecodeRootNode.java

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
*/
4141
package com.oracle.graal.python.nodes.bytecode;
4242

43-
import static com.oracle.graal.python.builtins.PythonBuiltinClassType.RecursionError;
4443
import static com.oracle.graal.python.builtins.PythonBuiltinClassType.SystemError;
4544
import static com.oracle.graal.python.builtins.PythonBuiltinClassType.ValueError;
4645
import static com.oracle.graal.python.builtins.PythonBuiltinClassType.ZeroDivisionError;
@@ -4645,12 +4644,7 @@ protected PException wrapJavaExceptionIfApplicable(Throwable e) {
46454644
if (PythonLanguage.get(this).getEngineOption(PythonOptions.CatchAllExceptions) && (e instanceof Exception || e instanceof AssertionError)) {
46464645
return ExceptionUtils.wrapJavaException(e, this, factory.createBaseException(SystemError, ErrorMessages.M, new Object[]{e}));
46474646
}
4648-
if (e instanceof StackOverflowError) {
4649-
CompilerDirectives.transferToInterpreterAndInvalidate();
4650-
PythonContext.get(this).reacquireGilAfterStackOverflow();
4651-
return ExceptionUtils.wrapJavaException(e, this, factory.createBaseException(RecursionError, ErrorMessages.MAXIMUM_RECURSION_DEPTH_EXCEEDED, new Object[]{}));
4652-
}
4653-
return null;
4647+
return ExceptionUtils.wrapJavaExceptionIfApplicable(this, e, factory);
46544648
}
46554649

46564650
@ExplodeLoop

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/exception/TopLevelExceptionHandler.java

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
*/
4141
package com.oracle.graal.python.nodes.exception;
4242

43-
import static com.oracle.graal.python.builtins.PythonBuiltinClassType.RecursionError;
4443
import static com.oracle.graal.python.builtins.modules.io.IONodes.T_WRITE;
4544
import static com.oracle.graal.python.nodes.BuiltinNames.T_SYS;
4645
import static com.oracle.graal.python.runtime.exception.ExceptionUtils.printToStdErr;
@@ -58,7 +57,6 @@
5857
import com.oracle.graal.python.lib.PyObjectCallMethodObjArgs;
5958
import com.oracle.graal.python.lib.PyObjectStrAsObjectNode;
6059
import com.oracle.graal.python.nodes.BuiltinNames;
61-
import com.oracle.graal.python.nodes.ErrorMessages;
6260
import com.oracle.graal.python.nodes.bytecode.PBytecodeRootNode;
6361
import com.oracle.graal.python.nodes.object.BuiltinClassProfiles.IsBuiltinClassProfile;
6462
import com.oracle.graal.python.nodes.object.GetClassNode;
@@ -73,6 +71,7 @@
7371
import com.oracle.graal.python.runtime.exception.ExceptionUtils;
7472
import com.oracle.graal.python.runtime.exception.PException;
7573
import com.oracle.graal.python.runtime.exception.PythonExitException;
74+
import com.oracle.graal.python.runtime.object.PythonObjectFactory;
7675
import com.oracle.graal.python.util.PythonUtils;
7776
import com.oracle.truffle.api.CompilerDirectives;
7877
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
@@ -151,13 +150,6 @@ public Object execute(VirtualFrame frame) {
151150
return handleChildContextExit(managedException);
152151
}
153152
throw handlePythonException(e);
154-
} catch (StackOverflowError e) {
155-
CompilerDirectives.transferToInterpreter();
156-
PythonContext context = getContext();
157-
context.reacquireGilAfterStackOverflow();
158-
PBaseException newException = context.factory().createBaseException(RecursionError, ErrorMessages.MAXIMUM_RECURSION_DEPTH_EXCEEDED, new Object[]{});
159-
PException pe = ExceptionUtils.wrapJavaException(e, this, newException);
160-
throw handlePythonException(pe);
161153
} catch (ThreadDeath e) {
162154
// do not handle, result of TruffleContext.closeCancelled()
163155
throw e;
@@ -218,6 +210,10 @@ private static boolean isSystemExit(PBaseException pythonException) {
218210

219211
@TruffleBoundary
220212
private void handleJavaException(Throwable e) {
213+
PException pe = ExceptionUtils.wrapJavaExceptionIfApplicable(this, e, PythonObjectFactory.getUncached());
214+
if (pe != null) {
215+
throw handlePythonException(pe);
216+
}
221217
try {
222218
boolean exitException = InteropLibrary.getUncached().isException(e) && InteropLibrary.getUncached().getExceptionType(e) == ExceptionType.EXIT;
223219
if (!exitException) {

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/exception/ExceptionUtils.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
*/
4141
package com.oracle.graal.python.runtime.exception;
4242

43+
import static com.oracle.graal.python.builtins.PythonBuiltinClassType.MemoryError;
44+
import static com.oracle.graal.python.builtins.PythonBuiltinClassType.RecursionError;
4345
import static com.oracle.graal.python.nodes.BuiltinNames.T_SYS;
4446

4547
import java.io.IOException;
@@ -54,15 +56,18 @@
5456
import com.oracle.graal.python.builtins.objects.function.PKeyword;
5557
import com.oracle.graal.python.builtins.objects.traceback.LazyTraceback;
5658
import com.oracle.graal.python.nodes.BuiltinNames;
59+
import com.oracle.graal.python.nodes.ErrorMessages;
5760
import com.oracle.graal.python.nodes.bytecode.FrameInfo;
5861
import com.oracle.graal.python.nodes.call.CallNode;
5962
import com.oracle.graal.python.nodes.exception.TopLevelExceptionHandler;
6063
import com.oracle.graal.python.nodes.function.BuiltinFunctionRootNode;
6164
import com.oracle.graal.python.nodes.object.GetClassNode;
6265
import com.oracle.graal.python.runtime.PythonContext;
66+
import com.oracle.graal.python.runtime.object.PythonObjectFactory;
6367
import com.oracle.truffle.api.CompilerAsserts;
6468
import com.oracle.truffle.api.CompilerDirectives;
6569
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
70+
import com.oracle.truffle.api.HostCompilerDirectives.InliningCutoff;
6671
import com.oracle.truffle.api.RootCallTarget;
6772
import com.oracle.truffle.api.Truffle;
6873
import com.oracle.truffle.api.TruffleStackTrace;
@@ -263,4 +268,18 @@ public static void printJavaStackTrace(PException e) {
263268
public static PException wrapJavaException(Throwable e, Node node, PBaseException pythonException) {
264269
return PException.fromObject(pythonException, node, e);
265270
}
271+
272+
@InliningCutoff
273+
public static PException wrapJavaExceptionIfApplicable(Node location, Throwable e, PythonObjectFactory factory) {
274+
if (e instanceof StackOverflowError) {
275+
CompilerDirectives.transferToInterpreterAndInvalidate();
276+
PythonContext.get(null).reacquireGilAfterStackOverflow();
277+
return ExceptionUtils.wrapJavaException(e, location, factory.createBaseException(RecursionError, ErrorMessages.MAXIMUM_RECURSION_DEPTH_EXCEEDED, new Object[]{}));
278+
}
279+
if (e instanceof OutOfMemoryError) {
280+
CompilerDirectives.transferToInterpreterAndInvalidate();
281+
return ExceptionUtils.wrapJavaException(e, location, factory.createBaseException(MemoryError));
282+
}
283+
return null;
284+
}
266285
}

mx.graalpython/mx_graalpython_bench_param.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@
116116
'regexp-universal-match': ITER_10,
117117
'regexp-char-class-match': ITER_10,
118118
'regexp-char-class-no-match': ITER_10,
119+
'virtualize-in-try-catch-oom': ITER_10,
119120
}
120121

121122
MICRO_BENCHMARKS_SMALL = {

0 commit comments

Comments
 (0)