Skip to content

Commit f167747

Browse files
committed
Merge branch 'main' into fix-issue-129069
2 parents 9c4debd + c919d02 commit f167747

File tree

12 files changed

+188
-98
lines changed

12 files changed

+188
-98
lines changed

Doc/library/mmap.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,8 @@ To map anonymous memory, -1 should be passed as the fileno along with the length
289289
pagefile) will silently create a new map with the original data copied over
290290
up to the length of the new size.
291291

292+
Availability: Windows and systems with the ``mremap()`` system call.
293+
292294
.. versionchanged:: 3.11
293295
Correctly fails if attempting to resize when another map is held
294296
Allows resize against an anonymous map on Windows

Doc/whatsnew/3.15.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -701,6 +701,9 @@ Porting to Python 3.15
701701
:func:`resource.setrlimit` and :func:`resource.prlimit` is now deprecated.
702702
(Contributed by Serhiy Storchaka in :gh:`137044`.)
703703

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

705708
Deprecated C APIs
706709
-----------------
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import unittest
2+
3+
from test.support import threading_helper
4+
5+
import cProfile
6+
import pstats
7+
8+
9+
NTHREADS = 10
10+
INSERT_PER_THREAD = 1000
11+
12+
13+
@threading_helper.requires_working_threading()
14+
class TestCProfile(unittest.TestCase):
15+
def test_cprofile_racing_list_insert(self):
16+
def list_insert(lst):
17+
for i in range(INSERT_PER_THREAD):
18+
lst.insert(0, i)
19+
20+
lst = []
21+
22+
with cProfile.Profile() as pr:
23+
threading_helper.run_concurrently(
24+
worker_func=list_insert, nthreads=NTHREADS, args=(lst,)
25+
)
26+
pr.create_stats()
27+
ps = pstats.Stats(pr)
28+
stats_profile = ps.get_stats_profile()
29+
list_insert_profile = stats_profile.func_profiles[
30+
"<method 'insert' of 'list' objects>"
31+
]
32+
# Even though there is no explicit recursive call to insert,
33+
# cProfile may record some calls as recursive due to limitations
34+
# in its handling of multithreaded programs. This issue is not
35+
# directly related to FT Python itself; however, it tends to be
36+
# more noticeable when using FT Python. Therefore, consider only
37+
# the calls section and disregard the recursive part.
38+
list_insert_ncalls = list_insert_profile.ncalls.split("/")[0]
39+
self.assertEqual(
40+
int(list_insert_ncalls), NTHREADS * INSERT_PER_THREAD
41+
)
42+
43+
self.assertEqual(len(lst), NTHREADS * INSERT_PER_THREAD)

Lib/test/test_mmap.py

Lines changed: 58 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ def test_basic(self):
5757
f.write(b'\0'* (PAGESIZE-3) )
5858
f.flush()
5959
m = mmap.mmap(f.fileno(), 2 * PAGESIZE)
60+
self.addCleanup(m.close)
6061
finally:
6162
f.close()
6263

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

118+
@unittest.skipUnless(hasattr(mmap.mmap, 'resize'), 'requires mmap.resize')
119+
def test_resize(self):
120+
# Create a file to be mmap'ed.
121+
with open(TESTFN, 'bw+') as f:
122+
# Write 2 pages worth of data to the file
123+
f.write(b'\0'* 2 * PAGESIZE)
124+
f.flush()
125+
m = mmap.mmap(f.fileno(), 2 * PAGESIZE)
126+
self.addCleanup(m.close)
127+
117128
# Try resizing map
118-
try:
119-
m.resize(512)
120-
except SystemError:
121-
# resize() not supported
122-
# No messages are printed, since the output of this test suite
123-
# would then be different across platforms.
124-
pass
125-
else:
126-
# resize() is supported
127-
self.assertEqual(len(m), 512)
128-
# Check that we can no longer seek beyond the new size.
129-
self.assertRaises(ValueError, m.seek, 513, 0)
130-
131-
# Check that the underlying file is truncated too
132-
# (bug #728515)
133-
f = open(TESTFN, 'rb')
134-
try:
135-
f.seek(0, 2)
136-
self.assertEqual(f.tell(), 512)
137-
finally:
138-
f.close()
139-
self.assertEqual(m.size(), 512)
129+
m.resize(512)
130+
self.assertEqual(len(m), 512)
131+
# Check that we can no longer seek beyond the new size.
132+
self.assertRaises(ValueError, m.seek, 513, 0)
140133

141-
m.close()
134+
# Check that the underlying file is truncated too
135+
# (bug #728515)
136+
with open(TESTFN, 'rb') as f:
137+
f.seek(0, 2)
138+
self.assertEqual(f.tell(), 512)
139+
self.assertEqual(m.size(), 512)
142140

143141
def test_access_parameter(self):
144142
# Test for "access" keyword parameter
@@ -183,15 +181,10 @@ def test_access_parameter(self):
183181
else:
184182
self.fail("Able to write to readonly memory map")
185183

186-
# Ensuring that readonly mmap can't be resized
187-
try:
188-
m.resize(2*mapsize)
189-
except SystemError: # resize is not universally supported
190-
pass
191-
except TypeError:
192-
pass
193-
else:
194-
self.fail("Able to resize readonly memory map")
184+
if hasattr(m, 'resize'):
185+
# Ensuring that readonly mmap can't be resized
186+
with self.assertRaises(TypeError):
187+
m.resize(2 * mapsize)
195188
with open(TESTFN, "rb") as fp:
196189
self.assertEqual(fp.read(), b'a'*mapsize,
197190
"Readonly memory map data file was modified")
@@ -242,8 +235,9 @@ def test_access_parameter(self):
242235
with open(TESTFN, "rb") as fp:
243236
self.assertEqual(fp.read(), b'c'*mapsize,
244237
"Copy-on-write test data file should not be modified.")
245-
# Ensuring copy-on-write maps cannot be resized
246-
self.assertRaises(TypeError, m.resize, 2*mapsize)
238+
if hasattr(m, 'resize'):
239+
# Ensuring copy-on-write maps cannot be resized
240+
self.assertRaises(TypeError, m.resize, 2 * mapsize)
247241
m.close()
248242

249243
# Ensuring invalid access parameter raises exception
@@ -282,10 +276,11 @@ def test_trackfd_parameter(self, close_original_fd):
282276
self.assertEqual(len(m), size)
283277
with self.assertRaises(ValueError):
284278
m.size()
285-
with self.assertRaises(ValueError):
286-
m.resize(size * 2)
287-
with self.assertRaises(ValueError):
288-
m.resize(size // 2)
279+
if hasattr(m, 'resize'):
280+
with self.assertRaises(ValueError):
281+
m.resize(size * 2)
282+
with self.assertRaises(ValueError):
283+
m.resize(size // 2)
289284
self.assertIs(m.closed, False)
290285

291286
# Smoke-test other API
@@ -313,8 +308,9 @@ def test_trackfd_neg1(self):
313308
with mmap.mmap(-1, size, trackfd=False) as m:
314309
with self.assertRaises(ValueError):
315310
m.size()
316-
with self.assertRaises(ValueError):
317-
m.resize(size // 2)
311+
if hasattr(m, 'resize'):
312+
with self.assertRaises(ValueError):
313+
m.resize(size // 2)
318314
self.assertEqual(len(m), size)
319315
m[0] = ord('a')
320316
assert m[0] == ord('a')
@@ -608,13 +604,9 @@ def test_offset (self):
608604
self.assertEqual(m[0:3], b'foo')
609605
f.close()
610606

611-
# Try resizing map
612-
try:
607+
if hasattr(m, 'resize'):
608+
# Try resizing map
613609
m.resize(512)
614-
except SystemError:
615-
pass
616-
else:
617-
# resize() is supported
618610
self.assertEqual(len(m), 512)
619611
# Check that we can no longer seek beyond the new size.
620612
self.assertRaises(ValueError, m.seek, 513, 0)
@@ -806,14 +798,12 @@ def test_write_returning_the_number_of_bytes_written(self):
806798
self.assertEqual(mm.write(b"yz"), 2)
807799
self.assertEqual(mm.write(b"python"), 6)
808800

801+
@unittest.skipUnless(hasattr(mmap.mmap, 'resize'), 'requires mmap.resize')
809802
def test_resize_past_pos(self):
810803
m = mmap.mmap(-1, 8192)
811804
self.addCleanup(m.close)
812805
m.read(5000)
813-
try:
814-
m.resize(4096)
815-
except SystemError:
816-
self.skipTest("resizing not supported")
806+
m.resize(4096)
817807
self.assertEqual(m.read(14), b'')
818808
self.assertRaises(ValueError, m.read_byte)
819809
self.assertRaises(ValueError, m.write_byte, 42)
@@ -895,6 +885,7 @@ def test_madvise(self):
895885
self.assertEqual(m.madvise(mmap.MADV_NORMAL, 0, 2), None)
896886
self.assertEqual(m.madvise(mmap.MADV_NORMAL, 0, size), None)
897887

888+
@unittest.skipUnless(hasattr(mmap.mmap, 'resize'), 'requires mmap.resize')
898889
def test_resize_up_anonymous_mapping(self):
899890
"""If the mmap is backed by the pagefile ensure a resize up can happen
900891
and that the original data is still in place
@@ -911,32 +902,26 @@ def test_resize_up_anonymous_mapping(self):
911902
with self.assertRaises(ValueError):
912903
m.resize(new_size)
913904
else:
914-
try:
915-
m.resize(new_size)
916-
except SystemError:
917-
pass
918-
else:
919-
self.assertEqual(len(m), new_size)
920-
self.assertEqual(m[:start_size], data)
921-
self.assertEqual(m[start_size:], b'\0' * (new_size - start_size))
905+
m.resize(new_size)
906+
self.assertEqual(len(m), new_size)
907+
self.assertEqual(m[:start_size], data)
908+
self.assertEqual(m[start_size:], b'\0' * (new_size - start_size))
922909

923910
@unittest.skipUnless(os.name == 'posix', 'requires Posix')
911+
@unittest.skipUnless(hasattr(mmap.mmap, 'resize'), 'requires mmap.resize')
924912
def test_resize_up_private_anonymous_mapping(self):
925913
start_size = PAGESIZE
926914
new_size = 2 * start_size
927915
data = random.randbytes(start_size)
928916

929917
with mmap.mmap(-1, start_size, flags=mmap.MAP_PRIVATE) as m:
930918
m[:] = data
931-
try:
932-
m.resize(new_size)
933-
except SystemError:
934-
pass
935-
else:
936-
self.assertEqual(len(m), new_size)
937-
self.assertEqual(m[:start_size], data)
938-
self.assertEqual(m[start_size:], b'\0' * (new_size - start_size))
919+
m.resize(new_size)
920+
self.assertEqual(len(m), new_size)
921+
self.assertEqual(m[:start_size], data)
922+
self.assertEqual(m[start_size:], b'\0' * (new_size - start_size))
939923

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

948933
with mmap.mmap(-1, start_size) as m:
949934
m[:] = data
950-
try:
951-
m.resize(new_size)
952-
except SystemError:
953-
pass
954-
else:
955-
self.assertEqual(len(m), new_size)
956-
self.assertEqual(m[:], data[:new_size])
957-
if sys.platform.startswith(('linux', 'android')):
958-
# Can't expand to its original size.
959-
with self.assertRaises(ValueError):
960-
m.resize(start_size)
935+
m.resize(new_size)
936+
self.assertEqual(len(m), new_size)
937+
self.assertEqual(m[:], data[:new_size])
938+
if sys.platform.startswith(('linux', 'android')):
939+
# Can't expand to its original size.
940+
with self.assertRaises(ValueError):
941+
m.resize(start_size)
961942

962943
@unittest.skipUnless(os.name == 'nt', 'requires Windows')
963944
def test_resize_fails_if_mapping_held_elsewhere(self):

Lib/test/test_typing.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9788,6 +9788,19 @@ class B(str): ...
97889788
self.assertIs(type(field_c2.__metadata__[0]), float)
97899789
self.assertIs(type(field_c3.__metadata__[0]), bool)
97909790

9791+
def test_forwardref_partial_evaluation(self):
9792+
# Test that Annotated partially evaluates if it contains a ForwardRef
9793+
# See: https://github.com/python/cpython/issues/137706
9794+
def f(x: Annotated[undefined, '']): pass
9795+
9796+
ann = annotationlib.get_annotations(f, format=annotationlib.Format.FORWARDREF)
9797+
9798+
# Test that the attributes are retrievable from the partially evaluated annotation
9799+
x_ann = ann['x']
9800+
self.assertIs(get_origin(x_ann), Annotated)
9801+
self.assertEqual(x_ann.__origin__, EqualToForwardRef('undefined', owner=f))
9802+
self.assertEqual(x_ann.__metadata__, ('',))
9803+
97919804

97929805
class TypeAliasTests(BaseTestCase):
97939806
def test_canonical_usage_with_variable_annotation(self):

Lib/typing.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1027,8 +1027,10 @@ def evaluate_forward_ref(
10271027

10281028

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

10331035

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

0 commit comments

Comments
 (0)