435 lines
12 KiB
Cython
435 lines
12 KiB
Cython
@cython.no_gc_clear
|
|
@cython.freelist(DEFAULT_FREELIST_SIZE)
|
|
cdef class Handle:
|
|
def __cinit__(self):
|
|
self._cancelled = 0
|
|
self.cb_type = 0
|
|
self._source_traceback = None
|
|
|
|
cdef inline _set_loop(self, Loop loop):
|
|
self.loop = loop
|
|
if UVLOOP_DEBUG:
|
|
loop._debug_cb_handles_total += 1
|
|
loop._debug_cb_handles_count += 1
|
|
if loop._debug:
|
|
self._source_traceback = extract_stack()
|
|
|
|
cdef inline _set_context(self, object context):
|
|
if context is None:
|
|
context = Context_CopyCurrent()
|
|
self.context = context
|
|
|
|
def __dealloc__(self):
|
|
if UVLOOP_DEBUG and self.loop is not None:
|
|
self.loop._debug_cb_handles_count -= 1
|
|
if self.loop is None:
|
|
raise RuntimeError('Handle.loop is None in Handle.__dealloc__')
|
|
|
|
def __init__(self):
|
|
raise TypeError(
|
|
'{} is not supposed to be instantiated from Python'.format(
|
|
self.__class__.__name__))
|
|
|
|
cdef inline _run(self):
|
|
cdef:
|
|
int cb_type
|
|
object callback
|
|
|
|
if self._cancelled:
|
|
return
|
|
|
|
cb_type = self.cb_type
|
|
|
|
# Since _run is a cdef and there's no BoundMethod,
|
|
# we guard 'self' manually (since the callback
|
|
# might cause GC of the handle.)
|
|
Py_INCREF(self)
|
|
|
|
try:
|
|
assert self.context is not None
|
|
Context_Enter(self.context)
|
|
|
|
if cb_type == 1:
|
|
callback = self.arg1
|
|
if callback is None:
|
|
raise RuntimeError(
|
|
'cannot run Handle; callback is not set')
|
|
|
|
args = self.arg2
|
|
|
|
if args is None:
|
|
callback()
|
|
else:
|
|
callback(*args)
|
|
|
|
elif cb_type == 2:
|
|
(<method_t>self.callback)(self.arg1)
|
|
|
|
elif cb_type == 3:
|
|
(<method1_t>self.callback)(self.arg1, self.arg2)
|
|
|
|
elif cb_type == 4:
|
|
(<method2_t>self.callback)(self.arg1, self.arg2, self.arg3)
|
|
|
|
elif cb_type == 5:
|
|
(<method3_t>self.callback)(
|
|
self.arg1, self.arg2, self.arg3, self.arg4)
|
|
|
|
else:
|
|
raise RuntimeError('invalid Handle.cb_type: {}'.format(
|
|
cb_type))
|
|
|
|
except (KeyboardInterrupt, SystemExit):
|
|
raise
|
|
except BaseException as ex:
|
|
if cb_type == 1:
|
|
msg = 'Exception in callback {}'.format(callback)
|
|
else:
|
|
msg = 'Exception in callback {}'.format(self.meth_name)
|
|
|
|
context = {
|
|
'message': msg,
|
|
'exception': ex,
|
|
'handle': self,
|
|
}
|
|
|
|
if self._source_traceback is not None:
|
|
context['source_traceback'] = self._source_traceback
|
|
|
|
self.loop.call_exception_handler(context)
|
|
|
|
finally:
|
|
context = self.context
|
|
Py_DECREF(self)
|
|
Context_Exit(context)
|
|
|
|
cdef _cancel(self):
|
|
self._cancelled = 1
|
|
self.callback = NULL
|
|
self.arg1 = self.arg2 = self.arg3 = self.arg4 = None
|
|
|
|
cdef _format_handle(self):
|
|
# Mirrors `asyncio.base_events._format_handle`.
|
|
if self.cb_type == 1 and self.arg1 is not None:
|
|
cb = self.arg1
|
|
if isinstance(getattr(cb, '__self__', None), aio_Task):
|
|
try:
|
|
return repr(cb.__self__)
|
|
except (AttributeError, TypeError, ValueError) as ex:
|
|
# Cython generates empty __code__ objects for coroutines
|
|
# that can crash asyncio.Task.__repr__ with an
|
|
# AttributeError etc. Guard against that.
|
|
self.loop.call_exception_handler({
|
|
'message': 'exception in Task.__repr__',
|
|
'task': cb.__self__,
|
|
'exception': ex,
|
|
'handle': self,
|
|
})
|
|
return repr(self)
|
|
|
|
# Public API
|
|
|
|
def __repr__(self):
|
|
info = [self.__class__.__name__]
|
|
|
|
if self._cancelled:
|
|
info.append('cancelled')
|
|
|
|
if self.cb_type == 1 and self.arg1 is not None:
|
|
func = self.arg1
|
|
# Cython can unset func.__qualname__/__name__, hence the checks.
|
|
if hasattr(func, '__qualname__') and func.__qualname__:
|
|
cb_name = func.__qualname__
|
|
elif hasattr(func, '__name__') and func.__name__:
|
|
cb_name = func.__name__
|
|
else:
|
|
cb_name = repr(func)
|
|
|
|
info.append(cb_name)
|
|
elif self.meth_name is not None:
|
|
info.append(self.meth_name)
|
|
|
|
if self._source_traceback is not None:
|
|
frame = self._source_traceback[-1]
|
|
info.append('created at {}:{}'.format(frame[0], frame[1]))
|
|
|
|
return '<' + ' '.join(info) + '>'
|
|
|
|
def cancel(self):
|
|
self._cancel()
|
|
|
|
def cancelled(self):
|
|
return self._cancelled
|
|
|
|
|
|
@cython.no_gc_clear
|
|
@cython.freelist(DEFAULT_FREELIST_SIZE)
|
|
cdef class TimerHandle:
|
|
def __cinit__(self, Loop loop, object callback, object args,
|
|
uint64_t delay, object context):
|
|
|
|
self.loop = loop
|
|
self.callback = callback
|
|
self.args = args
|
|
self._cancelled = 0
|
|
|
|
if UVLOOP_DEBUG:
|
|
self.loop._debug_cb_timer_handles_total += 1
|
|
self.loop._debug_cb_timer_handles_count += 1
|
|
|
|
if context is None:
|
|
context = Context_CopyCurrent()
|
|
self.context = context
|
|
|
|
if loop._debug:
|
|
self._debug_info = (
|
|
format_callback_name(callback),
|
|
extract_stack()
|
|
)
|
|
else:
|
|
self._debug_info = None
|
|
|
|
self.timer = UVTimer.new(
|
|
loop, <method_t>self._run, self, delay)
|
|
|
|
self.timer.start()
|
|
self._when = self.timer.get_when() * 1e-3
|
|
|
|
# Only add to loop._timers when `self.timer` is successfully created
|
|
loop._timers.add(self)
|
|
|
|
property _source_traceback:
|
|
def __get__(self):
|
|
if self._debug_info is not None:
|
|
return self._debug_info[1]
|
|
|
|
def __dealloc__(self):
|
|
if UVLOOP_DEBUG:
|
|
self.loop._debug_cb_timer_handles_count -= 1
|
|
if self.timer is not None:
|
|
raise RuntimeError('active TimerHandle is deallacating')
|
|
|
|
cdef _cancel(self):
|
|
if self._cancelled == 1:
|
|
return
|
|
self._cancelled = 1
|
|
self._clear()
|
|
|
|
cdef inline _clear(self):
|
|
if self.timer is None:
|
|
return
|
|
|
|
self.callback = None
|
|
self.args = None
|
|
|
|
try:
|
|
self.loop._timers.remove(self)
|
|
finally:
|
|
self.timer._close()
|
|
self.timer = None # let the UVTimer handle GC
|
|
|
|
cdef _run(self):
|
|
if self._cancelled == 1:
|
|
return
|
|
if self.callback is None:
|
|
raise RuntimeError('cannot run TimerHandle; callback is not set')
|
|
|
|
callback = self.callback
|
|
args = self.args
|
|
|
|
# Since _run is a cdef and there's no BoundMethod,
|
|
# we guard 'self' manually.
|
|
Py_INCREF(self)
|
|
|
|
if self.loop._debug:
|
|
started = time_monotonic()
|
|
try:
|
|
assert self.context is not None
|
|
Context_Enter(self.context)
|
|
|
|
if args is not None:
|
|
callback(*args)
|
|
else:
|
|
callback()
|
|
except (KeyboardInterrupt, SystemExit):
|
|
raise
|
|
except BaseException as ex:
|
|
context = {
|
|
'message': 'Exception in callback {}'.format(callback),
|
|
'exception': ex,
|
|
'handle': self,
|
|
}
|
|
|
|
if self._debug_info is not None:
|
|
context['source_traceback'] = self._debug_info[1]
|
|
|
|
self.loop.call_exception_handler(context)
|
|
else:
|
|
if self.loop._debug:
|
|
delta = time_monotonic() - started
|
|
if delta > self.loop.slow_callback_duration:
|
|
aio_logger.warning(
|
|
'Executing %r took %.3f seconds',
|
|
self, delta)
|
|
finally:
|
|
context = self.context
|
|
Py_DECREF(self)
|
|
Context_Exit(context)
|
|
self._clear()
|
|
|
|
# Public API
|
|
|
|
def __repr__(self):
|
|
info = [self.__class__.__name__]
|
|
|
|
if self._cancelled:
|
|
info.append('cancelled')
|
|
|
|
if self._debug_info is not None:
|
|
callback_name = self._debug_info[0]
|
|
source_traceback = self._debug_info[1]
|
|
else:
|
|
callback_name = None
|
|
source_traceback = None
|
|
|
|
if callback_name is not None:
|
|
info.append(callback_name)
|
|
elif self.callback is not None:
|
|
info.append(format_callback_name(self.callback))
|
|
|
|
if source_traceback is not None:
|
|
frame = source_traceback[-1]
|
|
info.append('created at {}:{}'.format(frame[0], frame[1]))
|
|
|
|
return '<' + ' '.join(info) + '>'
|
|
|
|
def cancelled(self):
|
|
return self._cancelled
|
|
|
|
def cancel(self):
|
|
self._cancel()
|
|
|
|
def when(self):
|
|
return self._when
|
|
|
|
|
|
cdef format_callback_name(func):
|
|
if hasattr(func, '__qualname__'):
|
|
cb_name = getattr(func, '__qualname__')
|
|
elif hasattr(func, '__name__'):
|
|
cb_name = getattr(func, '__name__')
|
|
else:
|
|
cb_name = repr(func)
|
|
return cb_name
|
|
|
|
|
|
cdef new_Handle(Loop loop, object callback, object args, object context):
|
|
cdef Handle handle
|
|
handle = Handle.__new__(Handle)
|
|
handle._set_loop(loop)
|
|
handle._set_context(context)
|
|
|
|
handle.cb_type = 1
|
|
|
|
handle.arg1 = callback
|
|
handle.arg2 = args
|
|
|
|
return handle
|
|
|
|
|
|
cdef new_MethodHandle(Loop loop, str name, method_t callback, object context,
|
|
object bound_to):
|
|
cdef Handle handle
|
|
handle = Handle.__new__(Handle)
|
|
handle._set_loop(loop)
|
|
handle._set_context(context)
|
|
|
|
handle.cb_type = 2
|
|
handle.meth_name = name
|
|
|
|
handle.callback = <void*> callback
|
|
handle.arg1 = bound_to
|
|
|
|
return handle
|
|
|
|
|
|
cdef new_MethodHandle1(Loop loop, str name, method1_t callback, object context,
|
|
object bound_to, object arg):
|
|
|
|
cdef Handle handle
|
|
handle = Handle.__new__(Handle)
|
|
handle._set_loop(loop)
|
|
handle._set_context(context)
|
|
|
|
handle.cb_type = 3
|
|
handle.meth_name = name
|
|
|
|
handle.callback = <void*> callback
|
|
handle.arg1 = bound_to
|
|
handle.arg2 = arg
|
|
|
|
return handle
|
|
|
|
|
|
cdef new_MethodHandle2(Loop loop, str name, method2_t callback, object context,
|
|
object bound_to, object arg1, object arg2):
|
|
|
|
cdef Handle handle
|
|
handle = Handle.__new__(Handle)
|
|
handle._set_loop(loop)
|
|
handle._set_context(context)
|
|
|
|
handle.cb_type = 4
|
|
handle.meth_name = name
|
|
|
|
handle.callback = <void*> callback
|
|
handle.arg1 = bound_to
|
|
handle.arg2 = arg1
|
|
handle.arg3 = arg2
|
|
|
|
return handle
|
|
|
|
|
|
cdef new_MethodHandle3(Loop loop, str name, method3_t callback, object context,
|
|
object bound_to, object arg1, object arg2, object arg3):
|
|
|
|
cdef Handle handle
|
|
handle = Handle.__new__(Handle)
|
|
handle._set_loop(loop)
|
|
handle._set_context(context)
|
|
|
|
handle.cb_type = 5
|
|
handle.meth_name = name
|
|
|
|
handle.callback = <void*> callback
|
|
handle.arg1 = bound_to
|
|
handle.arg2 = arg1
|
|
handle.arg3 = arg2
|
|
handle.arg4 = arg3
|
|
|
|
return handle
|
|
|
|
|
|
cdef extract_stack():
|
|
"""Replacement for traceback.extract_stack() that only does the
|
|
necessary work for asyncio debug mode.
|
|
"""
|
|
try:
|
|
f = sys_getframe()
|
|
# sys._getframe() might raise ValueError if being called without a frame, e.g.
|
|
# from Cython or similar C extensions.
|
|
except ValueError:
|
|
return None
|
|
if f is None:
|
|
return
|
|
|
|
try:
|
|
stack = tb_StackSummary.extract(tb_walk_stack(f),
|
|
limit=DEBUG_STACK_DEPTH,
|
|
lookup_lines=False)
|
|
finally:
|
|
f = None
|
|
|
|
stack.reverse()
|
|
return stack
|