blob: 5f0a379b29c566a45f31db0d55e23e188426007e [file] [log] [blame]
# Copyright 2013 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from future import Future
from object_store import ObjectStore
class _GetMultiFuture(object):
'''A Future for GetMulti.
Params:
- |toplevel_cache| CacheChainObjectStore's cache.
- |object_store_futures| a list of (object store, future) pairs, where future
is the result of calling GetMulti on the missing keys for the object store.
- |cached_items| a mapping of cache items already in memory.
- |missing_keys| the keys that were missing from the GetMulti call
'''
def __init__(self,
toplevel_cache,
object_store_futures,
cached_items,
missing_keys):
self._toplevel_cache = toplevel_cache
self._object_store_futures = object_store_futures
self._results_so_far = cached_items
self._missing_keys = missing_keys
def Get(self):
# Approach:
#
# Try each object store in order, until there are no more missing keys.
# Don't realise the Future value of an object store that we don't need to;
# this is important e.g. to avoid querying data store constantly.
#
# When a value is found, cache it in all object stores further up the
# chain, including the object-based cache on CacheChainObjectStore.
object_store_updates = []
for object_store, object_store_future in self._object_store_futures:
if len(self._missing_keys) == 0:
break
result = object_store_future.Get()
for k, v in result.items(): # use items(); changes during iteration
if v is None or k not in self._missing_keys:
del result[k]
continue
self._toplevel_cache[k] = v
self._results_so_far[k] = v
self._missing_keys.remove(k)
for _, updates in object_store_updates:
updates.update(result)
object_store_updates.append((object_store, {}))
# Update the caches of all object stores that need it.
for object_store, updates in object_store_updates:
if updates:
object_store.SetMulti(updates)
return self._results_so_far
class CacheChainObjectStore(ObjectStore):
'''Maintains an in-memory cache along with a chain of other object stores to
try for the same keys. This is useful for implementing a multi-layered cache.
The in-memory cache is inbuilt since it's synchronous, but the object store
interface is asynchronous.
The rules for the object store chain are:
- When setting (or deleting) items, all object stores in the hierarcy will
have that item set.
- When getting items, the behaviour depends on |start_empty|.
- If false, each object store is tried in order. The first object
store to find the item will trickle back up, setting it on all object
stores higher in the hierarchy.
- If true, only the first in-memory cache is checked, as though the store
had been initialized with no content as opposed to the union of its
delegate stores.
'''
def __init__(self, object_stores, start_empty=False):
self._object_stores = object_stores
self._start_empty = start_empty
self._cache = {}
def SetMulti(self, mapping):
self._cache.update(mapping)
for object_store in self._object_stores:
object_store.SetMulti(mapping)
def GetMulti(self, keys):
missing_keys = list(keys)
cached_items = {}
for key in keys:
if key in self._cache:
cached_items[key] = self._cache.get(key)
missing_keys.remove(key)
if len(missing_keys) == 0 or self._start_empty:
return Future(value=cached_items)
object_store_futures = [(object_store, object_store.GetMulti(missing_keys))
for object_store in self._object_stores]
return Future(delegate=_GetMultiFuture(
self._cache, object_store_futures, cached_items, missing_keys))
def DelMulti(self, keys):
for k in keys:
self._cache.pop(k, None)
for object_store in self._object_stores:
object_store.DelMulti(keys)