reactor: Prevent update_timer() from running a single timer multiple times

The "lazy" greenlet implementation could allow the same timer to run
multiple times in parallel if the first timer instance calls pause()
and another task calls update_timer().  This is confusing and can
cause hard to debug errors.  Add a new timer_is_running flag to
prevent it.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
This commit is contained in:
Kevin O'Connor 2025-09-03 13:45:13 -04:00
parent 6c1a4a825d
commit aa59b32031

View File

@ -1,6 +1,6 @@
# File descriptor and timer event helper
#
# Copyright (C) 2016-2020 Kevin O'Connor <kevin@koconnor.net>
# Copyright (C) 2016-2025 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import os, gc, select, math, time, logging, queue
@ -14,6 +14,7 @@ class ReactorTimer:
def __init__(self, callback, waketime):
self.callback = callback
self.waketime = waketime
self.timer_is_running = False
class ReactorCompletion:
class sentinel: pass
@ -118,6 +119,8 @@ class SelectReactor:
return tuple(self._last_gc_times)
# Timers
def update_timer(self, timer_handler, waketime):
if timer_handler.timer_is_running:
return
timer_handler.waketime = waketime
self._next_timer = min(self._next_timer, waketime)
def register_timer(self, callback, waketime=NEVER):
@ -155,7 +158,9 @@ class SelectReactor:
waketime = t.waketime
if eventtime >= waketime:
t.waketime = self.NEVER
t.timer_is_running = True
t.waketime = waketime = t.callback(eventtime)
t.timer_is_running = False
if g_dispatch is not self._g_dispatch:
self._next_timer = min(self._next_timer, waketime)
self._end_greenlet(g_dispatch)