Skip to content

Commit c5a2f41

Browse files
authored
Merge branch 'main' into wr-132413
2 parents 4b1b45c + fe119a0 commit c5a2f41

File tree

11 files changed

+200
-39
lines changed

11 files changed

+200
-39
lines changed

Doc/library/exceptions.rst

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,9 @@ The following exceptions are the exceptions that are usually raised.
429429

430430
* Creating a new Python thread.
431431
* :meth:`Joining <threading.Thread.join>` a running daemon thread.
432-
* :func:`os.fork`.
432+
* :func:`os.fork`,
433+
* acquiring a lock such as :class:`threading.Lock`, when it is known that
434+
the operation would otherwise deadlock.
433435

434436
See also the :func:`sys.is_finalizing` function.
435437

@@ -440,6 +442,11 @@ The following exceptions are the exceptions that are usually raised.
440442

441443
:meth:`threading.Thread.join` can now raise this exception.
442444

445+
.. versionchanged:: next
446+
447+
This exception may be raised when acquiring :meth:`threading.Lock`
448+
or :meth:`threading.RLock`.
449+
443450
.. exception:: RecursionError
444451

445452
This exception is derived from :exc:`RuntimeError`. It is raised when the

Include/internal/pycore_lock.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ typedef enum _PyLockFlags {
5151

5252
// Fail if interrupted by a signal while waiting on the lock.
5353
_PY_FAIL_IF_INTERRUPTED = 4,
54+
55+
// Locking & unlocking this lock requires attached thread state.
56+
// If locking returns PY_LOCK_FAILURE, a Python exception *may* be raised.
57+
// (Intended for use with _PY_LOCK_HANDLE_SIGNALS and _PY_LOCK_DETACH.)
58+
_PY_LOCK_PYTHONLOCK = 8,
5459
} _PyLockFlags;
5560

5661
// Lock a mutex with an optional timeout and additional options. See

Include/pyport.h

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -667,25 +667,6 @@ extern "C" {
667667
#endif
668668

669669

670-
// _Py_NO_SANITIZE_UNDEFINED(): Disable Undefined Behavior sanitizer (UBsan)
671-
// on a function.
672-
//
673-
// Clang and GCC 9.0+ use __attribute__((no_sanitize("undefined"))).
674-
// GCC 4.9+ uses __attribute__((no_sanitize_undefined)).
675-
#if defined(__has_feature)
676-
# if __has_feature(undefined_behavior_sanitizer)
677-
# define _Py_NO_SANITIZE_UNDEFINED __attribute__((no_sanitize("undefined")))
678-
# endif
679-
#endif
680-
#if !defined(_Py_NO_SANITIZE_UNDEFINED) && defined(__GNUC__) \
681-
&& ((__GNUC__ >= 5) || (__GNUC__ == 4) && (__GNUC_MINOR__ >= 9))
682-
# define _Py_NO_SANITIZE_UNDEFINED __attribute__((no_sanitize_undefined))
683-
#endif
684-
#ifndef _Py_NO_SANITIZE_UNDEFINED
685-
# define _Py_NO_SANITIZE_UNDEFINED
686-
#endif
687-
688-
689670
// _Py_NONSTRING: The nonstring variable attribute specifies that an object or
690671
// member declaration with type array of char, signed char, or unsigned char,
691672
// or pointer to such a type is intended to store character arrays that do not

Lib/test/test_fileio.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -591,7 +591,7 @@ def testBytesOpen(self):
591591
try:
592592
f.write(b"abc")
593593
f.close()
594-
with open(TESTFN_ASCII, "rb") as f:
594+
with self.open(TESTFN_ASCII, "rb") as f:
595595
self.assertEqual(f.read(), b"abc")
596596
finally:
597597
os.unlink(TESTFN_ASCII)
@@ -608,7 +608,7 @@ def testUtf8BytesOpen(self):
608608
try:
609609
f.write(b"abc")
610610
f.close()
611-
with open(TESTFN_UNICODE, "rb") as f:
611+
with self.open(TESTFN_UNICODE, "rb") as f:
612612
self.assertEqual(f.read(), b"abc")
613613
finally:
614614
os.unlink(TESTFN_UNICODE)
@@ -692,13 +692,13 @@ def bug801631():
692692

693693
def testAppend(self):
694694
try:
695-
f = open(TESTFN, 'wb')
695+
f = self.FileIO(TESTFN, 'wb')
696696
f.write(b'spam')
697697
f.close()
698-
f = open(TESTFN, 'ab')
698+
f = self.FileIO(TESTFN, 'ab')
699699
f.write(b'eggs')
700700
f.close()
701-
f = open(TESTFN, 'rb')
701+
f = self.FileIO(TESTFN, 'rb')
702702
d = f.read()
703703
f.close()
704704
self.assertEqual(d, b'spameggs')
@@ -734,6 +734,7 @@ def __setattr__(self, name, value):
734734
class COtherFileTests(OtherFileTests, unittest.TestCase):
735735
FileIO = _io.FileIO
736736
modulename = '_io'
737+
open = _io.open
737738

738739
@cpython_only
739740
def testInvalidFd_overflow(self):
@@ -755,6 +756,7 @@ def test_open_code(self):
755756
class PyOtherFileTests(OtherFileTests, unittest.TestCase):
756757
FileIO = _pyio.FileIO
757758
modulename = '_pyio'
759+
open = _pyio.open
758760

759761
def test_open_code(self):
760762
# Check that the default behaviour of open_code matches

Lib/test/test_pyexpat.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,11 @@
99
from io import BytesIO
1010
from test import support
1111
from test.support import os_helper
12-
12+
from test.support import sortdict
13+
from unittest import mock
1314
from xml.parsers import expat
1415
from xml.parsers.expat import errors
1516

16-
from test.support import sortdict
17-
1817

1918
class SetAttributeTest(unittest.TestCase):
2019
def setUp(self):
@@ -436,6 +435,19 @@ def test7(self):
436435
"<!--abc-->", "4", "<!--def-->", "5", "</a>"],
437436
"buffered text not properly split")
438437

438+
def test_change_character_data_handler_in_callback(self):
439+
# Test that xmlparse_handler_setter() properly handles
440+
# the special case "parser.CharacterDataHandler = None".
441+
def handler(*args):
442+
parser.CharacterDataHandler = None
443+
444+
handler_wrapper = mock.Mock(wraps=handler)
445+
parser = expat.ParserCreate()
446+
parser.CharacterDataHandler = handler_wrapper
447+
parser.Parse(b"<a>1<b/>2<c></c>3<!--abc-->4<!--def-->5</a> ", True)
448+
handler_wrapper.assert_called_once()
449+
self.assertIsNone(parser.CharacterDataHandler)
450+
439451

440452
# Test handling of exception from callback:
441453
class HandlerExceptionTest(unittest.TestCase):
@@ -595,7 +607,7 @@ def test_unchanged_size(self):
595607
def test_disabling_buffer(self):
596608
xml1 = b"<?xml version='1.0' encoding='iso8859'?><a>" + b'a' * 512
597609
xml2 = b'b' * 1024
598-
xml3 = b'c' * 1024 + b'</a>';
610+
xml3 = b'c' * 1024 + b'</a>'
599611
parser = expat.ParserCreate()
600612
parser.CharacterDataHandler = self.counting_handler
601613
parser.buffer_text = 1

Lib/test/test_threading.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1247,6 +1247,61 @@ def __del__(self):
12471247
self.assertEqual(err, b"")
12481248
self.assertIn(b"all clear", out)
12491249

1250+
@support.subTests('lock_class_name', ['Lock', 'RLock'])
1251+
def test_acquire_daemon_thread_lock_in_finalization(self, lock_class_name):
1252+
# gh-123940: Py_Finalize() prevents other threads from running Python
1253+
# code (and so, releasing locks), so acquiring a locked lock can not
1254+
# succeed.
1255+
# We raise an exception rather than hang.
1256+
code = textwrap.dedent(f"""
1257+
import threading
1258+
import time
1259+
1260+
thread_started_event = threading.Event()
1261+
1262+
lock = threading.{lock_class_name}()
1263+
def loop():
1264+
if {lock_class_name!r} == 'RLock':
1265+
lock.acquire()
1266+
with lock:
1267+
thread_started_event.set()
1268+
while True:
1269+
time.sleep(1)
1270+
1271+
uncontested_lock = threading.{lock_class_name}()
1272+
1273+
class Cycle:
1274+
def __init__(self):
1275+
self.self_ref = self
1276+
self.thr = threading.Thread(
1277+
target=loop, daemon=True)
1278+
self.thr.start()
1279+
thread_started_event.wait()
1280+
1281+
def __del__(self):
1282+
assert self.thr.is_alive()
1283+
1284+
# We *can* acquire an unlocked lock
1285+
uncontested_lock.acquire()
1286+
if {lock_class_name!r} == 'RLock':
1287+
uncontested_lock.acquire()
1288+
1289+
# Acquiring a locked one fails
1290+
try:
1291+
lock.acquire()
1292+
except PythonFinalizationError:
1293+
assert self.thr.is_alive()
1294+
print('got the correct exception!')
1295+
1296+
# Cycle holds a reference to itself, which ensures it is
1297+
# cleaned up during the GC that runs after daemon threads
1298+
# have been forced to exit during finalization.
1299+
Cycle()
1300+
""")
1301+
rc, out, err = assert_python_ok("-c", code)
1302+
self.assertEqual(err, b"")
1303+
self.assertIn(b"got the correct exception", out)
1304+
12501305
def test_start_new_thread_failed(self):
12511306
# gh-109746: if Python fails to start newly created thread
12521307
# due to failure of underlying PyThread_start_new_thread() call,
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Remove the private, undocumented macro :c:macro:`!_Py_NO_SANITIZE_UNDEFINED`.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Acquiring a :class:`threading.Lock` or :class:`threading.RLock` at interpreter
2+
shutdown will raise :exc:`PythonFinalizationError` if Python can determine
3+
that it would otherwise deadlock.

Modules/_threadmodule.c

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -834,9 +834,14 @@ lock_PyThread_acquire_lock(PyObject *op, PyObject *args, PyObject *kwds)
834834
return NULL;
835835
}
836836

837-
PyLockStatus r = _PyMutex_LockTimed(&self->lock, timeout,
838-
_PY_LOCK_HANDLE_SIGNALS | _PY_LOCK_DETACH);
837+
PyLockStatus r = _PyMutex_LockTimed(
838+
&self->lock, timeout,
839+
_PY_LOCK_PYTHONLOCK | _PY_LOCK_HANDLE_SIGNALS | _PY_LOCK_DETACH);
839840
if (r == PY_LOCK_INTR) {
841+
assert(PyErr_Occurred());
842+
return NULL;
843+
}
844+
if (r == PY_LOCK_FAILURE && PyErr_Occurred()) {
840845
return NULL;
841846
}
842847

@@ -1054,9 +1059,14 @@ rlock_acquire(PyObject *op, PyObject *args, PyObject *kwds)
10541059
return NULL;
10551060
}
10561061

1057-
PyLockStatus r = _PyRecursiveMutex_LockTimed(&self->lock, timeout,
1058-
_PY_LOCK_HANDLE_SIGNALS | _PY_LOCK_DETACH);
1062+
PyLockStatus r = _PyRecursiveMutex_LockTimed(
1063+
&self->lock, timeout,
1064+
_PY_LOCK_PYTHONLOCK | _PY_LOCK_HANDLE_SIGNALS | _PY_LOCK_DETACH);
10591065
if (r == PY_LOCK_INTR) {
1066+
assert(PyErr_Occurred());
1067+
return NULL;
1068+
}
1069+
if (r == PY_LOCK_FAILURE && PyErr_Occurred()) {
10601070
return NULL;
10611071
}
10621072

Modules/pyexpat.c

Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,11 @@ typedef struct {
9898

9999
#define CHARACTER_DATA_BUFFER_SIZE 8192
100100

101-
typedef const void *xmlhandler;
101+
// A generic function type for storage.
102+
// To avoid undefined behaviors, a handler must be cast to the correct
103+
// function type before it's called; see SETTER_WRAPPER below.
104+
typedef void (*xmlhandler)(void);
105+
102106
typedef void (*xmlhandlersetter)(XML_Parser self, xmlhandler handler);
103107

104108
struct HandlerInfo {
@@ -110,9 +114,7 @@ struct HandlerInfo {
110114

111115
static struct HandlerInfo handler_info[64];
112116

113-
// gh-111178: Use _Py_NO_SANITIZE_UNDEFINED, rather than using the exact
114-
// handler API for each handler.
115-
static inline void _Py_NO_SANITIZE_UNDEFINED
117+
static inline void
116118
CALL_XML_HANDLER_SETTER(const struct HandlerInfo *handler_info,
117119
XML_Parser xml_parser, xmlhandler xml_handler)
118120
{
@@ -1365,7 +1367,7 @@ xmlparse_handler_setter(PyObject *op, PyObject *v, void *closure)
13651367
elaborate system of handlers and state could remove the
13661368
C handler more effectively. */
13671369
if (handlernum == CharacterData && self->in_callback) {
1368-
c_handler = noop_character_data_handler;
1370+
c_handler = (xmlhandler)noop_character_data_handler;
13691371
}
13701372
v = NULL;
13711373
}
@@ -2222,13 +2224,84 @@ clear_handlers(xmlparseobject *self, int initial)
22222224
}
22232225
}
22242226

2227+
/* To avoid undefined behaviors, a function must be *called* via a function
2228+
* pointer of the correct type.
2229+
* So, for each `XML_Set*` function, we define a wrapper that calls `XML_Set*`
2230+
* with its argument cast to the appropriate type.
2231+
*/
2232+
2233+
typedef void (*parser_only)(void *);
2234+
typedef int (*not_standalone)(void *);
2235+
typedef void (*parser_and_data)(void *, const XML_Char *);
2236+
typedef void (*parser_and_data_and_int)(void *, const XML_Char *, int);
2237+
typedef void (*parser_and_data_and_data)(
2238+
void *, const XML_Char *, const XML_Char *);
2239+
typedef void (*start_element)(void *, const XML_Char *, const XML_Char **);
2240+
typedef void (*element_decl)(void *, const XML_Char *, XML_Content *);
2241+
typedef void (*xml_decl)(
2242+
void *, const XML_Char *, const XML_Char *, int);
2243+
typedef void (*start_doctype_decl)(
2244+
void *, const XML_Char *, const XML_Char *, const XML_Char *, int);
2245+
typedef void (*notation_decl)(
2246+
void *,
2247+
const XML_Char *, const XML_Char *, const XML_Char *, const XML_Char *);
2248+
typedef void (*attlist_decl)(
2249+
void *,
2250+
const XML_Char *, const XML_Char *, const XML_Char *, const XML_Char *,
2251+
int);
2252+
typedef void (*unparsed_entity_decl)(
2253+
void *,
2254+
const XML_Char *, const XML_Char *,
2255+
const XML_Char *, const XML_Char *, const XML_Char *);
2256+
typedef void (*entity_decl)(
2257+
void *,
2258+
const XML_Char *, int,
2259+
const XML_Char *, int,
2260+
const XML_Char *, const XML_Char *, const XML_Char *, const XML_Char *);
2261+
typedef int (*external_entity_ref)(
2262+
XML_Parser,
2263+
const XML_Char *, const XML_Char *, const XML_Char *, const XML_Char *);
2264+
2265+
#define SETTER_WRAPPER(NAME, TYPE) \
2266+
static inline void \
2267+
pyexpat_Set ## NAME (XML_Parser parser, xmlhandler handler) \
2268+
{ \
2269+
(void)XML_Set ## NAME (parser, (TYPE)handler); \
2270+
}
2271+
2272+
SETTER_WRAPPER(StartElementHandler, start_element)
2273+
SETTER_WRAPPER(EndElementHandler, parser_and_data)
2274+
SETTER_WRAPPER(ProcessingInstructionHandler, parser_and_data_and_data)
2275+
SETTER_WRAPPER(CharacterDataHandler, parser_and_data_and_int)
2276+
SETTER_WRAPPER(UnparsedEntityDeclHandler, unparsed_entity_decl)
2277+
SETTER_WRAPPER(NotationDeclHandler, notation_decl)
2278+
SETTER_WRAPPER(StartNamespaceDeclHandler, parser_and_data_and_data)
2279+
SETTER_WRAPPER(EndNamespaceDeclHandler, parser_and_data)
2280+
SETTER_WRAPPER(CommentHandler, parser_and_data)
2281+
SETTER_WRAPPER(StartCdataSectionHandler, parser_only)
2282+
SETTER_WRAPPER(EndCdataSectionHandler, parser_only)
2283+
SETTER_WRAPPER(DefaultHandler, parser_and_data_and_int)
2284+
SETTER_WRAPPER(DefaultHandlerExpand, parser_and_data_and_int)
2285+
SETTER_WRAPPER(NotStandaloneHandler, not_standalone)
2286+
SETTER_WRAPPER(ExternalEntityRefHandler, external_entity_ref)
2287+
SETTER_WRAPPER(StartDoctypeDeclHandler, start_doctype_decl)
2288+
SETTER_WRAPPER(EndDoctypeDeclHandler, parser_only)
2289+
SETTER_WRAPPER(EntityDeclHandler, entity_decl)
2290+
SETTER_WRAPPER(XmlDeclHandler, xml_decl)
2291+
SETTER_WRAPPER(ElementDeclHandler, element_decl)
2292+
SETTER_WRAPPER(AttlistDeclHandler, attlist_decl)
2293+
#if XML_COMBINED_VERSION >= 19504
2294+
SETTER_WRAPPER(SkippedEntityHandler, parser_and_data_and_int)
2295+
#endif
2296+
#undef SETTER_WRAPPER
2297+
22252298
static struct HandlerInfo handler_info[] = {
22262299

22272300
// The cast to `xmlhandlersetter` is needed as the signature of XML
22282301
// handler functions is not compatible with `xmlhandlersetter` since
22292302
// their second parameter is narrower than a `const void *`.
22302303
#define HANDLER_INFO(name) \
2231-
{#name, (xmlhandlersetter)XML_Set##name, my_##name},
2304+
{#name, (xmlhandlersetter)pyexpat_Set##name, (xmlhandler)my_##name},
22322305

22332306
HANDLER_INFO(StartElementHandler)
22342307
HANDLER_INFO(EndElementHandler)

0 commit comments

Comments
 (0)