Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions Doc/library/asyncio-task.rst
Original file line number Diff line number Diff line change
Expand Up @@ -843,17 +843,13 @@ Timeouts
Wait for the *fut* :ref:`awaitable <asyncio-awaitables>`
to complete with a timeout.

If *fut* is a coroutine it is automatically scheduled as a Task.

*timeout* can either be ``None`` or a float or int number of seconds
to wait for. If *timeout* is ``None``, block until the future
completes.

If a timeout occurs, it cancels the task and raises
:exc:`TimeoutError`.
If a timeout occurs, it cancels *fut* and raises :exc:`TimeoutError`.

To avoid the task :meth:`cancellation <Task.cancel>`,
wrap it in :func:`shield`.
To prevent *fut* from being cancelled, wrap it in :func:`shield`.

The function will wait until the future is actually cancelled,
so the total wait time may exceed the *timeout*. If an exception
Expand Down Expand Up @@ -894,6 +890,10 @@ Timeouts
.. versionchanged:: 3.11
Raises :exc:`TimeoutError` instead of :exc:`asyncio.TimeoutError`.

.. versionchanged:: 3.12
Implemented using :func:`asyncio.timeout`, a coroutine passed as *fut*
is no longer wrapped in a :class:`Task` when *timeout* is positive.


Waiting primitives
==================
Expand Down
12 changes: 11 additions & 1 deletion Lib/asyncio/streams.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,17 @@ def connection_made(self, transport):
self._over_ssl = transport.get_extra_info('sslcontext') is not None
if self._client_connected_cb is not None:
writer = StreamWriter(transport, self, reader, self._loop)
res = self._client_connected_cb(reader, writer)
try:
res = self._client_connected_cb(reader, writer)
except Exception as exc:
self._loop.call_exception_handler({
'message': 'Unhandled exception in client_connected_cb',
'exception': exc,
'transport': transport,
})
transport.close()
self._strong_reader = None
return
if coroutines.iscoroutine(res):
def callback(task):
if task.cancelled():
Expand Down
10 changes: 4 additions & 6 deletions Lib/asyncio/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -440,15 +440,13 @@ def _release_waiter(waiter, *args):
async def wait_for(fut, timeout):
"""Wait for the single Future or coroutine to complete, with timeout.

Coroutine will be wrapped in Task.

Returns result of the Future or coroutine. When a timeout occurs,
it cancels the task and raises TimeoutError. To avoid the task
cancellation, wrap it in shield().
it cancels fut and raises TimeoutError. To prevent fut from being
cancelled, wrap it in shield().

If the wait is cancelled, the task is also cancelled.
If the wait is cancelled, fut is also cancelled.

If the task suppresses the cancellation and returns a value instead,
If fut suppresses the cancellation and returns a value instead,
that value is returned.

This function is a coroutine.
Expand Down
10 changes: 8 additions & 2 deletions Lib/concurrent/interpreters/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,14 @@ def create():

def list_all():
"""Return all existing interpreters."""
return [Interpreter(id, _whence=whence)
for id, whence in _interpreters.list_all(require_ready=True)]
interps = []
for id, whence in _interpreters.list_all(require_ready=True):
try:
interps.append(Interpreter(id, _whence=whence))
except InterpreterNotFoundError:
# It was destroyed after it was listed.
pass
return interps


def get_current():
Expand Down
10 changes: 10 additions & 0 deletions Lib/test/_isolated_sample.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import sys
import time
import unittest
from test import support
from test.support import isolation

# DurationSample sleeps this long in the subprocess; a parent-reported duration
Expand Down Expand Up @@ -178,3 +179,12 @@ class TimeoutSample(unittest.TestCase):
@isolation.runInSubprocess(timeout=TIMEOUT)
def test_hang(self):
time.sleep(TIMEOUT_HANG)


class BigmemSample(unittest.TestCase):

@support.bigmemtest(size=1024, memuse=1)
def test_where_it_runs(self, size):
# A real run is isolated by bigmemtest() itself, a dummy run is not.
self.assertEqual(isolation.runningInSubprocess,
bool(support.real_max_memuse))
49 changes: 47 additions & 2 deletions Lib/test/clinic.test.c
Original file line number Diff line number Diff line change
Expand Up @@ -5431,14 +5431,53 @@ Test_property_set(PyObject *self, PyObject *value, void *Py_UNUSED(context))
{
int return_value;

if (value == NULL) {
PyErr_Format(PyExc_AttributeError,
"attribute 'property' of '%.100s' objects cannot be deleted",
Py_TYPE(self)->tp_name);
return -1;
}
return_value = Test_property_set_impl((TestObj *)self, value);

return return_value;
}

static int
Test_property_set_impl(TestObj *self, PyObject *value)
/*[clinic end generated code: output=49f925ab2a33b637 input=3bc3f46a23c83a88]*/
/*[clinic end generated code: output=ec103a151cf51d25 input=3bc3f46a23c83a88]*/

/*[clinic input]
@setter
@deleter
Test.settable_and_deletable
[clinic start generated code]*/

#if !defined(Test_settable_and_deletable_DOCSTR)
# define Test_settable_and_deletable_DOCSTR NULL
#endif
#if defined(TEST_SETTABLE_AND_DELETABLE_GETSETDEF)
# undef TEST_SETTABLE_AND_DELETABLE_GETSETDEF
# define TEST_SETTABLE_AND_DELETABLE_GETSETDEF {"settable_and_deletable", (getter)Test_settable_and_deletable_get, (setter)Test_settable_and_deletable_set, Test_settable_and_deletable_DOCSTR},
#else
# define TEST_SETTABLE_AND_DELETABLE_GETSETDEF {"settable_and_deletable", NULL, (setter)Test_settable_and_deletable_set, NULL},
#endif

static int
Test_settable_and_deletable_set_impl(TestObj *self, PyObject *value);

static int
Test_settable_and_deletable_set(PyObject *self, PyObject *value, void *Py_UNUSED(context))
{
int return_value;

return_value = Test_settable_and_deletable_set_impl((TestObj *)self, value);

return return_value;
}

static int
Test_settable_and_deletable_set_impl(TestObj *self, PyObject *value)
/*[clinic end generated code: output=479986d499b2f56d input=f5647f3511b9daea]*/

/*[clinic input]
@setter
Expand All @@ -5463,14 +5502,20 @@ Test_setter_first_with_docstr_set(PyObject *self, PyObject *value, void *Py_UNUS
{
int return_value;

if (value == NULL) {
PyErr_Format(PyExc_AttributeError,
"attribute 'setter_first_with_docstr' of '%.100s' objects cannot be deleted",
Py_TYPE(self)->tp_name);
return -1;
}
return_value = Test_setter_first_with_docstr_set_impl((TestObj *)self, value);

return return_value;
}

static int
Test_setter_first_with_docstr_set_impl(TestObj *self, PyObject *value)
/*[clinic end generated code: output=5aaf44373c0af545 input=31a045ce11bbe961]*/
/*[clinic end generated code: output=eac8bafcaa50aa51 input=31a045ce11bbe961]*/

/*[clinic input]
@getter
Expand Down
40 changes: 0 additions & 40 deletions Lib/test/memory_watchdog.py

This file was deleted.

62 changes: 32 additions & 30 deletions Lib/test/support/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1270,26 +1270,17 @@ def set_memlimit(limit: str) -> None:
max_memuse = memlimit


class _MemoryWatchdog:
"""An object which periodically watches the process' memory consumption
and prints it out.
"""

def __init__(self):
self.started = False
def _memory_watchdog(pid):
"""Return a function printing the memory usage of process *pid*."""
# Imported here: test.support does not depend on test.libregrtest.
from test.libregrtest.utils import get_process_memory_usage

def start(self):
import subprocess
watchdog_script = findfile("memory_watchdog.py")
cmd = [sys.executable, watchdog_script, str(os.getpid())]
self.mem_watchdog = subprocess.Popen(cmd)
self.started = True

def stop(self):
if not self.started:
return
self.mem_watchdog.terminate()
self.mem_watchdog.wait()
def watch():
mem = get_process_memory_usage(pid)
if mem is not None:
print(f" ... process data size: {mem / (1024 ** 3):.1f} GiB",
flush=True)
return watch


def bigmemtest(size, memuse, dry_run=True):
Expand All @@ -1304,8 +1295,14 @@ def bigmemtest(size, memuse, dry_run=True):
extra argument. If 'dry_run' is true, the value passed to the test method
may be less than the requested value. If 'dry_run' is false, it means the
test doesn't support dummy runs when -M is not specified.

A test that actually allocates the requested memory (that is, one run with
-M) runs in a subprocess, so that the memory it uses and the address space
it fragments are released when it ends. A dummy run stays in the process.
"""
def decorator(f):
from test.support import isolation

@functools.wraps(f)
def wrapper(self):
size = wrapper.size
Expand All @@ -1321,20 +1318,25 @@ def wrapper(self):
"not enough memory: %.1fG minimum needed"
% (size * memuse / (1024 ** 3)))

if real_max_memuse and verbose:
if (real_max_memuse and verbose
and not isolation.runningInSubprocess):
print()
peak = (size * memuse) / (1024 ** 3)
print(f" ... expected peak memory use: {peak:.1f} GiB")
watchdog = _MemoryWatchdog()
watchdog.start()
else:
watchdog = None
# Flushed, so that it precedes the memory usage below.
print(f" ... expected peak memory use: {peak:.1f} GiB",
flush=True)

if (real_max_memuse and has_subprocess_support
and not isolation.runningInSubprocess):
# Watch it from here: the output of the subprocess is captured.
cls = type(self)
qualname = f'{cls.__qualname__}.{f.__name__}'
proc = isolation._start_test(cls.__module__, qualname)
watchdog = _memory_watchdog(proc.pid) if verbose else None
isolation._replay_test(self, *proc.wait(tick=watchdog))
return

try:
return f(self, maxsize)
finally:
if watchdog:
watchdog.stop()
return f(self, maxsize)

wrapper.size = size
wrapper.memuse = memuse
Expand Down
Loading
Loading