Package vinzclortho :: Module vectorclock
[hide private]
[frames] | no frames]

Source Code for Module vinzclortho.vectorclock

  1  # -*- coding: utf-8 -*- 
  2  # 
  3  # Copyright (c) 2001-2010 Pär Bohrarper. 
  4  # See LICENSE for details. 
  5   
  6  import time 
  7  import unittest 
  8   
9 -class VectorClock(object):
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
19 - def __init__(self, clocks=None):
20 if clocks is not None: 21 self._clocks = clocks 22 else: 23 self._clocks = {}
24
25 - def __repr__(self):
26 return "VectorClock(%s)"%repr(self._clocks)
27
28 - def __str__(self):
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
35 - def clone(self):
36 """Returns a copy of the vector clock""" 37 return VectorClock(dict(self._clocks))
38
39 - def prune(self):
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
51 - def increment(self, name):
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
65 - def __eq__(self, rhs):
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 # Everything from rhs was in self, check length to see if self has more 76 return len(self._clocks) == len(rhs._clocks)
77
78 - def __ne__(self, rhs):
79 return not self.__eq__(rhs)
80
81 - def descends_from(self, rhs):
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 # if rhs is shorter than self, then self also has versions > than rhs 93 return (len(rhs._clocks) <= len(self._clocks))
94 95
96 -def merge(a, b):
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 # use latest timestamp if equal versions 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
122 -def _joiner(a, b):
123 return [a, b]
124
125 -def resolve(a, b, joiner=_joiner):
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 # concurrent 144 newclock = merge(c_a, c_b) 145 return (newclock, joiner(val_a, val_b))
146
147 -def resolve_list(c, joiner=_joiner):
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
156 -def resolve_list_extend(list_):
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
175 -class TestVectorClock(unittest.TestCase):
176 - def test_empty_equals_empty(self):
177 a = VectorClock() 178 b = VectorClock() 179 self.assertEquals(a, b)
180
182 a = VectorClock() 183 self.assertTrue(a.descends_from(a))
184
186 a = VectorClock() 187 b = VectorClock() 188 a.increment("foo") 189 self.assertTrue(a.descends_from(b))
190
191 - def test_merge(self):
192 a = VectorClock() 193 a.increment("foo") 194 a.increment("bar") 195 a.increment("bar") 196 a.increment("foo") 197 a.increment("foo") 198 b = a.clone() 199 b.increment("foo") 200 b.increment("baz") 201 c = a.clone() 202 c.increment("bar") 203 m = merge(b, c) 204 self.assertEquals(m._clocks["foo"][1], 4) 205 self.assertEquals(m._clocks["bar"][1], 3) 206 self.assertEquals(m._clocks["baz"][1], 1)
207
209 a = VectorClock() 210 a.increment("foo") 211 a.increment("bar") 212 b = a.clone() 213 b.increment("foo") 214 c = resolve((a, "a"), (b, "b")) 215 self.assertEquals(c[0], b) 216 self.assertEquals(c[1], "b")
217
218 - def test_resolve_concurrent(self):
219 a = VectorClock() 220 a.increment("foo") 221 a.increment("bar") 222 b = a.clone() 223 b.increment("foo") 224 a.increment("baz") 225 c = resolve((a, "a"), (b, "b")) 226 self.assertEquals(c[0], merge(a, b)) 227 self.assertEquals(sorted(c[1]), sorted(["a", "b"]))
228 229 if __name__=="__main__": 230 unittest.main() 231