Skip to content

Commit b089958

Browse files
committed
Merge remote-tracking branch 'upstream/main' into pythonrun
2 parents 0b538ce + 51d693c commit b089958

18 files changed

+191
-53
lines changed

Include/cpython/pyerrors.h

+4
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,10 @@ PyAPI_FUNC(PyObject *) _PyErr_FormatFromCause(
112112

113113
/* In exceptions.c */
114114

115+
PyAPI_FUNC(int) _PyException_AddNote(
116+
PyObject *exc,
117+
PyObject *note);
118+
115119
/* Helper that attempts to replace the current exception with one of the
116120
* same type but with a prefix added to the exception text. The resulting
117121
* exception description looks like:

Lib/asyncio/tasks.py

+6-16
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
from . import exceptions
2626
from . import futures
2727
from . import timeouts
28-
from .coroutines import _is_coroutine
2928

3029
# Helper to generate new task names
3130
# This uses itertools.count() instead of a "+= 1" operation because the latter
@@ -635,11 +634,14 @@ def ensure_future(coro_or_future, *, loop=None):
635634
raise ValueError('The future belongs to a different loop than '
636635
'the one specified as the loop argument')
637636
return coro_or_future
638-
called_wrap_awaitable = False
637+
should_close = True
639638
if not coroutines.iscoroutine(coro_or_future):
640639
if inspect.isawaitable(coro_or_future):
640+
async def _wrap_awaitable(awaitable):
641+
return await awaitable
642+
641643
coro_or_future = _wrap_awaitable(coro_or_future)
642-
called_wrap_awaitable = True
644+
should_close = False
643645
else:
644646
raise TypeError('An asyncio.Future, a coroutine or an awaitable '
645647
'is required')
@@ -649,23 +651,11 @@ def ensure_future(coro_or_future, *, loop=None):
649651
try:
650652
return loop.create_task(coro_or_future)
651653
except RuntimeError:
652-
if not called_wrap_awaitable:
654+
if should_close:
653655
coro_or_future.close()
654656
raise
655657

656658

657-
@types.coroutine
658-
def _wrap_awaitable(awaitable):
659-
"""Helper for asyncio.ensure_future().
660-
661-
Wraps awaitable (an object with __await__) into a coroutine
662-
that will later be wrapped in a Task by ensure_future().
663-
"""
664-
return (yield from awaitable.__await__())
665-
666-
_wrap_awaitable._is_coroutine = _is_coroutine
667-
668-
669659
class _GatheringFuture(futures.Future):
670660
"""Helper for gather().
671661

Lib/concurrent/futures/process.py

+5
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,11 @@ def run(self):
366366
if self.is_shutting_down():
367367
self.flag_executor_shutting_down()
368368

369+
# When only canceled futures remain in pending_work_items, our
370+
# next call to wait_result_broken_or_wakeup would hang forever.
371+
# This makes sure we have some running futures or none at all.
372+
self.add_call_item_to_queue()
373+
369374
# Since no new work items can be added, it is safe to shutdown
370375
# this thread if there are no pending work items.
371376
if not self.pending_work_items:

Lib/test/test_asyncio/test_tasks.py

+15
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import re
99
import sys
1010
import traceback
11+
import types
1112
import unittest
1213
from unittest import mock
1314
from types import GenericAlias
@@ -274,6 +275,20 @@ async def coro():
274275
loop.run_until_complete(fut)
275276
self.assertEqual(fut.result(), 'ok')
276277

278+
def test_ensure_future_task_awaitable(self):
279+
class Aw:
280+
def __await__(self):
281+
return asyncio.sleep(0, result='ok').__await__()
282+
283+
loop = asyncio.new_event_loop()
284+
self.set_event_loop(loop)
285+
task = asyncio.ensure_future(Aw(), loop=loop)
286+
loop.run_until_complete(task)
287+
self.assertTrue(task.done())
288+
self.assertEqual(task.result(), 'ok')
289+
self.assertIsInstance(task.get_coro(), types.CoroutineType)
290+
loop.close()
291+
277292
def test_ensure_future_neither(self):
278293
with self.assertRaises(TypeError):
279294
asyncio.ensure_future('ok')

Lib/test/test_capi/test_exceptions.py

+20
Original file line numberDiff line numberDiff line change
@@ -169,5 +169,25 @@ class Broken(Exception, metaclass=Meta):
169169
with self.assertRaises(ZeroDivisionError) as e:
170170
_testcapi.exc_set_object(Broken, Broken())
171171

172+
def test_set_object_and_fetch(self):
173+
class Broken(Exception):
174+
def __init__(self, *arg):
175+
raise ValueError("Broken __init__")
176+
177+
exc = _testcapi.exc_set_object_fetch(Broken, 'abcd')
178+
self.assertIsInstance(exc, ValueError)
179+
self.assertEqual(exc.__notes__[0],
180+
"Normalization failed: type=Broken args='abcd'")
181+
182+
class BadArg:
183+
def __repr__(self):
184+
raise TypeError('Broken arg type')
185+
186+
exc = _testcapi.exc_set_object_fetch(Broken, BadArg())
187+
self.assertIsInstance(exc, ValueError)
188+
self.assertEqual(exc.__notes__[0],
189+
'Normalization failed: type=Broken args=<unknown>')
190+
191+
172192
if __name__ == "__main__":
173193
unittest.main()

Lib/test/test_concurrent_futures.py

+28
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from logging.handlers import QueueHandler
1515
import os
1616
import queue
17+
import signal
1718
import sys
1819
import threading
1920
import time
@@ -397,6 +398,33 @@ def test_hang_gh83386(self):
397398
self.assertFalse(err)
398399
self.assertEqual(out.strip(), b"apple")
399400

401+
def test_hang_gh94440(self):
402+
"""shutdown(wait=True) doesn't hang when a future was submitted and
403+
quickly canceled right before shutdown.
404+
405+
See https://github.com/python/cpython/issues/94440.
406+
"""
407+
if not hasattr(signal, 'alarm'):
408+
raise unittest.SkipTest(
409+
"Tested platform does not support the alarm signal")
410+
411+
def timeout(_signum, _frame):
412+
raise RuntimeError("timed out waiting for shutdown")
413+
414+
kwargs = {}
415+
if getattr(self, 'ctx', None):
416+
kwargs['mp_context'] = self.get_context()
417+
executor = self.executor_type(max_workers=1, **kwargs)
418+
executor.submit(int).result()
419+
old_handler = signal.signal(signal.SIGALRM, timeout)
420+
try:
421+
signal.alarm(5)
422+
executor.submit(int).cancel()
423+
executor.shutdown(wait=True)
424+
finally:
425+
signal.alarm(0)
426+
signal.signal(signal.SIGALRM, old_handler)
427+
400428

401429
class ThreadPoolShutdownTest(ThreadPoolMixin, ExecutorShutdownTest, BaseTestCase):
402430
def test_threads_terminate(self):

Lib/webbrowser.py

+8-4
Original file line numberDiff line numberDiff line change
@@ -542,11 +542,15 @@ def register_standard_browsers():
542542
# First try to use the default Windows browser
543543
register("windows-default", WindowsDefault)
544544

545-
# Detect some common Windows browsers, fallback to IE
546-
iexplore = os.path.join(os.environ.get("PROGRAMFILES", "C:\\Program Files"),
547-
"Internet Explorer\\IEXPLORE.EXE")
545+
# Detect some common Windows browsers, fallback to Microsoft Edge
546+
# location in 64-bit Windows
547+
edge64 = os.path.join(os.environ.get("PROGRAMFILES(x86)", "C:\\Program Files (x86)"),
548+
"Microsoft\\Edge\\Application\\msedge.exe")
549+
# location in 32-bit Windows
550+
edge32 = os.path.join(os.environ.get("PROGRAMFILES", "C:\\Program Files"),
551+
"Microsoft\\Edge\\Application\\msedge.exe")
548552
for browser in ("firefox", "firebird", "seamonkey", "mozilla",
549-
"netscape", "opera", iexplore):
553+
"opera", edge64, edge32):
550554
if shutil.which(browser):
551555
register(browser, None, BackgroundBrowser(browser))
552556
else:

Misc/ACKS

+1
Original file line numberDiff line numberDiff line change
@@ -1385,6 +1385,7 @@ Thomas Perl
13851385
Mathieu Perreault
13861386
Mark Perrego
13871387
Trevor Perrin
1388+
Yonatan Perry
13881389
Gabriel de Perthuis
13891390
Tim Peters
13901391
Benjamin Peterson
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add note to exception raised in ``PyErr_SetObject`` when normalization fails.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix a :mod:`concurrent.futures.process` bug where ``ProcessPoolExecutor`` shutdown
2+
could hang after a future has been quickly submitted and canceled.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
:meth:`asyncio.Task.get_coro` now always returns a coroutine when wrapping an awaitable object. Patch by Kumar Aditya.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Update :mod:`webbrowser` to fall back to Microsoft Edge instead of Internet Explorer.

Modules/_testcapi/exceptions.c

+21
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,26 @@ exc_set_object(PyObject *self, PyObject *args)
9292
return NULL;
9393
}
9494

95+
static PyObject *
96+
exc_set_object_fetch(PyObject *self, PyObject *args)
97+
{
98+
PyObject *exc;
99+
PyObject *obj;
100+
PyObject *type;
101+
PyObject *value;
102+
PyObject *tb;
103+
104+
if (!PyArg_ParseTuple(args, "OO:exc_set_object", &exc, &obj)) {
105+
return NULL;
106+
}
107+
108+
PyErr_SetObject(exc, obj);
109+
PyErr_Fetch(&type, &value, &tb);
110+
Py_XDECREF(type);
111+
Py_XDECREF(tb);
112+
return value;
113+
}
114+
95115
static PyObject *
96116
raise_exception(PyObject *self, PyObject *args)
97117
{
@@ -262,6 +282,7 @@ static PyMethodDef test_methods[] = {
262282
{"make_exception_with_doc", _PyCFunction_CAST(make_exception_with_doc),
263283
METH_VARARGS | METH_KEYWORDS},
264284
{"exc_set_object", exc_set_object, METH_VARARGS},
285+
{"exc_set_object_fetch", exc_set_object_fetch, METH_VARARGS},
265286
{"raise_exception", raise_exception, METH_VARARGS},
266287
{"raise_memoryerror", raise_memoryerror, METH_NOARGS},
267288
{"set_exc_info", test_set_exc_info, METH_VARARGS},

Objects/exceptions.c

+15
Original file line numberDiff line numberDiff line change
@@ -3749,6 +3749,21 @@ _PyExc_Fini(PyInterpreterState *interp)
37493749
_PyExc_FiniTypes(interp);
37503750
}
37513751

3752+
int
3753+
_PyException_AddNote(PyObject *exc, PyObject *note)
3754+
{
3755+
if (!PyExceptionInstance_Check(exc)) {
3756+
PyErr_Format(PyExc_TypeError,
3757+
"exc must be an exception, not '%s'",
3758+
Py_TYPE(exc)->tp_name);
3759+
return -1;
3760+
}
3761+
PyObject *r = BaseException_add_note(exc, note);
3762+
int res = r == NULL ? -1 : 0;
3763+
Py_XDECREF(r);
3764+
return res;
3765+
}
3766+
37523767
/* Helper to do the equivalent of "raise X from Y" in C, but always using
37533768
* the current exception rather than passing one in.
37543769
*

Python/bltinmodule.c

-3
Original file line numberDiff line numberDiff line change
@@ -3098,9 +3098,6 @@ _PyBuiltin_Init(PyInterpreterState *interp)
30983098
}
30993099
Py_DECREF(debug);
31003100

3101-
/* m_copy of Py_None means it is copied some other way. */
3102-
builtinsmodule.m_base.m_copy = Py_NewRef(Py_None);
3103-
31043101
return mod;
31053102
#undef ADD_TO_ALL
31063103
#undef SETBUILTIN

Python/errors.c

+35-5
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,28 @@ _PyErr_GetTopmostException(PyThreadState *tstate)
135135
return exc_info;
136136
}
137137

138+
static PyObject *
139+
get_normalization_failure_note(PyThreadState *tstate, PyObject *exception, PyObject *value)
140+
{
141+
PyObject *args = PyObject_Repr(value);
142+
if (args == NULL) {
143+
_PyErr_Clear(tstate);
144+
args = PyUnicode_FromFormat("<unknown>");
145+
}
146+
PyObject *note;
147+
const char *tpname = ((PyTypeObject*)exception)->tp_name;
148+
if (args == NULL) {
149+
_PyErr_Clear(tstate);
150+
note = PyUnicode_FromFormat("Normalization failed: type=%s", tpname);
151+
}
152+
else {
153+
note = PyUnicode_FromFormat("Normalization failed: type=%s args=%S",
154+
tpname, args);
155+
Py_DECREF(args);
156+
}
157+
return note;
158+
}
159+
138160
void
139161
_PyErr_SetObject(PyThreadState *tstate, PyObject *exception, PyObject *value)
140162
{
@@ -160,19 +182,27 @@ _PyErr_SetObject(PyThreadState *tstate, PyObject *exception, PyObject *value)
160182
Py_XINCREF(value);
161183
if (!is_subclass) {
162184
/* We must normalize the value right now */
163-
PyObject *fixed_value;
164185

165186
/* Issue #23571: functions must not be called with an
166187
exception set */
167188
_PyErr_Clear(tstate);
168189

169-
fixed_value = _PyErr_CreateException(exception, value);
170-
Py_XDECREF(value);
190+
PyObject *fixed_value = _PyErr_CreateException(exception, value);
171191
if (fixed_value == NULL) {
192+
PyObject *exc = _PyErr_GetRaisedException(tstate);
193+
assert(PyExceptionInstance_Check(exc));
194+
195+
PyObject *note = get_normalization_failure_note(tstate, exception, value);
196+
Py_XDECREF(value);
197+
if (note != NULL) {
198+
/* ignore errors in _PyException_AddNote - they will be overwritten below */
199+
_PyException_AddNote(exc, note);
200+
Py_DECREF(note);
201+
}
202+
_PyErr_SetRaisedException(tstate, exc);
172203
return;
173204
}
174-
175-
value = fixed_value;
205+
Py_XSETREF(value, fixed_value);
176206
}
177207

178208
exc_value = _PyErr_GetTopmostException(tstate)->exc_value;

0 commit comments

Comments
 (0)