async def wrapper(*args, **kwargs): if not configuration.configured(): raise NotConfiguredCacheCalledException() configuration_snapshot = MutableCacheConfiguration.initialized_with(configuration) force_refresh = kwargs.pop('force_refresh_memoized', False) key = configuration_snapshot.key_extractor().format_key(method, args, kwargs) current_entry = await configuration_snapshot.storage().get(key) # type: Optional[CacheEntry] if current_entry is not None: configuration_snapshot.eviction_strategy().mark_read(key) now = datetime.datetime.utcnow() def value_future_provider(): return _apply_timeout(configuration_snapshot.method_timeout(), method(*args, **kwargs)) if current_entry is None: logger.debug('Creating (blocking) entry for key %s', key) result = await refresh(current_entry, key, value_future_provider, configuration_snapshot) elif force_refresh: logger.debug('Forced entry update (blocking) for key %s', key) result = await refresh(current_entry, key, value_future_provider, configuration_snapshot) elif current_entry.expires_after <= now: logger.debug('Entry expiration reached - entry update (blocking) for key %s', key) result = await refresh(None, key, value_future_provider, configuration_snapshot) elif current_entry.update_after <= now: logger.debug('Entry update point expired - entry update (async - current entry returned) for key %s', key) _call_soon(refresh, current_entry, key, value_future_provider, configuration_snapshot) result = current_entry else: result = current_entry return result.value
async def refresh(actual_entry: Optional[CacheEntry], key: CacheKey, value_future_provider: Callable[[], asyncio.Future], configuration_snapshot: CacheConfiguration): if actual_entry is None and update_statuses.is_being_updated(key): logger.debug( 'As entry expired, waiting for results of concurrent refresh %s', key) entry = await update_statuses.await_updated(key) if entry is None: raise CachedMethodFailedException( 'Concurrent refresh failed to complete') return entry elif actual_entry is not None and update_statuses.is_being_updated( key): logger.debug( 'As update point reached but concurrent update already in progress, ' 'relying on concurrent refresh to finish %s', key) return actual_entry elif not update_statuses.is_being_updated(key): update_statuses.mark_being_updated(key) try: value_future = value_future_provider() value = await value_future offered_entry = configuration_snapshot.entry_builder().build( key, value) await configuration_snapshot.storage().offer( key, offered_entry) update_statuses.mark_updated(key, offered_entry) logger.debug('Successfully refreshed cache for key %s', key) eviction_strategy = configuration_snapshot.eviction_strategy() eviction_strategy.mark_written(key, offered_entry) to_release = eviction_strategy.next_to_release() if to_release is not None: _call_soon(try_release, to_release, configuration_snapshot) return offered_entry except _timeout_error_type() as e: logger.debug('Timeout for %s: %s', key, e) update_statuses.mark_update_aborted(key) raise CachedMethodFailedException('Refresh timed out') except Exception as e: logger.debug('Error while refreshing cache for %s: %s', key, e) update_statuses.mark_update_aborted(key) raise CachedMethodFailedException('Refresh failed to complete', e)