Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Doc/library/mmap.rst
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,8 @@ To map anonymous memory, -1 should be passed as the fileno along with the length
pagefile) will silently create a new map with the original data copied over
up to the length of the new size.

Availability: Windows and systems with the ``mremap()`` system call.

.. versionchanged:: 3.11
Correctly fails if attempting to resize when another map is held
Allows resize against an anonymous map on Windows
Expand Down
3 changes: 3 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,9 @@ Porting to Python 3.15
:func:`resource.setrlimit` and :func:`resource.prlimit` is now deprecated.
(Contributed by Serhiy Storchaka in :gh:`137044`.)

* :meth:`~mmap.mmap.resize` has been removed on platforms that don't support the
underlying syscall, instead of raising a :exc:`SystemError`.


Deprecated C APIs
-----------------
Expand Down
43 changes: 43 additions & 0 deletions Lib/test/test_free_threading/test_cprofile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import unittest

from test.support import threading_helper

import cProfile
import pstats


NTHREADS = 10
INSERT_PER_THREAD = 1000


@threading_helper.requires_working_threading()
class TestCProfile(unittest.TestCase):
def test_cprofile_racing_list_insert(self):
def list_insert(lst):
for i in range(INSERT_PER_THREAD):
lst.insert(0, i)

lst = []

with cProfile.Profile() as pr:
threading_helper.run_concurrently(
worker_func=list_insert, nthreads=NTHREADS, args=(lst,)
)
pr.create_stats()
ps = pstats.Stats(pr)
stats_profile = ps.get_stats_profile()
list_insert_profile = stats_profile.func_profiles[
"<method 'insert' of 'list' objects>"
]
# Even though there is no explicit recursive call to insert,
# cProfile may record some calls as recursive due to limitations
# in its handling of multithreaded programs. This issue is not
# directly related to FT Python itself; however, it tends to be
# more noticeable when using FT Python. Therefore, consider only
# the calls section and disregard the recursive part.
list_insert_ncalls = list_insert_profile.ncalls.split("/")[0]
self.assertEqual(
int(list_insert_ncalls), NTHREADS * INSERT_PER_THREAD
)

self.assertEqual(len(lst), NTHREADS * INSERT_PER_THREAD)
135 changes: 58 additions & 77 deletions Lib/test/test_mmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ def test_basic(self):
f.write(b'\0'* (PAGESIZE-3) )
f.flush()
m = mmap.mmap(f.fileno(), 2 * PAGESIZE)
self.addCleanup(m.close)
finally:
f.close()

Expand Down Expand Up @@ -114,31 +115,28 @@ def test_basic(self):
# Try to seek to negative position...
self.assertRaises(ValueError, m.seek, -len(m)-1, 2)

@unittest.skipUnless(hasattr(mmap.mmap, 'resize'), 'requires mmap.resize')
def test_resize(self):
# Create a file to be mmap'ed.
with open(TESTFN, 'bw+') as f:
# Write 2 pages worth of data to the file
f.write(b'\0'* 2 * PAGESIZE)
f.flush()
m = mmap.mmap(f.fileno(), 2 * PAGESIZE)
self.addCleanup(m.close)

# Try resizing map
try:
m.resize(512)
except SystemError:
# resize() not supported
# No messages are printed, since the output of this test suite
# would then be different across platforms.
pass
else:
# resize() is supported
self.assertEqual(len(m), 512)
# Check that we can no longer seek beyond the new size.
self.assertRaises(ValueError, m.seek, 513, 0)

# Check that the underlying file is truncated too
# (bug #728515)
f = open(TESTFN, 'rb')
try:
f.seek(0, 2)
self.assertEqual(f.tell(), 512)
finally:
f.close()
self.assertEqual(m.size(), 512)
m.resize(512)
self.assertEqual(len(m), 512)
# Check that we can no longer seek beyond the new size.
self.assertRaises(ValueError, m.seek, 513, 0)

m.close()
# Check that the underlying file is truncated too
# (bug #728515)
with open(TESTFN, 'rb') as f:
f.seek(0, 2)
self.assertEqual(f.tell(), 512)
self.assertEqual(m.size(), 512)

def test_access_parameter(self):
# Test for "access" keyword parameter
Expand Down Expand Up @@ -183,15 +181,10 @@ def test_access_parameter(self):
else:
self.fail("Able to write to readonly memory map")

# Ensuring that readonly mmap can't be resized
try:
m.resize(2*mapsize)
except SystemError: # resize is not universally supported
pass
except TypeError:
pass
else:
self.fail("Able to resize readonly memory map")
if hasattr(m, 'resize'):
# Ensuring that readonly mmap can't be resized
with self.assertRaises(TypeError):
m.resize(2 * mapsize)
with open(TESTFN, "rb") as fp:
self.assertEqual(fp.read(), b'a'*mapsize,
"Readonly memory map data file was modified")
Expand Down Expand Up @@ -242,8 +235,9 @@ def test_access_parameter(self):
with open(TESTFN, "rb") as fp:
self.assertEqual(fp.read(), b'c'*mapsize,
"Copy-on-write test data file should not be modified.")
# Ensuring copy-on-write maps cannot be resized
self.assertRaises(TypeError, m.resize, 2*mapsize)
if hasattr(m, 'resize'):
# Ensuring copy-on-write maps cannot be resized
self.assertRaises(TypeError, m.resize, 2 * mapsize)
m.close()

# Ensuring invalid access parameter raises exception
Expand Down Expand Up @@ -282,10 +276,11 @@ def test_trackfd_parameter(self, close_original_fd):
self.assertEqual(len(m), size)
with self.assertRaises(ValueError):
m.size()
with self.assertRaises(ValueError):
m.resize(size * 2)
with self.assertRaises(ValueError):
m.resize(size // 2)
if hasattr(m, 'resize'):
with self.assertRaises(ValueError):
m.resize(size * 2)
with self.assertRaises(ValueError):
m.resize(size // 2)
self.assertIs(m.closed, False)

# Smoke-test other API
Expand Down Expand Up @@ -313,8 +308,9 @@ def test_trackfd_neg1(self):
with mmap.mmap(-1, size, trackfd=False) as m:
with self.assertRaises(ValueError):
m.size()
with self.assertRaises(ValueError):
m.resize(size // 2)
if hasattr(m, 'resize'):
with self.assertRaises(ValueError):
m.resize(size // 2)
self.assertEqual(len(m), size)
m[0] = ord('a')
assert m[0] == ord('a')
Expand Down Expand Up @@ -608,13 +604,9 @@ def test_offset (self):
self.assertEqual(m[0:3], b'foo')
f.close()

# Try resizing map
try:
if hasattr(m, 'resize'):
# Try resizing map
m.resize(512)
except SystemError:
pass
else:
# resize() is supported
self.assertEqual(len(m), 512)
# Check that we can no longer seek beyond the new size.
self.assertRaises(ValueError, m.seek, 513, 0)
Expand Down Expand Up @@ -806,14 +798,12 @@ def test_write_returning_the_number_of_bytes_written(self):
self.assertEqual(mm.write(b"yz"), 2)
self.assertEqual(mm.write(b"python"), 6)

@unittest.skipUnless(hasattr(mmap.mmap, 'resize'), 'requires mmap.resize')
def test_resize_past_pos(self):
m = mmap.mmap(-1, 8192)
self.addCleanup(m.close)
m.read(5000)
try:
m.resize(4096)
except SystemError:
self.skipTest("resizing not supported")
m.resize(4096)
self.assertEqual(m.read(14), b'')
self.assertRaises(ValueError, m.read_byte)
self.assertRaises(ValueError, m.write_byte, 42)
Expand Down Expand Up @@ -895,6 +885,7 @@ def test_madvise(self):
self.assertEqual(m.madvise(mmap.MADV_NORMAL, 0, 2), None)
self.assertEqual(m.madvise(mmap.MADV_NORMAL, 0, size), None)

@unittest.skipUnless(hasattr(mmap.mmap, 'resize'), 'requires mmap.resize')
def test_resize_up_anonymous_mapping(self):
"""If the mmap is backed by the pagefile ensure a resize up can happen
and that the original data is still in place
Expand All @@ -911,32 +902,26 @@ def test_resize_up_anonymous_mapping(self):
with self.assertRaises(ValueError):
m.resize(new_size)
else:
try:
m.resize(new_size)
except SystemError:
pass
else:
self.assertEqual(len(m), new_size)
self.assertEqual(m[:start_size], data)
self.assertEqual(m[start_size:], b'\0' * (new_size - start_size))
m.resize(new_size)
self.assertEqual(len(m), new_size)
self.assertEqual(m[:start_size], data)
self.assertEqual(m[start_size:], b'\0' * (new_size - start_size))

@unittest.skipUnless(os.name == 'posix', 'requires Posix')
@unittest.skipUnless(hasattr(mmap.mmap, 'resize'), 'requires mmap.resize')
def test_resize_up_private_anonymous_mapping(self):
start_size = PAGESIZE
new_size = 2 * start_size
data = random.randbytes(start_size)

with mmap.mmap(-1, start_size, flags=mmap.MAP_PRIVATE) as m:
m[:] = data
try:
m.resize(new_size)
except SystemError:
pass
else:
self.assertEqual(len(m), new_size)
self.assertEqual(m[:start_size], data)
self.assertEqual(m[start_size:], b'\0' * (new_size - start_size))
m.resize(new_size)
self.assertEqual(len(m), new_size)
self.assertEqual(m[:start_size], data)
self.assertEqual(m[start_size:], b'\0' * (new_size - start_size))

@unittest.skipUnless(hasattr(mmap.mmap, 'resize'), 'requires mmap.resize')
def test_resize_down_anonymous_mapping(self):
"""If the mmap is backed by the pagefile ensure a resize down up can happen
and that a truncated form of the original data is still in place
Expand All @@ -947,17 +932,13 @@ def test_resize_down_anonymous_mapping(self):

with mmap.mmap(-1, start_size) as m:
m[:] = data
try:
m.resize(new_size)
except SystemError:
pass
else:
self.assertEqual(len(m), new_size)
self.assertEqual(m[:], data[:new_size])
if sys.platform.startswith(('linux', 'android')):
# Can't expand to its original size.
with self.assertRaises(ValueError):
m.resize(start_size)
m.resize(new_size)
self.assertEqual(len(m), new_size)
self.assertEqual(m[:], data[:new_size])
if sys.platform.startswith(('linux', 'android')):
# Can't expand to its original size.
with self.assertRaises(ValueError):
m.resize(start_size)

@unittest.skipUnless(os.name == 'nt', 'requires Windows')
def test_resize_fails_if_mapping_held_elsewhere(self):
Expand Down
13 changes: 13 additions & 0 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -9788,6 +9788,19 @@ class B(str): ...
self.assertIs(type(field_c2.__metadata__[0]), float)
self.assertIs(type(field_c3.__metadata__[0]), bool)

def test_forwardref_partial_evaluation(self):
# Test that Annotated partially evaluates if it contains a ForwardRef
# See: https://github.com/python/cpython/issues/137706
def f(x: Annotated[undefined, '']): pass

ann = annotationlib.get_annotations(f, format=annotationlib.Format.FORWARDREF)

# Test that the attributes are retrievable from the partially evaluated annotation
x_ann = ann['x']
self.assertIs(get_origin(x_ann), Annotated)
self.assertEqual(x_ann.__origin__, EqualToForwardRef('undefined', owner=f))
self.assertEqual(x_ann.__metadata__, ('',))


class TypeAliasTests(BaseTestCase):
def test_canonical_usage_with_variable_annotation(self):
Expand Down
4 changes: 3 additions & 1 deletion Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -1027,8 +1027,10 @@ def evaluate_forward_ref(


def _is_unpacked_typevartuple(x: Any) -> bool:
# Need to check 'is True' here
# See: https://github.com/python/cpython/issues/137706
return ((not isinstance(x, type)) and
getattr(x, '__typing_is_unpacked_typevartuple__', False))
getattr(x, '__typing_is_unpacked_typevartuple__', False) is True)


def _is_typevar_like(x: Any) -> bool:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Make :mod:`cProfile` thread-safe on the :term:`free threaded <free
threading>` build.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Removed the :meth:`~mmap.mmap.resize` method on platforms that don't support the
underlying syscall, instead of raising a :exc:`SystemError`.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix the partial evaluation of annotations that use ``typing.Annotated[T, x]`` where ``T`` is a forward reference.
Loading
Loading