Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 3 additions & 3 deletions Lib/asyncio/base_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -1937,7 +1937,7 @@ def call_exception_handler(self, context):

def _add_callback(self, handle):
"""Add a Handle to _ready."""
if not handle._cancelled:
if not handle.cancelled():
self._ready.append(handle)

def _add_callback_signalsafe(self, handle):
Expand Down Expand Up @@ -1966,7 +1966,7 @@ def _run_once(self):
# is too high
new_scheduled = []
for handle in self._scheduled:
if handle._cancelled:
if handle.cancelled():
handle._scheduled = False
else:
new_scheduled.append(handle)
Expand Down Expand Up @@ -2016,7 +2016,7 @@ def _run_once(self):
ntodo = len(self._ready)
for i in range(ntodo):
handle = self._ready.popleft()
if handle._cancelled:
if handle.cancelled():
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This check is redundant because _run checks now

continue
if self._debug:
try:
Expand Down
82 changes: 57 additions & 25 deletions Lib/asyncio/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,13 @@

from . import format_helpers

_HANDLE_CANCELLED = object()


class Handle:
"""Object returned by callback registration methods."""

__slots__ = ('_callback', '_args', '_cancelled', '_loop',
__slots__ = ('_callback_args', '_loop',
'_source_traceback', '_repr', '__weakref__',
'_context')

Expand All @@ -47,60 +49,89 @@ def __init__(self, callback, args, loop, context=None):
context = contextvars.copy_context()
self._context = context
self._loop = loop
self._callback = callback
self._args = args
self._cancelled = False
self._callback_args = (callback, args)
self._repr = None
if self._loop.get_debug():
self._source_traceback = format_helpers.extract_stack(
sys._getframe(1))
else:
self._source_traceback = None

def _repr_info(self):
def _repr_info(self, cancelling, callback_args):
info = [self.__class__.__name__]
if self._cancelled:
if cancelling:
info.append('cancelled')
if callback_args is _HANDLE_CANCELLED:
info.append('cancelled')
if self._callback is not None:
callback = None
args = None
else:
callback, args = callback_args

if callback is not None:
info.append(format_helpers._format_callback_source(
self._callback, self._args,
callback, args,
debug=self._loop.get_debug()))
if self._source_traceback:
frame = self._source_traceback[-1]
info.append(f'created at {frame[0]}:{frame[1]}')
return info

def _repr_atomic(self, cancelling, callback_args):
info = self._repr_info(cancelling, callback_args)
return '<{}>'.format(' '.join(info))

def __repr__(self):
if self._repr is not None:
return self._repr
info = self._repr_info()
return '<{}>'.format(' '.join(info))
return self._repr_atomic(cancelling=False, callback_args=self._callback_args)

def get_context(self):
return self._context

def cancel(self):
if not self._cancelled:
self._cancelled = True
callback_args = self._callback_args
self._callback_args = _HANDLE_CANCELLED
if callback_args is not _HANDLE_CANCELLED:
if self._loop.get_debug():
# Keep a representation in debug mode to keep callback and
# parameters. For example, to log the warning
# "Executing <Handle...> took 2.5 second"
self._repr = repr(self)
self._callback = None
self._args = None
self._repr = self._repr_atomic(cancelling=True, callback_args=callback_args)

def cancelled(self):
return self._cancelled
return self._callback_args is _HANDLE_CANCELLED

@property
def _cancelled(self):
return self.cancelled()

@property
def _callback(self):
callback_args = self._callback_args
if callback_args is _HANDLE_CANCELLED:
return None
return callback_args[0]

@property
def _args(self):
callback_args = self._callback_args
if callback_args is _HANDLE_CANCELLED:
return None
return callback_args[1]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

the tests access private attributes - I'd rather update those tests. These properties were just to get a PR up and passing CI quickly.


def _run(self):
callback_args = self._callback_args
if callback_args is _HANDLE_CANCELLED:
return
callback, args = callback_args
try:
self._context.run(self._callback, *self._args)
self._context.run(callback, *args)
except (SystemExit, KeyboardInterrupt):
raise
except BaseException as exc:
cb = format_helpers._format_callback_source(
self._callback, self._args,
callback, args,
debug=self._loop.get_debug())
msg = f'Exception in callback {cb}'
context = {
Expand All @@ -111,6 +142,9 @@ def _run(self):
if self._source_traceback:
context['source_traceback'] = self._source_traceback
self._loop.call_exception_handler(context)
callback = None
args = None
callback_args = None
self = None # Needed to break cycles when an exception occurs.


Expand All @@ -126,9 +160,9 @@ def __init__(self, when, callback, args, loop, context=None):
self._when = when
self._scheduled = False

def _repr_info(self):
info = super()._repr_info()
pos = 2 if self._cancelled else 1
def _repr_info(self, cancelling, callback_args):
info = super()._repr_info(cancelling, callback_args)
pos = 2 if (cancelling or callback_args is _HANDLE_CANCELLED) else 1
info.insert(pos, f'when={self._when}')
return info

Expand Down Expand Up @@ -158,13 +192,11 @@ def __ge__(self, other):
def __eq__(self, other):
if isinstance(other, TimerHandle):
return (self._when == other._when and
self._callback == other._callback and
self._args == other._args and
self._cancelled == other._cancelled)
self._callback_args == other._callback_args)
return NotImplemented

def cancel(self):
if not self._cancelled:
if not self.cancelled():
self._loop._timer_handle_cancelled(self)
super().cancel()

Expand Down
2 changes: 1 addition & 1 deletion Lib/asyncio/unix_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ def _handle_signal(self, sig):
handle = self._signal_handlers.get(sig)
if handle is None:
return # Assume it's some race condition.
if handle._cancelled:
if handle.cancelled():
self.remove_signal_handler(sig) # Remove it properly.
else:
self._add_callback_signalsafe(handle)
Expand Down
4 changes: 2 additions & 2 deletions Lib/test/test_asyncio/test_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -1810,11 +1810,11 @@ def call_later(delay, callback, *args):
loop.call_later = call_later
test_utils.run_briefly(loop)

self.assertFalse(handle._cancelled)
self.assertFalse(handle.cancelled())

t.cancel()
test_utils.run_briefly(loop)
self.assertTrue(handle._cancelled)
self.assertTrue(handle.cancelled())

def test_task_cancel_sleeping_task(self):

Expand Down
Loading