From 134d66c57a804bbc627adca2fc9a87b9553c6a32 Mon Sep 17 00:00:00 2001 From: Antonie Blom Date: Sat, 27 Jan 2024 14:59:25 +0100 Subject: [PATCH] Get rid of possible deadlock Previously it could theoretically happen that loop() would set self.rate_ok to False and notify() self.tick, without ticker() waiting on self.tick. This would mean a deadlock. --- rate_limit.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/rate_limit.py b/rate_limit.py index b09e0e3..3b97162 100644 --- a/rate_limit.py +++ b/rate_limit.py @@ -34,20 +34,32 @@ def __init__(self, schema, client, interval): self.telegram = None self.msg = Condition() self.tick = Condition() + self.started = Condition() self.total_msgs = 0 self.epoch = time.monotonic_ns() self.rate_ok = True logging.debug(f'Rate limiter epoch: {self.epoch} ns') - Thread(target=self.ticker, daemon=True).start() - Thread(target=self.loop, daemon=True).start() + # Make sure we return an object with a consistent state. msg and + # tick conditions must be in locked or waiting state before + # we start receiving messages + with self.started: + Thread(target=self.ticker, daemon=True).start() + self.started.wait() + Thread(target=self.loop, daemon=True).start() + self.started.wait() def next_ts(self): return self.epoch + self.interval_ns * self.total_msgs def ticker(self): with self.tick: + # Notify parent thread that we're holding the tick lock, and + # it's okay to continue + with self.started: + self.started.notify() + while True: self.tick.wait() sleep_ns = self.next_ts() - time.monotonic_ns() @@ -63,6 +75,11 @@ def ticker(self): def loop(self): with self.msg: + # Notify parent thread that we're holding the msg lock, and + # it's okay to continue + with self.started: + self.started.notify() + while True: self.msg.wait() if not self.rate_ok: