Author: Carl Friedrich Bolz <[email protected]>
Branch:
Changeset: r86065:16e118636641
Date: 2016-08-07 14:35 +0200
http://bitbucket.org/pypy/pypy/changeset/16e118636641/
Log: merge resource_warning:
adds a new commandline option -X track-resources that will produce a
ResourceWarning when the GC closes a file or socket. The traceback
for the place where the file or socket was allocated is given as
well
diff --git a/pypy/doc/cpython_differences.rst b/pypy/doc/cpython_differences.rst
--- a/pypy/doc/cpython_differences.rst
+++ b/pypy/doc/cpython_differences.rst
@@ -99,17 +99,24 @@
The garbage collectors used or implemented by PyPy are not based on
reference counting, so the objects are not freed instantly when they are no
-longer reachable. The most obvious effect of this is that files are not
+longer reachable. The most obvious effect of this is that files (and sockets,
etc) are not
promptly closed when they go out of scope. For files that are opened for
writing, data can be left sitting in their output buffers for a while, making
the on-disk file appear empty or truncated. Moreover, you might reach your
OS's limit on the number of concurrently opened files.
-Fixing this is essentially impossible without forcing a
+If you are debugging a case where a file in your program is not closed
+properly, you can use the ``-X track-resources`` command line option. If it is
+given, a ``ResourceWarning`` is produced for every file and socket that the
+garbage collector closes. The warning will contain the stack trace of the
+position where the file or socket was created, to make it easier to see which
+parts of the program don't close files explicitly.
+
+Fixing this difference to CPython is essentially impossible without forcing a
reference-counting approach to garbage collection. The effect that you
get in CPython has clearly been described as a side-effect of the
implementation and not a language design decision: programs relying on
-this are basically bogus. It would anyway be insane to try to enforce
+this are basically bogus. It would a too strong restriction to try to enforce
CPython's behavior in a language spec, given that it has no chance to be
adopted by Jython or IronPython (or any other port of Python to Java or
.NET).
@@ -134,7 +141,7 @@
Here are some more technical details. This issue affects the precise
time at which ``__del__`` methods are called, which
-is not reliable in PyPy (nor Jython nor IronPython). It also means that
+is not reliable or timely in PyPy (nor Jython nor IronPython). It also means
that
**weak references** may stay alive for a bit longer than expected. This
makes "weak proxies" (as returned by ``weakref.proxy()``) somewhat less
useful: they will appear to stay alive for a bit longer in PyPy, and
diff --git a/pypy/doc/man/pypy.1.rst b/pypy/doc/man/pypy.1.rst
--- a/pypy/doc/man/pypy.1.rst
+++ b/pypy/doc/man/pypy.1.rst
@@ -51,6 +51,10 @@
-B
Disable writing bytecode (``.pyc``) files.
+-X track-resources
+ Produce a ``ResourceWarning`` whenever a file or socket is closed by the
+ garbage collector.
+
--version
Print the PyPy version.
diff --git a/pypy/doc/whatsnew-head.rst b/pypy/doc/whatsnew-head.rst
--- a/pypy/doc/whatsnew-head.rst
+++ b/pypy/doc/whatsnew-head.rst
@@ -119,3 +119,8 @@
``ffi.from_buffer(string)`` in CFFI. Additionally, and most
importantly, CFFI calls that take directly a string as argument don't
copy the string any more---this is like CFFI on CPython.
+
+.. branch: resource_warning
+
+Add a new command line option -X track-resources which will produce
+ResourceWarnings when the GC closes unclosed files and sockets.
diff --git a/pypy/interpreter/app_main.py b/pypy/interpreter/app_main.py
--- a/pypy/interpreter/app_main.py
+++ b/pypy/interpreter/app_main.py
@@ -24,11 +24,15 @@
-V : print the Python version number and exit (also --version)
-W arg : warning control; arg is action:message:category:module:lineno
also PYTHONWARNINGS=arg
+-X arg : set implementation-specific option
file : program read from script file
- : program read from stdin (default; interactive mode if a tty)
arg ...: arguments passed to program in sys.argv[1:]
+
PyPy options and arguments:
--info : print translation information about this PyPy executable
+-X track-resources : track the creation of files and sockets and display
+ a warning if they are not closed explicitly
"""
# Missing vs CPython: PYTHONHOME, PYTHONCASEOK
USAGE2 = """
@@ -229,6 +233,14 @@
import pypyjit
pypyjit.set_param(jitparam)
+def set_runtime_options(options, Xparam, *args):
+ if Xparam == 'track-resources':
+ sys.pypy_set_track_resources(True)
+ else:
+ print >> sys.stderr, 'usage: %s -X [options]' % (get_sys_executable(),)
+ print >> sys.stderr, '[options] can be: track-resources'
+ raise SystemExit
+
class CommandLineError(Exception):
pass
@@ -404,6 +416,7 @@
'--info': (print_info, None),
'--jit': (set_jit_option, Ellipsis),
'-funroll-loops': (funroll_loops, None),
+ '-X': (set_runtime_options, Ellipsis),
'--': (end_options, None),
}
diff --git a/pypy/interpreter/baseobjspace.py b/pypy/interpreter/baseobjspace.py
--- a/pypy/interpreter/baseobjspace.py
+++ b/pypy/interpreter/baseobjspace.py
@@ -1764,6 +1764,40 @@
_warnings.warn(msg, warningcls, stacklevel=stacklevel)
""")
+ def resource_warning(self, w_msg, w_tb):
+ self.appexec([w_msg, w_tb],
+ """(msg, tb):
+ import sys
+ print >> sys.stderr, msg
+ if tb:
+ print >> sys.stderr, "Created at (most recent call last):"
+ print >> sys.stderr, tb
+ """)
+
+ def format_traceback(self):
+ # we need to disable track_resources before calling the traceback
+ # module. Else, it tries to open more files to format the traceback,
+ # the file constructor will call space.format_traceback etc., in an
+ # inifite recursion
+ flag = self.sys.track_resources
+ self.sys.track_resources = False
+ try:
+ return self.appexec([],
+ """():
+ import sys, traceback
+ # the "1" is because we don't want to show THIS code
+ # object in the traceback
+ try:
+ f = sys._getframe(1)
+ except ValueError:
+ # this happens if you call format_traceback at the very
beginning
+ # of startup, when there is no bottom code object
+ return '<no stacktrace available>'
+ return "".join(traceback.format_stack(f))
+ """)
+ finally:
+ self.sys.track_resources = flag
+
class AppExecCache(SpaceCache):
def build(cache, source):
diff --git a/pypy/interpreter/test/test_app_main.py
b/pypy/interpreter/test/test_app_main.py
--- a/pypy/interpreter/test/test_app_main.py
+++ b/pypy/interpreter/test/test_app_main.py
@@ -220,6 +220,13 @@
expected = {"no_user_site": True}
self.check(['-c', 'pass'], {}, sys_argv=['-c'], run_command='pass',
**expected)
+ def test_track_resources(self, monkeypatch):
+ myflag = [False]
+ def pypy_set_track_resources(flag):
+ myflag[0] = flag
+ monkeypatch.setattr(sys, 'pypy_set_track_resources',
pypy_set_track_resources, raising=False)
+ self.check(['-X', 'track-resources'], {}, sys_argv=[''],
run_stdin=True)
+ assert myflag[0] == True
class TestInteraction:
"""
@@ -1074,4 +1081,3 @@
# assert it did not crash
finally:
sys.path[:] = old_sys_path
-
diff --git a/pypy/interpreter/test/test_objspace.py
b/pypy/interpreter/test/test_objspace.py
--- a/pypy/interpreter/test/test_objspace.py
+++ b/pypy/interpreter/test/test_objspace.py
@@ -427,3 +427,28 @@
space.finish()
# assert that we reach this point without getting interrupted
# by the OperationError(NameError)
+
+ def test_format_traceback(self):
+ from pypy.tool.pytest.objspace import maketestobjspace
+ from pypy.interpreter.gateway import interp2app
+ #
+ def format_traceback(space):
+ return space.format_traceback()
+ #
+ space = maketestobjspace()
+ w_format_traceback = space.wrap(interp2app(format_traceback))
+ w_tb = space.appexec([w_format_traceback], """(format_traceback):
+ def foo():
+ return bar()
+ def bar():
+ return format_traceback()
+ return foo()
+ """)
+ tb = space.str_w(w_tb)
+ expected = '\n'.join([
+ ' File "?", line 6, in anonymous', # this is the appexec code
object
+ ' File "?", line 3, in foo',
+ ' File "?", line 5, in bar',
+ ''
+ ])
+ assert tb == expected
diff --git a/pypy/module/_file/interp_file.py b/pypy/module/_file/interp_file.py
--- a/pypy/module/_file/interp_file.py
+++ b/pypy/module/_file/interp_file.py
@@ -38,23 +38,33 @@
errors = None
fd = -1
cffi_fileobj = None # pypy/module/_cffi_backend
+ w_tb = None # String representation of the traceback at creation time
newlines = 0 # Updated when the stream is closed
def __init__(self, space):
self.space = space
self.register_finalizer(space)
+ if self.space.sys.track_resources:
+ self.w_tb = self.space.format_traceback()
def _finalize_(self):
# assume that the file and stream objects are only visible in the
# thread that runs _finalize_, so no race condition should be
# possible and no locking is done here.
- if self.stream is not None:
- try:
- self.direct_close()
- except StreamErrors as e:
- operr = wrap_streamerror(self.space, e, self.w_name)
- raise operr
+ if self.stream is None:
+ return
+ if self.space.sys.track_resources:
+ w_repr = self.space.repr(self)
+ str_repr = self.space.str_w(w_repr)
+ w_msg = self.space.wrap("WARNING: unclosed file: " + str_repr)
+ self.space.resource_warning(w_msg, self.w_tb)
+ #
+ try:
+ self.direct_close()
+ except StreamErrors as e:
+ operr = wrap_streamerror(self.space, e, self.w_name)
+ raise operr
def fdopenstream(self, stream, fd, mode, w_name=None):
self.fd = fd
diff --git a/pypy/module/_file/test/test_file.py
b/pypy/module/_file/test/test_file.py
--- a/pypy/module/_file/test/test_file.py
+++ b/pypy/module/_file/test/test_file.py
@@ -1,5 +1,6 @@
from __future__ import with_statement
-import py, os, errno
+import pytest, os, errno
+from pypy.interpreter.gateway import interp2app, unwrap_spec
def getfile(space):
return space.appexec([], """():
@@ -10,13 +11,24 @@
return file
""")
+# the following function is used e.g. in test_resource_warning
+@unwrap_spec(regex=str, s=str)
+def regex_search(space, regex, s):
+ import re
+ import textwrap
+ regex = textwrap.dedent(regex).strip()
+ m = re.search(regex, s)
+ m = bool(m)
+ return space.wrap(m)
+
class AppTestFile(object):
spaceconfig = dict(usemodules=("_file",))
def setup_class(cls):
cls.w_temppath = cls.space.wrap(
- str(py.test.ensuretemp("fileimpl").join("foo.txt")))
+ str(pytest.ensuretemp("fileimpl").join("foo.txt")))
cls.w_file = getfile(cls.space)
+ cls.w_regex_search = cls.space.wrap(interp2app(regex_search))
def test_simple(self):
f = self.file(self.temppath, "w")
@@ -206,6 +218,9 @@
assert exc.value.filename == os.curdir
def test_encoding_errors(self):
+ import sys
+ if '__pypy__' not in sys.builtin_module_names:
+ pytest.skip("pypy only test")
import _file
with self.file(self.temppath, "w") as f:
@@ -254,6 +269,71 @@
if '__pypy__' in sys.builtin_module_names:
assert repr(self.temppath) in g.getvalue()
+ @pytest.mark.skipif("config.option.runappdirect")
+ def test_track_resources(self):
+ import os, gc, sys, cStringIO
+ if '__pypy__' not in sys.builtin_module_names:
+ skip("pypy specific test")
+ def fn(flag1, flag2, do_close=False):
+ sys.pypy_set_track_resources(flag1)
+ f = self.file(self.temppath, 'w')
+ sys.pypy_set_track_resources(flag2)
+ buf = cStringIO.StringIO()
+ preverr = sys.stderr
+ try:
+ sys.stderr = buf
+ if do_close:
+ f.close()
+ del f
+ gc.collect() # force __del__ to be called
+ finally:
+ sys.stderr = preverr
+ sys.pypy_set_track_resources(False)
+ return buf.getvalue()
+
+ # check with track_resources disabled
+ assert fn(False, False) == ""
+ #
+ # check that we don't get the warning if we actually close the file
+ assert fn(False, False, do_close=True) == ""
+ #
+ # check with track_resources enabled
+ msg = fn(True, True)
+ assert self.regex_search(r"""
+ WARNING: unclosed file: <open file .*>
+ Created at \(most recent call last\):
+ File ".*", line .*, in test_track_resources
+ File ".*", line .*, in fn
+ """, msg)
+ #
+ # check with track_resources enabled in the destructor BUT with a
+ # file which was created when track_resources was disabled
+ msg = fn(False, True)
+ assert self.regex_search("WARNING: unclosed file: <open file .*>", msg)
+ assert "Created at" not in msg
+
+ @pytest.mark.skipif("config.option.runappdirect")
+ def test_track_resources_dont_crash(self):
+ import os, gc, sys, cStringIO
+ if '__pypy__' not in sys.builtin_module_names:
+ skip("pypy specific test")
+ #
+ # try hard to create a code object whose co_filename points to an
+ # EXISTING file, so that traceback.py tries to open it when formatting
+ # the stacktrace
+ f = open(self.temppath, 'w')
+ f.close()
+ co = compile('open("%s")' % self.temppath, self.temppath, 'exec')
+ sys.pypy_set_track_resources(True)
+ try:
+ # this exec used to fail, because space.format_traceback tried to
+ # recurively open a file, causing an infinite recursion. For the
+ # purpose of this test, it is enough that it actually finishes
+ # without errors
+ exec co
+ finally:
+ sys.pypy_set_track_resources(False)
+
def test_truncate(self):
f = self.file(self.temppath, "w")
f.write("foo")
@@ -313,7 +393,7 @@
cls.old_read = os.read
if cls.runappdirect:
- py.test.skip("works with internals of _file impl on py.py")
+ pytest.skip("works with internals of _file impl on py.py")
def read(fd, n=None):
if fd != 424242:
return cls.old_read(fd, n)
@@ -352,9 +432,9 @@
def setup_class(cls):
if not cls.runappdirect:
- py.test.skip("likely to deadlock when interpreted by py.py")
+ pytest.skip("likely to deadlock when interpreted by py.py")
cls.w_temppath = cls.space.wrap(
- str(py.test.ensuretemp("fileimpl").join("concurrency.txt")))
+ str(pytest.ensuretemp("fileimpl").join("concurrency.txt")))
cls.w_file = getfile(cls.space)
def test_concurrent_writes(self):
@@ -465,7 +545,7 @@
def setup_class(cls):
cls.w_temppath = cls.space.wrap(
- str(py.test.ensuretemp("fileimpl").join("foo.txt")))
+ str(pytest.ensuretemp("fileimpl").join("foo.txt")))
cls.w_file = getfile(cls.space)
def test___enter__(self):
diff --git a/pypy/module/_socket/interp_socket.py
b/pypy/module/_socket/interp_socket.py
--- a/pypy/module/_socket/interp_socket.py
+++ b/pypy/module/_socket/interp_socket.py
@@ -151,9 +151,23 @@
class W_Socket(W_Root):
+ w_tb = None # String representation of the traceback at creation time
+
def __init__(self, space, sock):
+ self.space = space
self.sock = sock
register_socket(space, sock)
+ if self.space.sys.track_resources:
+ self.w_tb = self.space.format_traceback()
+ self.register_finalizer(space)
+
+ def _finalize_(self):
+ is_open = self.sock.fd >= 0
+ if is_open and self.space.sys.track_resources:
+ w_repr = self.space.repr(self)
+ str_repr = self.space.str_w(w_repr)
+ w_msg = self.space.wrap("WARNING: unclosed " + str_repr)
+ self.space.resource_warning(w_msg, self.w_tb)
def get_type_w(self, space):
return space.wrap(self.sock.type)
diff --git a/pypy/module/_socket/test/test_sock_app.py
b/pypy/module/_socket/test/test_sock_app.py
--- a/pypy/module/_socket/test/test_sock_app.py
+++ b/pypy/module/_socket/test/test_sock_app.py
@@ -1,6 +1,8 @@
import sys, os
-import py
+import pytest
from pypy.tool.pytest.objspace import gettestobjspace
+from pypy.interpreter.gateway import interp2app
+from pypy.module._file.test.test_file import regex_search
from rpython.tool.udir import udir
from rpython.rlib import rsocket
from rpython.rtyper.lltypesystem import lltype, rffi
@@ -12,8 +14,6 @@
mod.w_socket = space.appexec([], "(): import _socket as m; return m")
mod.path = udir.join('fd')
mod.path.write('fo')
- mod.raises = py.test.raises # make raises available from app-level tests
- mod.skip = py.test.skip
def test_gethostname():
host = space.appexec([w_socket], "(_socket): return _socket.gethostname()")
@@ -41,7 +41,7 @@
for host in ["localhost", "127.0.0.1", "::1"]:
if host == "::1" and not ipv6:
from pypy.interpreter.error import OperationError
- with py.test.raises(OperationError):
+ with pytest.raises(OperationError):
space.appexec([w_socket, space.wrap(host)],
"(_socket, host): return
_socket.gethostbyaddr(host)")
continue
@@ -57,14 +57,14 @@
assert space.unwrap(port) == 25
# 1 arg version
if sys.version_info < (2, 4):
- py.test.skip("getservbyname second argument is not optional before
python 2.4")
+ pytest.skip("getservbyname second argument is not optional before
python 2.4")
port = space.appexec([w_socket, space.wrap(name)],
"(_socket, name): return _socket.getservbyname(name)")
assert space.unwrap(port) == 25
def test_getservbyport():
if sys.version_info < (2, 4):
- py.test.skip("getservbyport does not exist before python 2.4")
+ pytest.skip("getservbyport does not exist before python 2.4")
port = 25
# 2 args version
name = space.appexec([w_socket, space.wrap(port)],
@@ -97,7 +97,7 @@
def test_fromfd():
# XXX review
if not hasattr(socket, 'fromfd'):
- py.test.skip("No socket.fromfd on this platform")
+ pytest.skip("No socket.fromfd on this platform")
orig_fd = path.open()
fd = space.appexec([w_socket, space.wrap(orig_fd.fileno()),
space.wrap(socket.AF_INET), space.wrap(socket.SOCK_STREAM),
@@ -157,7 +157,7 @@
def test_pton_ntop_ipv4():
if not hasattr(socket, 'inet_pton'):
- py.test.skip('No socket.inet_pton on this platform')
+ pytest.skip('No socket.inet_pton on this platform')
tests = [
("123.45.67.89", "\x7b\x2d\x43\x59"),
("0.0.0.0", "\x00" * 4),
@@ -173,9 +173,9 @@
def test_ntop_ipv6():
if not hasattr(socket, 'inet_pton'):
- py.test.skip('No socket.inet_pton on this platform')
+ pytest.skip('No socket.inet_pton on this platform')
if not socket.has_ipv6:
- py.test.skip("No IPv6 on this platform")
+ pytest.skip("No IPv6 on this platform")
tests = [
("\x00" * 16, "::"),
("\x01" * 16, ":".join(["101"] * 8)),
@@ -194,9 +194,9 @@
def test_pton_ipv6():
if not hasattr(socket, 'inet_pton'):
- py.test.skip('No socket.inet_pton on this platform')
+ pytest.skip('No socket.inet_pton on this platform')
if not socket.has_ipv6:
- py.test.skip("No IPv6 on this platform")
+ pytest.skip("No IPv6 on this platform")
tests = [
("\x00" * 16, "::"),
("\x01" * 16, ":".join(["101"] * 8)),
@@ -215,7 +215,7 @@
assert space.unwrap(w_packed) == packed
def test_has_ipv6():
- py.test.skip("has_ipv6 is always True on PyPy for now")
+ pytest.skip("has_ipv6 is always True on PyPy for now")
res = space.appexec([w_socket], "(_socket): return _socket.has_ipv6")
assert space.unwrap(res) == socket.has_ipv6
@@ -229,7 +229,7 @@
w_l = space.appexec([w_socket, space.wrap(host), space.wrap(port)],
"(_socket, host, port): return
_socket.getaddrinfo(host, long(port))")
assert space.unwrap(w_l) == info
- py.test.skip("Unicode conversion is too slow")
+ pytest.skip("Unicode conversion is too slow")
w_l = space.appexec([w_socket, space.wrap(unicode(host)),
space.wrap(port)],
"(_socket, host, port): return
_socket.getaddrinfo(host, port)")
assert space.unwrap(w_l) == info
@@ -250,7 +250,7 @@
def test_addr_raw_packet():
from pypy.module._socket.interp_socket import addr_as_object
if not hasattr(rsocket._c, 'sockaddr_ll'):
- py.test.skip("posix specific test")
+ pytest.skip("posix specific test")
# HACK: To get the correct interface number of lo, which in most cases is
1,
# but can be anything (i.e. 39), we need to call the libc function
# if_nametoindex to get the correct index
@@ -314,6 +314,7 @@
def setup_class(cls):
cls.space = space
cls.w_udir = space.wrap(str(udir))
+ cls.w_regex_search = space.wrap(interp2app(regex_search))
def teardown_class(cls):
if not cls.runappdirect:
@@ -402,6 +403,64 @@
if os.name != 'nt':
raises(OSError, os.close, fileno)
+ def test_socket_track_resources(self):
+ import _socket, os, gc, sys, cStringIO
+ s = _socket.socket(_socket.AF_INET, _socket.SOCK_STREAM, 0)
+ fileno = s.fileno()
+ assert s.fileno() >= 0
+ s.close()
+ assert s.fileno() < 0
+ s.close()
+ if os.name != 'nt':
+ raises(OSError, os.close, fileno)
+
+ @pytest.mark.skipif("config.option.runappdirect")
+ def test_track_resources(self):
+ import os, gc, sys, cStringIO
+ import _socket
+ if '__pypy__' not in sys.builtin_module_names:
+ skip("pypy specific test")
+ #
+ def fn(flag1, flag2, do_close=False):
+ sys.pypy_set_track_resources(flag1)
+ mysock = _socket.socket(_socket.AF_INET, _socket.SOCK_STREAM, 0)
+ sys.pypy_set_track_resources(flag2)
+ buf = cStringIO.StringIO()
+ preverr = sys.stderr
+ try:
+ sys.stderr = buf
+ if do_close:
+ mysock.close()
+ del mysock
+ gc.collect() # force __del__ to be called
+ finally:
+ sys.stderr = preverr
+ sys.pypy_set_track_resources(False)
+ return buf.getvalue()
+
+ # check with track_resources disabled
+ assert fn(False, False) == ""
+ #
+ # check that we don't get the warning if we actually closed the socket
+ msg = fn(True, True, do_close=True)
+ assert msg == ''
+ #
+ # check with track_resources enabled
+ msg = fn(True, True)
+ assert self.regex_search(r"""
+ WARNING: unclosed <socket object, .*>
+ Created at \(most recent call last\):
+ File ".*", line .*, in test_track_resources
+ File ".*", line .*, in fn
+ """, msg)
+ #
+ # track_resources is enabled after the construction of the socket. in
+ # this case, the socket is not registered for finalization at all, so
+ # we don't see a message
+ msg = fn(False, True)
+ assert msg == ''
+
+
def test_socket_close_error(self):
import _socket, os
if os.name == 'nt':
@@ -630,11 +689,11 @@
class AppTestNetlink:
def setup_class(cls):
if not hasattr(os, 'getpid'):
- py.test.skip("AF_NETLINK needs os.getpid()")
+ pytest.skip("AF_NETLINK needs os.getpid()")
w_ok = space.appexec([], "(): import _socket; " +
"return hasattr(_socket, 'AF_NETLINK')")
if not space.is_true(w_ok):
- py.test.skip("no AF_NETLINK on this platform")
+ pytest.skip("no AF_NETLINK on this platform")
cls.space = space
def test_connect_to_kernel_netlink_routing_socket(self):
@@ -650,11 +709,11 @@
class AppTestPacket:
def setup_class(cls):
if not hasattr(os, 'getuid') or os.getuid() != 0:
- py.test.skip("AF_PACKET needs to be root for testing")
+ pytest.skip("AF_PACKET needs to be root for testing")
w_ok = space.appexec([], "(): import _socket; " +
"return hasattr(_socket, 'AF_PACKET')")
if not space.is_true(w_ok):
- py.test.skip("no AF_PACKET on this platform")
+ pytest.skip("no AF_PACKET on this platform")
cls.space = space
def test_convert_between_tuple_and_sockaddr_ll(self):
diff --git a/pypy/module/sys/__init__.py b/pypy/module/sys/__init__.py
--- a/pypy/module/sys/__init__.py
+++ b/pypy/module/sys/__init__.py
@@ -20,6 +20,7 @@
self.defaultencoding = "ascii"
self.filesystemencoding = None
self.debug = True
+ self.track_resources = False
self.dlopenflags = rdynload._dlopen_default_mode()
interpleveldefs = {
@@ -55,6 +56,8 @@
'_current_frames' : 'currentframes._current_frames',
'setrecursionlimit' : 'vm.setrecursionlimit',
'getrecursionlimit' : 'vm.getrecursionlimit',
+ 'pypy_set_track_resources' : 'vm.set_track_resources',
+ 'pypy_get_track_resources' : 'vm.get_track_resources',
'setcheckinterval' : 'vm.setcheckinterval',
'getcheckinterval' : 'vm.getcheckinterval',
'exc_info' : 'vm.exc_info',
diff --git a/pypy/module/sys/vm.py b/pypy/module/sys/vm.py
--- a/pypy/module/sys/vm.py
+++ b/pypy/module/sys/vm.py
@@ -61,6 +61,13 @@
"""
return space.wrap(space.sys.recursionlimit)
+@unwrap_spec(flag=bool)
+def set_track_resources(space, flag):
+ space.sys.track_resources = flag
+
+def get_track_resources(space):
+ return space.wrap(space.sys.track_resources)
+
@unwrap_spec(interval=int)
def setcheckinterval(space, interval):
"""Tell the Python interpreter to check for asynchronous events every
diff --git a/pypy/objspace/fake/objspace.py b/pypy/objspace/fake/objspace.py
--- a/pypy/objspace/fake/objspace.py
+++ b/pypy/objspace/fake/objspace.py
@@ -428,4 +428,5 @@
FakeObjSpace.sys.filesystemencoding = 'foobar'
FakeObjSpace.sys.defaultencoding = 'ascii'
FakeObjSpace.sys.dlopenflags = 123
+FakeObjSpace.sys.track_resources = False
FakeObjSpace.builtin = FakeModule()
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit