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 # 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. # This file may be distributed under the terms of the GNU GPLv3 license.
import os, gc, select, math, time, logging, queue import os, gc, select, math, time, logging, queue
@ -14,6 +14,7 @@ class ReactorTimer:
def __init__(self, callback, waketime): def __init__(self, callback, waketime):
self.callback = callback self.callback = callback
self.waketime = waketime self.waketime = waketime
self.timer_is_running = False
class ReactorCompletion: class ReactorCompletion:
class sentinel: pass class sentinel: pass
@ -118,6 +119,8 @@ class SelectReactor:
return tuple(self._last_gc_times) return tuple(self._last_gc_times)
# Timers # Timers
def update_timer(self, timer_handler, waketime): def update_timer(self, timer_handler, waketime):
if timer_handler.timer_is_running:
return
timer_handler.waketime = waketime timer_handler.waketime = waketime
self._next_timer = min(self._next_timer, waketime) self._next_timer = min(self._next_timer, waketime)
def register_timer(self, callback, waketime=NEVER): def register_timer(self, callback, waketime=NEVER):
@ -155,7 +158,9 @@ class SelectReactor:
waketime = t.waketime waketime = t.waketime
if eventtime >= waketime: if eventtime >= waketime:
t.waketime = self.NEVER t.waketime = self.NEVER
t.timer_is_running = True
t.waketime = waketime = t.callback(eventtime) t.waketime = waketime = t.callback(eventtime)
t.timer_is_running = False
if g_dispatch is not self._g_dispatch: if g_dispatch is not self._g_dispatch:
self._next_timer = min(self._next_timer, waketime) self._next_timer = min(self._next_timer, waketime)
self._end_greenlet(g_dispatch) self._end_greenlet(g_dispatch)