Skip to content

Commit 968a8d1

Browse files
authored
Merge branch 'main' into fix/thread-ident-154937
2 parents 3efdf4f + 204feba commit 968a8d1

8 files changed

Lines changed: 54 additions & 10 deletions

File tree

Doc/library/functools.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -386,9 +386,9 @@ The :mod:`!functools` module defines the following functions:
386386
only one positional argument is provided, but there are two placeholders
387387
that must be filled in.
388388

389-
If :func:`!partial` is applied to an existing :func:`!partial` object,
390-
:data:`!Placeholder` sentinels of the input object are filled in with
391-
new positional arguments.
389+
If :func:`!partial` is applied to an existing
390+
:ref:`partial object <partial-objects>`, :data:`!Placeholder` sentinels of the
391+
input object are filled in with new positional arguments.
392392
A placeholder can be retained by inserting a new
393393
:data:`!Placeholder` sentinel to the place held by a previous :data:`!Placeholder`:
394394

Lib/logging/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1709,7 +1709,11 @@ def removeHandler(self, hdlr):
17091709
"""
17101710
with _lock:
17111711
if hdlr in self.handlers:
1712-
self.handlers.remove(hdlr)
1712+
# Replace the list instead of mutating it in place, so that
1713+
# callHandlers() can iterate it without a lock (gh-79366).
1714+
handlers = self.handlers.copy()
1715+
handlers.remove(hdlr)
1716+
self.handlers = handlers
17131717

17141718
def hasHandlers(self):
17151719
"""

Lib/test/test_codecs.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import _codecs
12
import codecs
23
import contextlib
34
import copy
@@ -3735,6 +3736,17 @@ def test_encode_errors(self):
37353736
self.assertEqual(codecs.iconv_encode(enc, 'a€b', 'xmlcharrefreplace')[0],
37363737
b'a&#8364;b')
37373738

3739+
def test_encode_errors_unencodable_replacement(self):
3740+
# Encoding the replacement must not call the error handler again.
3741+
enc = self.require('ASCII')
3742+
codecs.register_error('test.iconv', lambda exc: ('€', exc.end))
3743+
self.addCleanup(_codecs._unregister_error, 'test.iconv')
3744+
with self.assertRaises(UnicodeEncodeError) as cm:
3745+
codecs.iconv_encode(enc, 'a€b', 'test.iconv')
3746+
self.assertEqual((cm.exception.start, cm.exception.end), (1, 2))
3747+
self.assertEqual(cm.exception.reason,
3748+
'unable to encode error handler result')
3749+
37383750
def test_decode_errors(self):
37393751
enc = self.require('ASCII')
37403752
bad = b'a\xffb'

Lib/test/test_logging.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -814,6 +814,23 @@ def lock_holder_thread_fn():
814814

815815
support.wait_process(pid, exitcode=0)
816816

817+
def test_remove_handler_while_emitting(self):
818+
# Removing a handler while callHandlers() iterates over the handlers
819+
# should not cause the following handlers to be skipped (gh-79366).
820+
logger = logging.Logger('test_remove_handler_while_emitting')
821+
calls = []
822+
class RemovingHandler(logging.Handler):
823+
def emit(self, record):
824+
calls.append('removing')
825+
logger.removeHandler(self)
826+
class CountingHandler(logging.Handler):
827+
def emit(self, record):
828+
calls.append('counting')
829+
logger.addHandler(RemovingHandler())
830+
logger.addHandler(CountingHandler())
831+
logger.error('spam')
832+
self.assertEqual(calls, ['removing', 'counting'])
833+
817834

818835
class BadStream(object):
819836
def write(self, data):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fixed a race condition in :mod:`logging`:
2+
if a handler was removed while a record was being emitted,
3+
the following handlers of the same logger could be skipped.

Modules/_decimal/_decimal.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1696,12 +1696,12 @@ _decimal.Context.copy
16961696
16971697
cls: defining_class
16981698
1699-
Return a duplicate of the context with all flags cleared.
1699+
Return a duplicate of the context.
17001700
[clinic start generated code]*/
17011701

17021702
static PyObject *
17031703
_decimal_Context_copy_impl(PyObject *self, PyTypeObject *cls)
1704-
/*[clinic end generated code: output=31c9c8eeb0c0cf77 input=aef1c0bddabdf8f0]*/
1704+
/*[clinic end generated code: output=31c9c8eeb0c0cf77 input=87f8b92b1c7462a5]*/
17051705
{
17061706
decimal_state *state = PyType_GetModuleState(cls);
17071707

Modules/_decimal/clinic/_decimal.c.h

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Objects/unicodeobject.c

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8520,11 +8520,19 @@ _PyUnicode_EncodeIconv(const char *encoding, PyObject *unicode,
85208520
replen = PyBytes_GET_SIZE(rep);
85218521
}
85228522
else {
8523-
/* A str replacement is encoded through the same codec. */
8523+
/* A str replacement is encoded through the same codec, but
8524+
strictly: handling its errors in turn could never terminate. */
85248525
assert(PyUnicode_Check(rep));
8525-
repbytes = _PyUnicode_EncodeIconv(encoding, rep, errors);
8526+
repbytes = _PyUnicode_EncodeIconv(encoding, rep, NULL);
85268527
Py_DECREF(rep);
85278528
if (repbytes == NULL) {
8529+
if (PyErr_ExceptionMatches(PyExc_UnicodeEncodeError)) {
8530+
/* Report the input the caller knows about, not the
8531+
replacement. */
8532+
PyErr_Clear();
8533+
raise_encode_exception(&exc, encoding, unicode, pos, pos + 1,
8534+
"unable to encode error handler result");
8535+
}
85288536
goto done;
85298537
}
85308538
repdata = PyBytes_AS_STRING(repbytes);

0 commit comments

Comments
 (0)