3 from threading import Thread, Event, Lock
8 class GetterThread(Thread):
9 def __init__(self, view):
10 super(GetterThread, self).__init__()
17 val = self._view._get()
20 self._view.log.exception("Error while calling _get:")
21 # TODO: separate channel for passing back error
22 self._view.value = None
23 self._view.value_when = None
24 self._view.getter_thread = None
27 self._view.latency = t1 - t0
28 self._view.value = val
29 self._view.value_when = datetime.datetime.now()
30 self._view.getter_thread = None
35 class RemoteViewCache(object):
37 A cached view of something we have to fetch remotely, e.g. the output
38 of a 'tell' command to a daemon, or something we read from RADOS like
39 and rbd image's status.
41 There are two reasons this wrapper exists:
42 * To reduce load on remote entities when we frequently fetch something
43 (i.e. 10 page loads in one second should not translate into 10 separate
45 * To handle the blocking nature of some remote operations
46 (i.e. if RADOS isn't responding, we need a predictable way to either
47 return no data, or return stale data, when something from RADOS is
50 Note that relying on timeouts in underlying libraries isn't wise here:
51 if something is really slow, we would like to return a 'stale' state
52 to a GUI quickly, but we should let our underlying request to the cluster
53 run to completion so that we get some data even if it's slow, and so that
54 a polling caller doesn't end up firing off a large number of requests to
55 the cluster because each one timed out.
57 Subclasses may override _init and must override _get
60 def __init__(self, module_inst):
61 self.initialized = False
63 self.log = module_inst.log
65 self.getter_thread = None
67 # Consider data within 1s old to be sufficiently fresh
68 self.stale_period = 1.0
70 # Return stale data if
74 self.value_when = None
79 self._module = module_inst
83 self.initialized = True
91 If data less than `stale_period` old is available, return it
93 If an attempt to fetch data does not complete within `timeout`, then
94 return the most recent data available, with a status to indicate that
97 Initialization does not count towards the timeout, so the first call
98 on one of these objects during the process lifetime may be slower
99 than subsequent calls.
101 :return: 2-tuple of value status code, value
104 if not self.initialized:
107 now = datetime.datetime.now()
108 if self.value_when and now - self.value_when < datetime.timedelta(
109 seconds=self.stale_period):
110 return self.VALUE_OK, self.value
112 if self.getter_thread is None:
113 self.getter_thread = GetterThread(self)
114 self.getter_thread.start()
116 ev = self.getter_thread.event
118 success = ev.wait(timeout=self.timeout)
122 # We fetched the data within the timeout
123 return self.VALUE_OK, self.value
124 elif self.value_when is not None:
125 # We have some data, but it doesn't meet freshness requirements
126 return self.VALUE_STALE, self.value
128 # We have no data, not even stale data
129 return self.VALUE_NONE, None
135 raise NotImplementedError()