Skip to content

Commit 0ddbc84

Browse files
authored
Merge branch 'main' into fix-issue-139686
2 parents a34d440 + 5ec03cf commit 0ddbc84

File tree

20 files changed

+268
-50
lines changed

20 files changed

+268
-50
lines changed

Doc/library/xmlrpc.client.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,7 @@ remote server into a single request [#]_.
472472

473473
Create an object used to boxcar method calls. *server* is the eventual target of
474474
the call. Calls can be made to the result object, but they will immediately
475-
return ``None``, and only store the call name and parameters in the
475+
return ``None``, and only store the call name and arguments in the
476476
:class:`MultiCall` object. Calling the object itself causes all stored calls to
477477
be transmitted as a single ``system.multicall`` request. The result of this call
478478
is a :term:`generator`; iterating over this generator yields the individual

Include/cpython/object.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -436,8 +436,6 @@ PyAPI_FUNC(void) _Py_NO_RETURN _PyObject_AssertFailed(
436436
PyAPI_FUNC(void) _PyTrash_thread_deposit_object(PyThreadState *tstate, PyObject *op);
437437
PyAPI_FUNC(void) _PyTrash_thread_destroy_chain(PyThreadState *tstate);
438438

439-
PyAPI_FUNC(int) _Py_ReachedRecursionLimitWithMargin(PyThreadState *tstate, int margin_count);
440-
441439
/* For backwards compatibility with the old trashcan mechanism */
442440
#define Py_TRASHCAN_BEGIN(op, dealloc)
443441
#define Py_TRASHCAN_END

Include/internal/pycore_ceval.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,11 @@ static inline int _Py_ReachedRecursionLimit(PyThreadState *tstate) {
263263
#endif
264264
}
265265

266+
// Export for test_peg_generator
267+
PyAPI_FUNC(int) _Py_ReachedRecursionLimitWithMargin(
268+
PyThreadState *tstate,
269+
int margin_count);
270+
266271
static inline void _Py_LeaveRecursiveCall(void) {
267272
}
268273

Include/internal/pycore_pymem.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,27 @@ static inline int _PyMem_IsPtrFreed(const void *ptr)
7070
#endif
7171
}
7272

73+
// Similar to _PyMem_IsPtrFreed() but expects an 'unsigned long' instead of a
74+
// pointer.
75+
static inline int _PyMem_IsULongFreed(unsigned long value)
76+
{
77+
#if SIZEOF_LONG == 8
78+
return (value == 0
79+
|| value == (unsigned long)0xCDCDCDCDCDCDCDCD
80+
|| value == (unsigned long)0xDDDDDDDDDDDDDDDD
81+
|| value == (unsigned long)0xFDFDFDFDFDFDFDFD
82+
|| value == (unsigned long)0xFFFFFFFFFFFFFFFF);
83+
#elif SIZEOF_LONG == 4
84+
return (value == 0
85+
|| value == (unsigned long)0xCDCDCDCD
86+
|| value == (unsigned long)0xDDDDDDDD
87+
|| value == (unsigned long)0xFDFDFDFD
88+
|| value == (unsigned long)0xFFFFFFFF);
89+
#else
90+
# error "unknown long size"
91+
#endif
92+
}
93+
7394
extern int _PyMem_GetAllocatorName(
7495
const char *name,
7596
PyMemAllocatorName *allocator);

Include/internal/pycore_tracemalloc.h

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ struct _PyTraceMalloc_Config {
3030
};
3131

3232

33-
/* Pack the frame_t structure to reduce the memory footprint on 64-bit
34-
architectures: 12 bytes instead of 16. */
33+
/* Pack the tracemalloc_frame and tracemalloc_traceback structures to reduce
34+
the memory footprint on 64-bit architectures: 12 bytes instead of 16. */
3535
#if defined(_MSC_VER)
3636
#pragma pack(push, 4)
3737
#endif
@@ -46,18 +46,22 @@ tracemalloc_frame {
4646
PyObject *filename;
4747
unsigned int lineno;
4848
};
49-
#ifdef _MSC_VER
50-
#pragma pack(pop)
51-
#endif
5249

53-
struct tracemalloc_traceback {
50+
struct
51+
#ifdef __GNUC__
52+
__attribute__((packed))
53+
#endif
54+
tracemalloc_traceback {
5455
Py_uhash_t hash;
5556
/* Number of frames stored */
5657
uint16_t nframe;
5758
/* Total number of frames the traceback had */
5859
uint16_t total_nframe;
59-
struct tracemalloc_frame frames[1];
60+
struct tracemalloc_frame frames[];
6061
};
62+
#ifdef _MSC_VER
63+
#pragma pack(pop)
64+
#endif
6165

6266

6367
struct _tracemalloc_runtime_state {
@@ -95,7 +99,7 @@ struct _tracemalloc_runtime_state {
9599
Protected by TABLES_LOCK(). */
96100
_Py_hashtable_t *domains;
97101

98-
struct tracemalloc_traceback empty_traceback;
102+
struct tracemalloc_traceback *empty_traceback;
99103

100104
Py_tss_t reentrant_key;
101105
};

Lib/importlib/_bootstrap_external.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -208,12 +208,8 @@ def _write_atomic(path, data, mode=0o666):
208208
try:
209209
# We first write data to a temporary file, and then use os.replace() to
210210
# perform an atomic rename.
211-
with _io.FileIO(fd, 'wb') as file:
212-
bytes_written = file.write(data)
213-
if bytes_written != len(data):
214-
# Raise an OSError so the 'except' below cleans up the partially
215-
# written file.
216-
raise OSError("os.write() didn't write the full pyc file")
211+
with _io.open(fd, 'wb') as file:
212+
file.write(data)
217213
_os.replace(path_tmp, path)
218214
except OSError:
219215
try:

Lib/test/test_importlib/test_util.py

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -788,31 +788,70 @@ def test_complete_multi_phase_init_module(self):
788788
self.run_with_own_gil(script)
789789

790790

791-
class MiscTests(unittest.TestCase):
792-
def test_atomic_write_should_notice_incomplete_writes(self):
791+
class PatchAtomicWrites:
792+
def __init__(self, truncate_at_length, never_complete=False):
793+
self.truncate_at_length = truncate_at_length
794+
self.never_complete = never_complete
795+
self.seen_write = False
796+
self._children = []
797+
798+
def __enter__(self):
793799
import _pyio
794800

795801
oldwrite = os.write
796-
seen_write = False
797-
798-
truncate_at_length = 100
799802

800803
# Emulate an os.write that only writes partial data.
801804
def write(fd, data):
802-
nonlocal seen_write
803-
seen_write = True
804-
return oldwrite(fd, data[:truncate_at_length])
805+
if self.seen_write and self.never_complete:
806+
return None
807+
self.seen_write = True
808+
return oldwrite(fd, data[:self.truncate_at_length])
805809

806810
# Need to patch _io to be _pyio, so that io.FileIO is affected by the
807811
# os.write patch.
808-
with (support.swap_attr(_bootstrap_external, '_io', _pyio),
809-
support.swap_attr(os, 'write', write)):
810-
with self.assertRaises(OSError):
811-
# Make sure we write something longer than the point where we
812-
# truncate.
813-
content = b'x' * (truncate_at_length * 2)
814-
_bootstrap_external._write_atomic(os_helper.TESTFN, content)
815-
assert seen_write
812+
self.children = [
813+
support.swap_attr(_bootstrap_external, '_io', _pyio),
814+
support.swap_attr(os, 'write', write)
815+
]
816+
for child in self.children:
817+
child.__enter__()
818+
return self
819+
820+
def __exit__(self, exc_type, exc_val, exc_tb):
821+
for child in self.children:
822+
child.__exit__(exc_type, exc_val, exc_tb)
823+
824+
825+
class MiscTests(unittest.TestCase):
826+
827+
def test_atomic_write_retries_incomplete_writes(self):
828+
truncate_at_length = 100
829+
length = truncate_at_length * 2
830+
831+
with PatchAtomicWrites(truncate_at_length=truncate_at_length) as cm:
832+
# Make sure we write something longer than the point where we
833+
# truncate.
834+
content = b'x' * length
835+
_bootstrap_external._write_atomic(os_helper.TESTFN, content)
836+
self.assertTrue(cm.seen_write)
837+
838+
self.assertEqual(os.stat(support.os_helper.TESTFN).st_size, length)
839+
os.unlink(support.os_helper.TESTFN)
840+
841+
def test_atomic_write_errors_if_unable_to_complete(self):
842+
truncate_at_length = 100
843+
844+
with (
845+
PatchAtomicWrites(
846+
truncate_at_length=truncate_at_length, never_complete=True,
847+
) as cm,
848+
self.assertRaises(OSError)
849+
):
850+
# Make sure we write something longer than the point where we
851+
# truncate.
852+
content = b'x' * (truncate_at_length * 2)
853+
_bootstrap_external._write_atomic(os_helper.TESTFN, content)
854+
self.assertTrue(cm.seen_write)
816855

817856
with self.assertRaises(OSError):
818857
os.stat(support.os_helper.TESTFN) # Check that the file did not get written.

Lib/test/test_sysconfig.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
)
2121
from test.support.import_helper import import_module
2222
from test.support.os_helper import (TESTFN, unlink, skip_unless_symlink,
23-
change_cwd)
23+
change_cwd, EnvironmentVarGuard)
2424
from test.support.venv import VirtualEnvironmentMixin
2525

2626
import sysconfig
@@ -807,7 +807,9 @@ def test_parse_makefile_renamed_vars(self):
807807
print("PY_LDFLAGS=-lm", file=makefile)
808808
print("var2=$(LDFLAGS)", file=makefile)
809809
print("var3=$(CPPFLAGS)", file=makefile)
810-
vars = _parse_makefile(TESTFN)
810+
with EnvironmentVarGuard() as env:
811+
env.clear()
812+
vars = _parse_makefile(TESTFN)
811813
self.assertEqual(vars, {
812814
'var1': '-Wall',
813815
'CFLAGS': '-Wall',
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
When importing a module, use Python's regular file object to ensure that
2+
writes to ``.pyc`` files are complete or an appropriate error is raised.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Make ``test_sysconfig.test_parse_makefile_renamed_vars`` less fragile by
2+
clearing the environment variables before parsing the Makefile.

0 commit comments

Comments
 (0)