linotp.lib.resources module¶
- ResourceScheduler - handle iteration on resources list with blocking
similar to a list of circuit breakers.
the ResourceSchedule keeps track to the resouces, which might have been blocked for a certain time. After the blocking time has been expired, the access to the resource will be scheduled again
Bases:
Exception
to be thrown when all services are unavailable.
- class linotp.lib.resources.DictResourceRegistry¶
Bases:
ResourceRegistry
the dict resource registry is a module global dict, which keeps an entry per resource. The advantage of the dict is that the operations on the dict are threadsafe. Other resource registries might use different strategies
see: http://effbot.org/
pyfaq/what-kinds-of-global-value-mutation-are-thread-safe.htm
remark: we use the tread safe dictionary method ‘setdefault’ which is an atomic version of:
>>> if key not in dict: >>> dict[key] = value >>> return dic[key]
- registry = {}¶
- classmethod store(resource, value)¶
store the resource along with a new value
- Parameters
resource – the resource
value – the value which should be associated with the resource
- classmethod store_or_retrieve(resource, value)¶
if a resource already exists in the registry, it is returned. If it does not exist, it is preserved and then retrieved to be returned.
- Parameters
resource – the resource
value – the fallback value, if the resource does not exist
- Returns
the value associated with the resource
- class linotp.lib.resources.ResourceRegistry¶
Bases:
object
the resource registry is a global registry, which keeps an entry per resource. The resource is identified by a unique identifier, eg. URI. the value could be specific eg. the block expiration time only
- classmethod store(resource, value)¶
abstraction to store the resource along with a new value
- Parameters
resource – a resource identifier
value – the value which should be associated with the resource
- classmethod store_or_retrieve(resource, value)¶
atomic operation - get or set a value from/into the registry. The set operation is only made, when there is no value for the resource in the registry
- class linotp.lib.resources.ResourceScheduler(uri_list=None, tries=1, resource_registry_class=<class 'linotp.lib.resources.DictResourceRegistry'>)¶
Bases:
object
Class to manage the list of resources (uris) in a global register, while keeping track of the connect-ability
example usage:
>>> rs = ResourceScheduler( >>> retry=3, uri_list=['h://1', 'h://2', 'h://3', 'h://4')) >>> >>> for uri in rs.next(): >>> try: >>> return conn(uri) >>> except TimeoutException: >>> rs.block(uri, delay=2) >>> >>> print "all resouces unavailable!"
about:blocking
the blocking with a fixed delay could lead to the problem, that when a server is offline, the server will be retried after a short delay. Looking at the relevant functions next (S) and block (b) we have the following event sequence with a constant delay between the retries.
- ———————————————————-> timeline
S S b S b S b S b S S
the sequence ( S -> S ) indicate that the server has been reachable in the first request.
What required is, is that we remember if the previous state already has been blocked - the block_indicator
- ———————————————————-> timeline
S S b S b S b S b S S
in 0 0 0 1 0 1 0 1 0 1 0 out 0 0 1 0 1 0 1 0 1 0 0
as we can see, that if the next():S gets the input 1, it knows, that the former request was blocked.
But this helps nothing as the next():S always resets the block status to give a chance for a new request. The idea is now to just remember the former state and this could as well be used to aggregate the number of b->S sequences. Therefore we use a second state, the block_counter. Thus we have the tuple
(block_indicator, block_counter)
with the following algorithm
the func block():b always only sets the block_indicator (x,n) -> (1,n)
the func next():S takes the block_indicator - if set, and adds it to the block_counter (1,n) -> (0, n+1) - if not set, the block_counter will be reseted (0,n) -> (0,0)
the upper sequence would have the following counters:
- ———————————————————-> timeline
s S b S b S b S b S S
in 0,0 0,0 0,0 1,0 0,1 1,1 0,2 1,2 0,3 1,4 0,5 out 0,0 0,0 1,0 0,1 1,1 0,2 1,2 0,3 1,4 0,5 0,0
Now we can dynamically adjust the delay by the doubling the time based on the counter: delay + delay * 2**n assuming a delay of 30 seconds, this will result in a sequence of doubling delays
0 -> 30 = 0,5 Min 1 -> 30 + 30 * 2^1 = 90 = 1,5 Min 2 -> 30 + 30 * 2^2 = 150 = 2,5 Min 3 -> 30 + 30 * 2^3 = 270 = 4,5 Min 4 -> 30 + 30 * 2^4 = 510 = 8,5 Min 5 -> 30 + 30 * 2^5 = 990 = 16,5 Min 6 -> 30 + 30 * 2^6 = 1950 = 32,5 Min 7 -> 30 + 30 * 2^7 = 3970 = 64,5 Min 8 -> 30 + 30 * 2^8 = 7710 = 128,5 Min
the max delay time should be limited to 8 which is ~2 hours
- block(resource, delay=30, immediately=False)¶
mark the given resource as blocked for a delay of seconds
the blocking is only possible if all retries are made. this is controlled by the _retry_complete, that is controlled in the retry iterator
- Parameters
resource – the resource that should be blocked
delay – optional - specify the delay, till a request should be re-triggered
immediately – should be locked immediately
- linotp.lib.resources.string_to_list(string_list, sep=',')¶
tiny helper to create a list from a string with separators
- Parameters
string_list – single string which should be split into a list
sep – the item separator