Skip to content

Commit 797c8f6

Browse files
committed
fix: only enable call_soon timer when callbacks exist, to prevent polling induced CPU usage
1 parent ed1545e commit 797c8f6

File tree

1 file changed

+27
-6
lines changed

1 file changed

+27
-6
lines changed

qasync/__init__.py

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -303,29 +303,41 @@ def __init__(self):
303303
# Use a deque instead of Queue, as we don't require
304304
# synchronization between threads here.
305305
self.__callbacks = deque()
306-
# Set a 0-delay timer on itself, this will ensure that
307-
# timerEvent gets fired each time after window events are processed
308-
# See https://doc.qt.io/qt-6/qtimer.html#interval-prop
309-
self.__timerId = self.startTimer(0)
306+
307+
# Keep track of the current timer.
308+
# The queue can only have a single timer that services it.
309+
# Once fired, all pending callbacks will be processed.
310+
self.__timer_id = None
310311
self.__stopped = False
311312
self.__debug_enabled = False
312313

313314
def add_callback(self, handle):
314315
# handle must be an asyncio.Handle
315316
self.__callbacks.append(handle)
316317
self.__log_debug("Registering call_soon handle %s", id(handle))
318+
319+
# Create a timer if it doesn't yet exist
320+
if self.__timer_id is None:
321+
# Set a 0-delay timer on itself, this will ensure thats
322+
# it gets fired immediately after window events are processed the next time.
323+
# See https://doc.qt.io/qt-6/qtimer.html#interval-prop
324+
self.__timer_id = self.startTimer(0)
325+
self.__log_debug("Registering call_soon timer %s", self.__timer_id)
317326
return handle
318327

319328
def timerEvent(self, event):
320329
timerId = event.timerId()
321-
assert timerId == self.__timerId
330+
# We should have only one timer active at the same time, so
331+
# this assert will get hit only when something's very bad
332+
assert timerId == self.__timer_id
322333

323334
# Stop timer if stopped
324335
if self.__stopped:
325-
self.killTimer(timerId)
326336
self.__log_debug("call_soon queue stopped, clearing handles")
327337
# TODO: Do we need to del the handles or somehow invalidate them?
328338
self.__callbacks.clear()
339+
self.killTimer(timerId)
340+
self.__timer_id = None
329341
return
330342

331343
# Iterate over pending callbacks
@@ -335,6 +347,15 @@ def timerEvent(self, event):
335347
self.__log_debug("Calling call_soon handle %s", id(handle))
336348
handle._run()
337349

350+
# No more callbacks exist, we can dispose this timer.
351+
# It will be recreated once a callback is registered again.
352+
# It's should be safe to assume that another thread isn't calling
353+
# add_callback during the lifetime of timerEvent
354+
self.__log_debug("Stopping call_soon timer %s", timerId)
355+
self.killTimer(timerId)
356+
self.__timer_id = None
357+
assert len(self.__callbacks) == 0
358+
338359
def stop(self):
339360
self.__log_debug("Stopping call_soon queue")
340361
self.__stopped = True

0 commit comments

Comments
 (0)