1
2
3
4
5
6 import time
7 import unittest
8
10 """A vector clock implementation. Each node's clock is stored as a
11 tuple: (clock, timestamp) in a dict with name as key. This means
12 that the key needs to be hashable.
13
14 Inspired by: http://github.com/cliffmoon/dynomite/blob/master/elibs/vector_clock.erl
15 """
16 prune_size = 10
17 prune_age = 3600.0
18
20 if clocks is not None:
21 self._clocks = clocks
22 else:
23 self._clocks = {}
24
26 return "VectorClock(%s)"%repr(self._clocks)
27
29 s = []
30 for k, v in self._clocks:
31 t, c = v
32 s.append("%s, %s, (%f)" % (k, v, t))
33 return "\n".join(s)
34
36 """Returns a copy of the vector clock"""
37 return VectorClock(dict(self._clocks))
38
40 """
41 This remove items so that it contains no items older than L{prune_age}
42 and a maximum of L{prune_size} items.
43 """
44 t = time.time()
45 newclocks = dict(((k, v) for v, k in
46 sorted(((v, k) for k, v in self._clocks.items()
47 if t - v[0] > self.prune_age))[:self.prune_size]))
48 self._clocks = newclocks
49 return self
50
52 """
53 Increments the vector clock for name.
54
55 @param name: A unique identifier
56 @type name: hashable
57 """
58 try:
59 timestamp, clock = self._clocks[name]
60 except KeyError:
61 clock = 0
62 self._clocks[name] = (time.time(), clock+1)
63 return self
64
66 for name, v1 in rhs._clocks.items():
67 try:
68 v2 = self._clocks[name]
69 except KeyError:
70 return False
71 t1, clock1 = v1
72 t2, clock2 = v2
73 if clock1 != clock2:
74 return False
75
76 return len(self._clocks) == len(rhs._clocks)
77
79 return not self.__eq__(rhs)
80
82 """Determines if rhs is an ancestor of self. Note that vc.descends_from(vc) returns True"""
83 for name, v_r in rhs._clocks.items():
84 try:
85 v_s = self._clocks[name]
86 except KeyError:
87 return False
88 t_r, clock_r = v_r
89 t_s, clock_s = v_s
90 if not (clock_s >= clock_r):
91 return False
92
93 return (len(rhs._clocks) <= len(self._clocks))
94
95
97 """Merges the two vector clocks, using the latest version for each client"""
98 newclocks = {}
99 c_a = a._clocks
100 c_b = b._clocks
101 for k_a, v_a in c_a.items():
102 try:
103 v_b = c_b[k_a]
104 t_a, clock_a = v_a
105 t_b, clock_b = v_b
106 if v_a > v_b:
107 newclocks[k_a] = (t_a, clock_a)
108 elif v_a < v_b:
109 newclocks[k_a] = (t_b, clock_b)
110 else:
111
112 newclocks[k_a] = (max(t_a, t_b), clock_a)
113 except KeyError:
114 newclocks[k_a] = v_a
115
116 k_only_in_b = set(c_b.keys()) - set(c_a.keys())
117 for k in k_only_in_b:
118 newclocks[k] = c_b[k]
119
120 return VectorClock(newclocks)
121
124
126 """Resolves the latest value for a and b,
127 which should be a tuple of (VectorClock, value).
128
129 @param a: Tuple of L{VectorClock}, value
130 @param b: Tuple of L{VectorClock}, value
131 @param joiner: A function that takes the two values and produces a new value. The default joiner produces a list of the two values
132 @return: A tuple of the merged vector clock and the latest (possibly joined) value.
133 """
134 c_a, val_a = a
135 c_b, val_b = b
136 if c_a == c_b:
137 return (c_a, val_a)
138 elif c_a.descends_from(c_b):
139 return (c_a, val_a)
140 elif c_b.descends_from(c_a):
141 return (c_b, val_b)
142 else:
143
144 newclock = merge(c_a, c_b)
145 return (newclock, joiner(val_a, val_b))
146
148 """Returns the latest/merged value from a list of L{VectorClock}, value tuples"""
149 def _resolve(curr, rest):
150 if not rest:
151 return curr
152 curr = resolve(curr, rest[0], joiner)
153 return _resolve(curr, rest[1:])
154 return _resolve(c[0], c[1:])
155
157 """Resolves the list of results to a unified result (which may be a list of concurrent versions)"""
158 if not list_:
159 return None
160 if len(list_) == 1:
161 return list_[0]
162
163 def joiner(a, b):
164 """This way of joining concurrent versions makes it possible
165 to store concurrent versions as lists, while still being able
166 to return a single list for a request
167 """
168 if not isinstance(a, list):
169 a = [a]
170 if not isinstance(b, list):
171 b = [b]
172 return a + b
173 return resolve_list(list_, joiner)
174
228
229 if __name__=="__main__":
230 unittest.main()
231