Skip to content

gh-109955 : Update state transition comments for asyncio.Task #109910

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Sep 27, 2023
Merged
Changes from 2 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
26 changes: 19 additions & 7 deletions Lib/asyncio/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,27 @@ class Task(futures._PyFuture): # Inherit Python Task implementation
"""A coroutine wrapped in a Future."""

# An important invariant maintained while a Task not done:
# _fut_waiter is either None or a Future. The task can be
# in any of 3 states:
#
# - Either _fut_waiter is None, and _step() is scheduled;
# - or _fut_waiter is some Future, and _step() is *not* scheduled.
# - 1 (_fut_waiter is not None and not _fut_waiter.done());
# __step() is *not* scheduled and the Task is waiting for _fut_waiter.
# - 2a (_fut_waiter is None or _fut_waiter.done()), and __wakeup() is scheduled;
# the Task is waiting for __wakeup() to be executed.
# - 2b (_fut_waiter is None or _fut_waiter.done()), and __step() is scheduled;
# the Task is waiting for __step() to be executed.
# - 3 _fut_waiter is None and __step() is *not* scheduled;
# the Task is currently executing (in __step()).
#
# The only transition from the latter to the former is through
# _wakeup(). When _fut_waiter is not None, one of its callbacks
# must be _wakeup().

# If False, don't log a message if the task is destroyed whereas its
# The transition from 1 to 2a happens when _fut_waiter becomes done(),
# as it schedules __wakeup() to be called.
# The transition from 2a to 2b happens when __wakeup() is executed,
# scheduling __step() to be called, leaving _fut_waiter in place.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This confuses me. Once __wakeup() executes, it proceeds without waiting to call __step() -- there's no scheduling involved that I can see in the source code (unless the .c extension is different). The call to future.result() immediately produces a value or raises -- it doesn't block (there's no await there). I do think it is possible to enter step 2b without going through 2a when __step() is scheduled directly by __init__(). So maybe it's clearer to say that we go through either 2a or 2b?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I may have got my wires crossed here, because it is all confusing. Initially, I thought __wakeup() was called directly (futures just calling registered callbacks) and _wakeup scheduling __step,.. But then it turns out that futures schedule their callbacks... I'll have another look.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you were right. So, there is no 2a or2b anymore just 2.
The main point I'm trying to get across here is that there can be a _fut_waiter present in a done state, i.e. the
mere presence of a _fut_waiter does not indicate that the Task is still blocked. I hope I'm not making this sound more complicated than it is.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the mere presence of a _fut_waiter does not indicate that the Task is still blocked

Yes, that's crucial information.

# In state 1, one of the callbacks of __fut_waiter must be __wakeup().
# It transitions from 2b to 3 when __step() is executed, and it clears
# _fut_waiter to None.

# If False, don't log a message if the task is destroyed while its
# status is still pending
_log_destroy_pending = True

Expand Down