1from __future__ import absolute_import
2
3from decimal import Decimal
4
5
6def _Decimal(v):
7    if not isinstance(v, Decimal):
8        return Decimal(str(v))
9    return v
10
11
12class BackoffTimer(object):
13    """
14    This is a timer that is smart about backing off exponentially when there are problems
15    """
16    def __init__(self, min_interval, max_interval, ratio=.25, short_length=10, long_length=250):
17        assert isinstance(min_interval, (int, float, Decimal))
18        assert isinstance(max_interval, (int, float, Decimal))
19
20        self.min_interval = _Decimal(min_interval)
21        self.max_interval = _Decimal(max_interval)
22
23        self.max_short_timer = (self.max_interval - self.min_interval) * _Decimal(ratio)
24        self.max_long_timer = (self.max_interval - self.min_interval) * (1 - _Decimal(ratio))
25        self.short_unit = self.max_short_timer / _Decimal(short_length)
26        self.long_unit = self.max_long_timer / _Decimal(long_length)
27
28        self.short_interval = Decimal(0)
29        self.long_interval = Decimal(0)
30        self.update_interval()
31
32    def success(self):
33        """Update the timer to reflect a successfull call"""
34        if self.interval == 0.0:
35            return
36        self.short_interval -= self.short_unit
37        self.long_interval -= self.long_unit
38        self.short_interval = max(self.short_interval, Decimal(0))
39        self.long_interval = max(self.long_interval, Decimal(0))
40        self.update_interval()
41
42    def failure(self):
43        """Update the timer to reflect a failed call"""
44        self.short_interval += self.short_unit
45        self.long_interval += self.long_unit
46        self.short_interval = min(self.short_interval, self.max_short_timer)
47        self.long_interval = min(self.long_interval, self.max_long_timer)
48        self.update_interval()
49
50    def update_interval(self):
51        self.interval = float(self.min_interval + self.short_interval + self.long_interval)
52
53    def get_interval(self):
54        return self.interval
55