eventloop: fix timer reentrancy

Avoid running multiple callbacks if a timer is stopped and restarted
in quick succession.

Signed-off-by:  Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
Eric Callahan 2024-01-19 19:32:12 -05:00
parent d4316d9878
commit bf9223225c
No known key found for this signature in database
GPG Key ID: 5A1EB336DFB4C71B
1 changed files with 21 additions and 4 deletions

View File

@ -164,12 +164,18 @@ class FlexTimer:
self.eventloop = eventloop
self.callback = callback
self.timer_handle: Optional[asyncio.TimerHandle] = None
self.timer_task: Optional[asyncio.Task] = None
self.running: bool = False
def in_callback(self) -> bool:
return self.timer_task is not None and not self.timer_task.done()
def start(self, delay: float = 0.):
if self.running:
return
self.running = True
if self.in_callback():
return
call_time = self.eventloop.get_loop_time() + delay
self.timer_handle = self.eventloop.call_at(
call_time, self._schedule_task)
@ -182,9 +188,14 @@ class FlexTimer:
self.timer_handle.cancel()
self.timer_handle = None
async def wait_timer_done(self) -> None:
if self.timer_task is None:
return
await self.timer_task
def _schedule_task(self):
self.timer_handle = None
self.eventloop.create_task(self._call_wrapper())
self.timer_task = self.eventloop.create_task(self._call_wrapper())
def is_running(self) -> bool:
return self.running
@ -192,8 +203,14 @@ class FlexTimer:
async def _call_wrapper(self):
if not self.running:
return
ret = self.callback(self.eventloop.get_loop_time())
if isinstance(ret, Awaitable):
ret = await ret
try:
ret = self.callback(self.eventloop.get_loop_time())
if isinstance(ret, Awaitable):
ret = await ret
except Exception:
self.running = False
raise
finally:
self.timer_task = None
if self.running:
self.timer_handle = self.eventloop.call_at(ret, self._schedule_task)