Skip to content

Commit b089958

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

File tree

18 files changed

+191
-53
lines changed

18 files changed

+191
-53
lines changed

Include/cpython/pyerrors.h

Lines changed: 4 additions & 0 deletions
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

Lines changed: 6 additions & 16 deletions
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

Lines changed: 5 additions & 0 deletions
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

Lines changed: 15 additions & 0 deletions
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

Lines changed: 20 additions & 0 deletions
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

Lines changed: 28 additions & 0 deletions
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

Lines changed: 8 additions & 4 deletions
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

Lines changed: 1 addition & 0 deletions
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
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add note to exception raised in ``PyErr_SetObject`` when normalization fails.
Lines changed: 2 additions & 0 deletions
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.

0 commit comments

Comments
 (0)