Skip to content

Commit 44a6ab6

Browse files
committed
Issue #33: Add slow_callback_duration property to loop
1 parent 28ddbfa commit 44a6ab6

File tree

5 files changed

+112
-15
lines changed

5 files changed

+112
-15
lines changed

tests/test_base.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,38 @@ async def foo():
288288
with self.assertRaisesRegex(ValueError, 'aaa'):
289289
self.loop.run_until_complete(foo())
290290

291+
def test_debug_slow_callbacks(self):
292+
logger = logging.getLogger('asyncio')
293+
self.loop.set_debug(True)
294+
self.loop.slow_callback_duration = 0.2
295+
self.loop.call_soon(lambda: time.sleep(0.3))
296+
297+
with mock.patch.object(logger, 'warning') as log:
298+
self.loop.run_until_complete(asyncio.sleep(0, loop=self.loop))
299+
300+
self.assertEqual(log.call_count, 1)
301+
# format message
302+
msg = log.call_args[0][0] % log.call_args[0][1:]
303+
304+
self.assertIn('Executing <Handle', msg)
305+
self.assertIn('test_debug_slow_callbacks', msg)
306+
307+
def test_debug_slow_timer_callbacks(self):
308+
logger = logging.getLogger('asyncio')
309+
self.loop.set_debug(True)
310+
self.loop.slow_callback_duration = 0.2
311+
self.loop.call_later(0.01, lambda: time.sleep(0.3))
312+
313+
with mock.patch.object(logger, 'warning') as log:
314+
self.loop.run_until_complete(asyncio.sleep(0.02, loop=self.loop))
315+
316+
self.assertEqual(log.call_count, 1)
317+
# format message
318+
msg = log.call_args[0][0] % log.call_args[0][1:]
319+
320+
# self.assertIn('Executing <Handle', msg)
321+
# self.assertIn('test_debug_slow_callbacks', msg)
322+
291323
def test_default_exc_handler_callback(self):
292324
self.loop._process_events = mock.Mock()
293325

uvloop/cbhandles.pyx

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -97,17 +97,34 @@ cdef class Handle:
9797
cdef _cancel(self):
9898
self.cancelled = 1
9999
self.callback = NULL
100-
self.arg1 = self.arg2 = self.arg3 = self.arg4 = None
100+
self.arg2 = self.arg3 = self.arg4 = None
101101

102102
# Public API
103103

104104
def __repr__(self):
105+
info = [self.__class__.__name__]
106+
105107
if self.cancelled:
106-
return '<Handle cancelled {:#x}>'.format(id(self))
108+
info.append('cancelled')
109+
110+
if self.cb_type == 1:
111+
func = self.arg1
112+
if hasattr(func, '__qualname__'):
113+
cb_name = getattr(func, '__qualname__')
114+
elif hasattr(func, '__name__'):
115+
cb_name = getattr(func, '__name__')
116+
else:
117+
cb_name = repr(func)
118+
119+
info.append(cb_name)
107120
else:
108-
return '<Handle {!r} {:#x}>'.format(
109-
self.arg1 if self.cb_type == 1 else self.meth_name,
110-
id(self))
121+
info.append(self.meth_name)
122+
123+
if self._source_traceback is not None:
124+
frame = self._source_traceback[-1]
125+
info.append('created at {}:{}'.format(frame[0], frame[1]))
126+
127+
return '<' + ' '.join(info) + '>'
111128

112129
def cancel(self):
113130
self._cancel()
@@ -177,6 +194,8 @@ cdef class TimerHandle:
177194
if old_exec_py_code == 1:
178195
raise RuntimeError('Python exec-mode before TimerHandle._run')
179196
self.loop._executing_py_code = 1
197+
if self.loop._debug:
198+
started = time_monotonic()
180199
try:
181200
if args is not None:
182201
callback(*args)
@@ -193,17 +212,40 @@ cdef class TimerHandle:
193212
context['source_traceback'] = self._source_traceback
194213

195214
self.loop.call_exception_handler(context)
215+
else:
216+
if self.loop._debug:
217+
delta = time_monotonic() - started
218+
if delta > self.loop.slow_callback_duration:
219+
aio_logger.warning(
220+
'Executing %r took %.3f seconds',
221+
self, delta)
196222
finally:
197223
self.loop._executing_py_code = old_exec_py_code
198224
Py_DECREF(self)
199225

200226
# Public API
201227

202228
def __repr__(self):
229+
info = [self.__class__.__name__]
230+
203231
if self.closed:
204-
return '<TimerHandle cancelled {:#x}>'.format(id(self))
232+
info.append('cancelled')
233+
234+
func = self.callback
235+
if hasattr(func, '__qualname__'):
236+
cb_name = getattr(func, '__qualname__')
237+
elif hasattr(func, '__name__'):
238+
cb_name = getattr(func, '__name__')
205239
else:
206-
return '<TimerHandle {!r} {:#x}>'.format(self.callback, id(self))
240+
cb_name = repr(func)
241+
242+
info.append(cb_name)
243+
244+
if self._source_traceback is not None:
245+
frame = self._source_traceback[-1]
246+
info.append('created at {}:{}'.format(frame[0], frame[1]))
247+
248+
return '<' + ' '.join(info) + '>'
207249

208250
def cancel(self):
209251
self._cancel()

uvloop/includes/stdlib.pxi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ cdef subprocess_SubprocessError = subprocess.SubprocessError
112112
cdef int signal_NSIG = std_signal.NSIG
113113

114114
cdef time_sleep = time.sleep
115+
cdef time_monotonic = time.monotonic
115116

116117
cdef tb_extract_stack = traceback.extract_stack
117118
cdef tb_format_list = traceback.format_list

uvloop/loop.pxd

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ cdef class Loop:
3636

3737
bint _coroutine_wrapper_set
3838

39+
public slow_callback_duration
40+
3941
readonly bint _closed
4042
bint _debug
4143
bint _running

uvloop/loop.pyx

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ cdef class Loop:
5757
if self.uvloop is NULL:
5858
raise MemoryError()
5959

60+
self.slow_callback_duration = 0.1
61+
6062
self._closed = 0
6163
self._debug = 0
6264
self._thread_is_main = 0
@@ -230,14 +232,32 @@ cdef class Loop:
230232
self._check_sigint()
231233

232234
ntodo = len(self._ready)
233-
for i from 0 <= i < ntodo:
234-
handler = <Handle> popleft()
235-
if handler.cancelled == 0:
236-
try:
237-
handler._run()
238-
except BaseException as ex:
239-
self._stop(ex)
240-
return
235+
if self._debug:
236+
for i from 0 <= i < ntodo:
237+
handler = <Handle> popleft()
238+
if handler.cancelled == 0:
239+
try:
240+
started = time_monotonic()
241+
handler._run()
242+
except BaseException as ex:
243+
self._stop(ex)
244+
return
245+
else:
246+
delta = time_monotonic() - started
247+
if delta > self.slow_callback_duration:
248+
aio_logger.warning(
249+
'Executing %r took %.3f seconds',
250+
handler, delta)
251+
252+
else:
253+
for i from 0 <= i < ntodo:
254+
handler = <Handle> popleft()
255+
if handler.cancelled == 0:
256+
try:
257+
handler._run()
258+
except BaseException as ex:
259+
self._stop(ex)
260+
return
241261

242262
if len(self._queued_streams):
243263
self._exec_queued_writes()

0 commit comments

Comments
 (0)