remade ExpiringDict deterministic and changed it to not overload the reactor with...
[p2pool.git] / p2pool / util / expiring_dict.py
1 from __future__ import division
2
3 import random
4 import time
5
6 from twisted.internet import task
7
8 class Node(object):
9     def __init__(self, contents, prev=None, next=None):
10         self.contents, self.prev, self.next = contents, prev, next
11     
12     def insert_before(self, contents):
13         self.prev.next = self.prev = node = Node(contents, self.prev, self)
14         return node
15     
16     def insert_after(self, contents):
17         self.next.prev = self.next = node = Node(contents, self, self.next)
18         return node
19     
20     @staticmethod
21     def connect(prev, next):
22         if prev.next is not None or next.prev is not None:
23             raise ValueError('node already connected')
24         prev.next, next.prev = next, prev
25     
26     def replace(self, contents):
27         self.contents = contents
28     
29     def delete(self):
30         if self.prev.next is None or self.next.prev is None:
31             raise ValueError('node not connected')
32         self.prev.next, self.next.prev = self.next, self.prev
33         self.next = self.prev = None
34
35
36 class LinkedList(object):
37     def __init__(self, iterable=[]):
38         self.start, self.end = Node(None), Node(None)
39         Node.connect(self.start, self.end)
40         
41         for item in iterable:
42             self.append(item)
43     
44     def __repr__(self):
45         return 'LinkedList(%r)' % (list(self),)
46     
47     def __len__(self):
48         return sum(1 for x in self)
49     
50     def __iter__(self):
51         cur = self.start.next
52         while cur is not self.end:
53             cur2 = cur
54             cur = cur.next
55             yield cur2 # in case cur is deleted, but items inserted after are ignored
56     
57     def __reversed__(self):
58         cur = self.end.prev
59         while cur is not self.start:
60             cur2 = cur
61             cur = cur.prev
62             yield cur2
63     
64     def __getitem__(self, index):
65         if index < 0:
66             cur = self.end
67             for i in xrange(-index):
68                 cur = cur.prev
69                 if cur is self.start:
70                     raise IndexError('index out of range')
71         else:
72             cur = self.start
73             for i in xrange(index + 1):
74                 cur = cur.next
75                 if cur is self.end:
76                     raise IndexError('index out of range')
77         return cur
78     
79     def appendleft(self, item):
80         return self.start.insert_after(item)
81     
82     def append(self, item):
83         return self.end.insert_before(item)
84     
85     def popleft(self):
86         node = self.start.next
87         if node is self.end:
88             raise IndexError('popleft from empty')
89         node.delete()
90         return node.contents
91     
92     def pop(self):
93         node = self.end.prev
94         if node is self.start:
95             raise IndexError('pop from empty')
96         node.delete()
97         return node.contents
98
99
100 class ExpiringDict(object):
101     def __init__(self, expiry_time, get_touches=True):
102         self.expiry_time = expiry_time
103         self.get_touches = get_touches
104         
105         self.expiry_deque = LinkedList()
106         self.d = dict() # key -> node, value
107         task.LoopingCall(self.expire).start(1) # XXX
108     
109     def __repr__(self):
110         return 'ExpiringDict' + repr(self.__dict__)
111     
112     def __len__(self):
113         return len(self.d)
114     
115     _nothing = object()
116     def touch(self, key, value=_nothing):
117         'Updates expiry node, optionally replacing value, returning new value'
118         if value is self._nothing or key in self.d:
119             node, old_value = self.d[key]
120             node.delete()
121         
122         new_value = old_value if value is self._nothing else value
123         self.d[key] = self.expiry_deque.append((time.time() + self.expiry_time, key)), new_value
124         return new_value
125     
126     def expire(self):
127         t = time.time()
128         for node in self.expiry_deque:
129             timestamp, key = node.contents
130             if timestamp > t:
131                 break
132             del self.d[key]
133             node.delete()
134     
135     def __contains__(self, key):
136         return key in self.d
137     
138     def __getitem__(self, key):
139         if self.get_touches:
140             value = self.touch(key)
141         else:
142             node, value = self.d[key]
143         return value
144     
145     def __setitem__(self, key, value):
146         self.touch(key, value)
147     
148     def __delitem__(self, key):
149         node, value = self.d.pop(key)
150         node.delete()
151     
152     def get(self, key, default_value=None):
153         if key in self.d:
154             res = self[key]
155         else:
156             res = default_value
157         return res
158     
159     def setdefault(self, key, default_value):
160         if key in self.d:
161             return self[key]
162         else:
163             self[key] = default_value
164             return default_value
165     
166     def keys(self):
167         return self.d.keys()
168     
169     def values(self):
170         return [value for node, value in self.d.itervalues()]
171     
172     def itervalues(self):
173         for node, value in self.d.itervalues():
174             yield value
175
176 if __name__ == '__main__':
177     x = ExpiringDict(5)
178     print x
179     
180     time.sleep(1)
181     
182     x[1] = 2
183     print 'x[1] = 2'
184     print x
185     
186     time.sleep(1)
187     
188     x[1] = 3
189     print 'x[1] = 3'
190     print x
191     
192     time.sleep(5)
193     
194     print x