Home | History | Annotate | Download | only in server2
      1 # Copyright 2013 The Chromium Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 from future import Future
      6 from object_store import ObjectStore
      7 
      8 
      9 class CacheChainObjectStore(ObjectStore):
     10   '''Maintains an in-memory cache along with a chain of other object stores to
     11   try for the same keys. This is useful for implementing a multi-layered cache.
     12   The in-memory cache is inbuilt since it's synchronous, but the object store
     13   interface is asynchronous.
     14   The rules for the object store chain are:
     15     - When setting (or deleting) items, all object stores in the hierarcy will
     16       have that item set.
     17     - When getting items, the behaviour depends on |start_empty|.
     18       - If false, each object store is tried in order. The first object
     19         store to find the item will trickle back up, setting it on all object
     20         stores higher in the hierarchy.
     21       - If true, only the first in-memory cache is checked, as though the store
     22         had been initialized with no content as opposed to the union of its
     23         delegate stores.
     24   '''
     25   def __init__(self, object_stores, start_empty=False):
     26     self._object_stores = object_stores
     27     self._start_empty = start_empty
     28     self._cache = {}
     29 
     30   def SetMulti(self, mapping):
     31     self._cache.update(mapping)
     32     for object_store in self._object_stores:
     33       object_store.SetMulti(mapping)
     34 
     35   def GetMulti(self, keys):
     36     missing_keys = list(keys)
     37     cached_items = {}
     38     for key in keys:
     39       if key in self._cache:
     40         cached_items[key] = self._cache.get(key)
     41         missing_keys.remove(key)
     42     if len(missing_keys) == 0 or self._start_empty:
     43       return Future(value=cached_items)
     44     object_store_futures = [(object_store, object_store.GetMulti(missing_keys))
     45                             for object_store in self._object_stores]
     46     def resolve():
     47       # Approach:
     48       #
     49       # Try each object store in order, until there are no more missing keys.
     50       # Don't realise the Future value of an object store that we don't need to;
     51       # this is important e.g. to avoid querying data store constantly.
     52       #
     53       # When a value is found, cache it in all object stores further up the
     54       # chain, including the object-based cache on CacheChainObjectStore.
     55       object_store_updates = []
     56       for object_store, object_store_future in object_store_futures:
     57         if len(missing_keys) == 0:
     58           break
     59         result = object_store_future.Get()
     60         for k, v in result.items():  # use items(); changes during iteration
     61           if v is None or k not in missing_keys:
     62             del result[k]
     63             continue
     64           self._cache[k] = v
     65           cached_items[k] = v
     66           missing_keys.remove(k)
     67         for _, updates in object_store_updates:
     68           updates.update(result)
     69         object_store_updates.append((object_store, {}))
     70       # Update the caches of all object stores that need it.
     71       for object_store, updates in object_store_updates:
     72         if updates:
     73           object_store.SetMulti(updates)
     74       return cached_items
     75     return Future(callback=resolve)
     76 
     77   def DelMulti(self, keys):
     78     for k in keys:
     79       self._cache.pop(k, None)
     80     for object_store in self._object_stores:
     81       object_store.DelMulti(keys)
     82