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
3 changes: 2 additions & 1 deletion Lib/asyncio/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ def _build_graph_for_future(
while coro is not None:
if hasattr(coro, 'cr_await'):
# A native coroutine or duck-type compatible iterator
st.append(FrameCallGraphEntry(coro.cr_frame))
if coro.cr_frame is not None:
st.append(FrameCallGraphEntry(coro.cr_frame))
coro = coro.cr_await
elif hasattr(coro, 'ag_await'):
# A native async generator or duck-type compatible iterator
Expand Down
4 changes: 4 additions & 0 deletions Lib/asyncio/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -562,9 +562,11 @@ def __init__(self, aws, timeout):
self._timeout_handle = None

loop = events.get_event_loop()
self._cur_task = current_task()
todo = {ensure_future(aw, loop=loop) for aw in set(aws)}
for f in todo:
f.add_done_callback(self._handle_completion)
futures.future_add_to_awaited_by(f, self._cur_task)
if todo and timeout is not None:
self._timeout_handle = (
loop.call_later(timeout, self._handle_timeout)
Expand Down Expand Up @@ -595,13 +597,15 @@ def __next__(self):
def _handle_timeout(self):
for f in self._todo:
f.remove_done_callback(self._handle_completion)
futures.future_discard_from_awaited_by(f, self._cur_task)
self._done.put_nowait(None) # Sentinel for _wait_for_one().
self._todo.clear() # Can't do todo.remove(f) in the loop.

def _handle_completion(self, f):
if not self._todo:
return # _handle_timeout() was here first.
self._todo.remove(f)
futures.future_discard_from_awaited_by(f, self._cur_task)
self._done.put_nowait(f)
if not self._todo and self._timeout_handle is not None:
self._timeout_handle.cancel()
Expand Down
6 changes: 4 additions & 2 deletions Lib/configparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -618,7 +618,8 @@ class RawConfigParser(MutableMapping):
_OPT_TMPL = r"""
(?P<option> # very permissive!
(?:(?!{delim})\S)* # non-delimiter non-whitespace
(?:\s+(?:(?!{delim})\S)+)*) # optionally more words
(?:(?:(?!{delim})\s)+ # optionally more
(?:(?!{delim})\S)+)*) # space-separated words
\s*(?P<vi>{delim})\s* # any number of space/tab,
# followed by any of the
# allowed delimiters,
Expand All @@ -628,7 +629,8 @@ class RawConfigParser(MutableMapping):
_OPT_NV_TMPL = r"""
(?P<option> # very permissive!
(?:(?!{delim})\S)* # non-delimiter non-whitespace
(?:\s+(?:(?!{delim})\S)+)*) # optionally more words
(?:(?:(?!{delim})\s)+ # optionally more
(?:(?!{delim})\S)+)*) # space-separated words
\s*(?: # any number of space/tab,
(?P<vi>{delim})\s* # optionally followed by
# any of the allowed
Expand Down
80 changes: 80 additions & 0 deletions Lib/test/test_asyncio/test_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,68 @@ async def main(t1, t2):
]
])

async def test_stack_as_completed(self):
# gh-156523: as_completed() must record the awaiting task
stack_for_inner = None

async def inner():
await asyncio.sleep(0)
nonlocal stack_for_inner
stack_for_inner = capture_test_stack()

async def main(t):
for f in asyncio.as_completed([t]):
await f

t = asyncio.create_task(inner(), name='inner')
await main(t)
self.assertFalse(t._asyncio_awaited_by)

self.assertEqual(stack_for_inner[0], [
'T<inner>',
['s capture_test_stack', 'a inner'],
[
['T<anon>',
['a get', 'a _wait_for_one', 'a main',
'a test_stack_as_completed'],
[]
]
]
])

async def test_stack_as_completed_timeout(self):
# gh-156523: the awaiting task must be dropped when as_completed() times out
stack_for_inner = None

async def inner():
nonlocal stack_for_inner
stack_for_inner = capture_test_stack()
await asyncio.sleep(3600)

async def main(t):
with self.assertRaises(TimeoutError):
for f in asyncio.as_completed([t], timeout=0.01):
await f

t = asyncio.create_task(inner(), name='inner')
await main(t)
self.assertFalse(t._asyncio_awaited_by)
t.cancel()
with self.assertRaises(asyncio.CancelledError):
await t

self.assertEqual(stack_for_inner[0], [
'T<inner>',
['s capture_test_stack', 'a inner'],
[
['T<anon>',
['a get', 'a _wait_for_one', 'a main',
'a test_stack_as_completed_timeout'],
[]
]
]
])

async def test_stack_task(self):

stack_for_inner = None
Expand Down Expand Up @@ -422,6 +484,24 @@ def test_capture_call_graph_non_future(self):
with self.assertRaises(TypeError):
asyncio.capture_call_graph("not a future")

async def test_call_graph_finished_task(self):
# gh-156408: the call graph must not record a finished coroutine's None frame
async def boom():
raise ValueError

done = asyncio.create_task(asyncio.sleep(0), name='done')
failed = asyncio.create_task(boom(), name='failed')
cancelled = asyncio.create_task(asyncio.Event().wait(), name='cancelled')
cancelled.cancel()
await asyncio.gather(done, failed, cancelled, return_exceptions=True)

for task in (done, failed, cancelled):
with self.subTest(task=task.get_name()):
buf = io.StringIO()
asyncio.print_call_graph(task, file=buf)
self.assertEqual(asyncio.capture_call_graph(task).call_stack, ())
self.assertIn(f"name={task.get_name()!r}", buf.getvalue())

async def test_capture_call_graph_no_current_task(self):
results = []

Expand Down
33 changes: 30 additions & 3 deletions Lib/test/test_configparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class CfgParserTestCaseClass:
default_section = configparser.DEFAULTSECT
interpolation = configparser._UNSET

def newconfig(self, defaults=None):
def newconfig(self, defaults=None, **kwargs):
arguments = dict(
defaults=defaults,
allow_no_value=self.allow_no_value,
Expand All @@ -56,6 +56,7 @@ def newconfig(self, defaults=None):
default_section=self.default_section,
interpolation=self.interpolation,
)
arguments.update(kwargs)
instance = self.config_class(**arguments)
return instance

Expand Down Expand Up @@ -358,6 +359,32 @@ def test_basic(self):
the larch {0[1]} 1
""".format(self.delimiters)))

@support.subTests('data', [
'foo bar=baz',
'foo bar=baz',
'foo=bar=baz',
'foo = bar=baz',
'foo\t \t=\t \tbar=baz',
])
def test_space_delimiter(self, data):
# gh-156353: Space should be accepted as a delimiter
cf = self.newconfig(delimiters=(' ', '='))
cf.read_string(f"[all]\n{data}")
self.assertEqual(cf.options('all'), ['foo'])
self.assertEqual(cf.get('all', 'foo'), 'bar=baz')

@support.subTests('delimiter', ' =:;#x\t\0\N{RS}\N{CEDILLA}\N{CAT}')
@support.subTests('space_before', ['', ' ', '\t', ' \t'])
@support.subTests('space_after', ['', ' ', '\t', ' \t'])
def test_any_delimiter(self, delimiter, space_before, space_after):
cf = self.newconfig(
delimiters=(delimiter,),
inline_comment_prefixes=None,
)
cf.read_string(f"[all]\nfoo{space_before}{delimiter}{space_after}bar=baz")
self.assertEqual(cf.options('all'), ['foo'])
self.assertEqual(cf.get('all', 'foo'), 'bar=baz')

def test_basic_from_dict(self):
config = {
"Foo Bar": {
Expand Down Expand Up @@ -1991,8 +2018,8 @@ class ConvertersTestCase(BasicTestCase, unittest.TestCase):

config_class = configparser.ConfigParser

def newconfig(self, defaults=None):
instance = super().newconfig(defaults=defaults)
def newconfig(self, defaults=None, **kwargs):
instance = super().newconfig(defaults=defaults, **kwargs)
instance.converters['list'] = lambda v: [e.strip() for e in v.split()
if e.strip()]
return instance
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Speed up :meth:`~datetime.date.timetuple` and :meth:`~datetime.date.strftime`
by caching the ``time.struct_time`` and ``time.strftime`` lookups, and
:meth:`~datetime.datetime.today` by reading the clock directly instead of
calling :func:`time.time`. Patching :mod:`time` after importing
:mod:`datetime` no longer affects these methods. Patch by Maurycy
Pawłowski-Wieroński.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix :mod:`configparser` parsing when using whitespace in *delimiters*.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix :func:`asyncio.print_call_graph` raising :exc:`AttributeError` when
called on a task that has already finished.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix :func:`asyncio.as_completed` not recording the awaiting task in the call
graph.
60 changes: 35 additions & 25 deletions Modules/_datetimemodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ typedef struct {

/* The interned Unix epoch datetime instance */
PyObject *epoch;

PyObject *time_struct_time;
PyObject *time_strftime;
} datetime_state;

/* The module has a fixed number of static objects, due to being exposed
Expand Down Expand Up @@ -1892,10 +1895,12 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple,
assert(object && format && timetuple);
assert(PyUnicode_Check(format));

PyObject *strftime = PyImport_ImportModuleAttrString("time", "strftime");
if (strftime == NULL) {
PyObject *current_mod = NULL;
datetime_state *st = GET_CURRENT_STATE(current_mod);
if (st == NULL) {
return NULL;
}
PyObject *strftime = st->time_strftime;

/* Scan the input format, looking for %z/%Z/%f escapes, building
* a new format. Since computing the replacements for those codes
Expand Down Expand Up @@ -2055,7 +2060,7 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple,
Py_XDECREF(zreplacement);
Py_XDECREF(colonzreplacement);
Py_XDECREF(Zreplacement);
Py_XDECREF(strftime);
RELEASE_CURRENT_STATE(st, current_mod);
return result;

Error:
Expand All @@ -2068,41 +2073,26 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple,
* from C. Perhaps they should be.
*/

/* Call time.time() and return its result (a Python float). */
static PyObject *
time_time(void)
{
PyObject *result = NULL;
PyObject *time = PyImport_ImportModuleAttrString("time", "time");

if (time != NULL) {
result = PyObject_CallNoArgs(time);
Py_DECREF(time);
}
return result;
}

/* Build a time.struct_time. The weekday and day number are automatically
* computed from the y,m,d args.
*/
static PyObject *
build_struct_time(int y, int m, int d, int hh, int mm, int ss, int dstflag)
{
PyObject *struct_time;
PyObject *result;

struct_time = PyImport_ImportModuleAttrString("time", "struct_time");
if (struct_time == NULL) {
PyObject *current_mod = NULL;
datetime_state *st = GET_CURRENT_STATE(current_mod);
if (st == NULL) {
return NULL;
}

result = PyObject_CallFunction(struct_time, "((iiiiiiiii))",
PyObject *result = PyObject_CallFunction(st->time_struct_time,
"((iiiiiiiii))",
y, m, d,
hh, mm, ss,
weekday(y, m, d),
days_before_month(y, m) + d,
dstflag);
Py_DECREF(struct_time);
RELEASE_CURRENT_STATE(st, current_mod);
return result;
}

Expand Down Expand Up @@ -3337,7 +3327,11 @@ datetime_date_today_impl(PyTypeObject *type)
type);
}

PyObject *time = time_time();
PyTime_t ts;
if (PyTime_Time(&ts) < 0) {
return NULL;
}
PyObject *time = PyFloat_FromDouble(PyTime_AsSecondsDouble(ts));
if (time == NULL) {
return NULL;
}
Expand Down Expand Up @@ -7449,6 +7443,8 @@ init_state(datetime_state *st, PyObject *module, PyObject *old_module)
.us_per_week = Py_NewRef(st_old->us_per_week),
.seconds_per_day = Py_NewRef(st_old->seconds_per_day),
.epoch = Py_NewRef(st_old->epoch),
.time_struct_time = Py_NewRef(st_old->time_struct_time),
.time_strftime = Py_NewRef(st_old->time_strftime),
};
return 0;
}
Expand Down Expand Up @@ -7493,6 +7489,15 @@ init_state(datetime_state *st, PyObject *module, PyObject *old_module)
return -1;
}

st->time_struct_time = PyImport_ImportModuleAttrString("time", "struct_time");
if (st->time_struct_time == NULL) {
return -1;
}
st->time_strftime = PyImport_ImportModuleAttrString("time", "strftime");
if (st->time_strftime == NULL) {
return -1;
}

return 0;
}

Expand All @@ -7502,6 +7507,9 @@ traverse_state(datetime_state *st, visitproc visit, void *arg)
/* heap types */
Py_VISIT(st->isocalendar_date_type);

Py_VISIT(st->time_struct_time);
Py_VISIT(st->time_strftime);

return 0;
}

Expand All @@ -7517,6 +7525,8 @@ clear_state(datetime_state *st)
Py_CLEAR(st->us_per_week);
Py_CLEAR(st->seconds_per_day);
Py_CLEAR(st->epoch);
Py_CLEAR(st->time_struct_time);
Py_CLEAR(st->time_strftime);
return 0;
}

Expand Down
Loading