class InvalidationCacheManager(object): """Manages cache checks, updates and invalidation keeping track of basic change and invalidation statistics. Note that this is distinct from the ArtifactCache concept, and should probably be renamed. """ class CacheValidationError(Exception): """Indicates a problem accessing the cache.""" def __init__(self, cache_key_generator, build_invalidator_dir, invalidate_dependents, fingerprint_strategy=None, invalidation_report=None, task_name=None): self._cache_key_generator = cache_key_generator self._task_name = task_name or 'UNKNOWN' self._invalidate_dependents = invalidate_dependents self._invalidator = BuildInvalidator(build_invalidator_dir) self._fingerprint_strategy = fingerprint_strategy self.invalidation_report = invalidation_report def update(self, vts): """Mark a changed or invalidated VersionedTargetSet as successfully processed.""" for vt in vts.versioned_targets: self._invalidator.update(vt.cache_key) vt.valid = True self._invalidator.update(vts.cache_key) vts.valid = True def force_invalidate(self, vts): """Force invalidation of a VersionedTargetSet.""" for vt in vts.versioned_targets: self._invalidator.force_invalidate(vt.cache_key) vt.valid = False self._invalidator.force_invalidate(vts.cache_key) vts.valid = False def check(self, targets, partition_size_hint=None, target_colors=None, topological_order=False): """Checks whether each of the targets has changed and invalidates it if so. Returns a list of VersionedTargetSet objects (either valid or invalid). The returned sets 'cover' the input targets, possibly partitioning them, with one caveat: if the FingerprintStrategy opted out of fingerprinting a target because it doesn't contribute to invalidation, then that target will be excluded from all_vts, invalid_vts, and the partitioned VTS. Callers can inspect these vts and rebuild the invalid ones, for example. If target_colors is specified, it must be a map from Target -> opaque 'color' values. Two Targets will be in the same partition only if they have the same color. """ all_vts = self.wrap_targets(targets, topological_order=topological_order) invalid_vts = filter(lambda vt: not vt.valid, all_vts) return InvalidationCheck(all_vts, invalid_vts, partition_size_hint, target_colors) @property def task_name(self): return self._task_name def wrap_targets(self, targets, topological_order=False): """Wrap targets and their computed cache keys in VersionedTargets. If the FingerprintStrategy opted out of providing a fingerprint for a target, that target will not have an associated VersionedTarget returned. Returns a list of VersionedTargets, each representing one input target. """ def vt_iter(): if topological_order: sorted_targets = [t for t in reversed(sort_targets(targets)) if t in targets] else: sorted_targets = sorted(targets) for target in sorted_targets: target_key = self._key_for(target) if target_key is not None: yield VersionedTarget(self, target, target_key) return list(vt_iter()) def previous_key(self, cache_key): return self._invalidator.previous_key(cache_key) def _key_for(self, target): try: return self._cache_key_generator.key_for_target(target, transitive=self._invalidate_dependents, fingerprint_strategy=self._fingerprint_strategy) except Exception as e: # This is a catch-all for problems we haven't caught up with and given a better diagnostic. # TODO(Eric Ayers): If you see this exception, add a fix to catch the problem earlier. exc_info = sys.exc_info() new_exception = self.CacheValidationError("Problem validating target {} in {}: {}" .format(target.id, target.address.spec_path, e)) raise self.CacheValidationError, new_exception, exc_info[2]
class InvalidationCacheManager(object): """Manages cache checks, updates and invalidation keeping track of basic change and invalidation statistics. Note that this is distinct from the ArtifactCache concept, and should probably be renamed. """ class CacheValidationError(Exception): """Indicates a problem accessing the cache.""" _STABLE_DIR_NAME = 'current' def __init__(self, results_dir_root, cache_key_generator, build_invalidator_dir, invalidate_dependents, fingerprint_strategy=None, invalidation_report=None, task_name=None, task_version=None, artifact_write_callback=lambda _: None): """ :API: public """ self._cache_key_generator = cache_key_generator self._task_name = task_name or 'UNKNOWN' self._task_version = task_version or 'Unknown_0' self._invalidate_dependents = invalidate_dependents self._invalidator = BuildInvalidator(build_invalidator_dir) self._fingerprint_strategy = fingerprint_strategy self._artifact_write_callback = artifact_write_callback self.invalidation_report = invalidation_report # Create the task-versioned prefix of the results dir, and a stable symlink to it (useful when debugging). self._results_dir_prefix = os.path.join(results_dir_root, sha1(self._task_version).hexdigest()[:12]) safe_mkdir(self._results_dir_prefix) stable_prefix = os.path.join(results_dir_root, self._STABLE_DIR_NAME) safe_delete(stable_prefix) relative_symlink(self._results_dir_prefix, stable_prefix) def update(self, vts): """Mark a changed or invalidated VersionedTargetSet as successfully processed.""" for vt in vts.versioned_targets: vt.ensure_legal() if not vt.valid: self._invalidator.update(vt.cache_key) vt.valid = True self._artifact_write_callback(vt) if not vts.valid: vts.ensure_legal() self._invalidator.update(vts.cache_key) vts.valid = True self._artifact_write_callback(vts) def force_invalidate(self, vts): """Force invalidation of a VersionedTargetSet.""" for vt in vts.versioned_targets: self._invalidator.force_invalidate(vt.cache_key) vt.valid = False self._invalidator.force_invalidate(vts.cache_key) vts.valid = False def check(self, targets, topological_order=False): """Checks whether each of the targets has changed and invalidates it if so. Returns a list of VersionedTargetSet objects (either valid or invalid). The returned sets 'cover' the input targets, with one caveat: if the FingerprintStrategy opted out of fingerprinting a target because it doesn't contribute to invalidation, then that target will be excluded from all_vts and invalid_vts. Callers can inspect these vts and rebuild the invalid ones, for example. """ all_vts = self.wrap_targets(targets, topological_order=topological_order) invalid_vts = filter(lambda vt: not vt.valid, all_vts) return InvalidationCheck(all_vts, invalid_vts) @property def task_name(self): return self._task_name def results_dir_path(self, key, stable): """Return a results directory path for the given key. :param key: A CacheKey to generate an id for. :param stable: True to use a stable subdirectory, false to use a portion of the cache key to generate a path unique to the key. """ # TODO: Shorten cache_key hashes in general? return os.path.join( self._results_dir_prefix, key.id, self._STABLE_DIR_NAME if stable else sha1(key.hash).hexdigest()[:12] ) def wrap_targets(self, targets, topological_order=False): """Wrap targets and their computed cache keys in VersionedTargets. If the FingerprintStrategy opted out of providing a fingerprint for a target, that target will not have an associated VersionedTarget returned. Returns a list of VersionedTargets, each representing one input target. """ def vt_iter(): if topological_order: target_set = set(targets) sorted_targets = [t for t in reversed(sort_targets(targets)) if t in target_set] else: sorted_targets = sorted(targets) for target in sorted_targets: target_key = self._key_for(target) if target_key is not None: yield VersionedTarget(self, target, target_key) return list(vt_iter()) def previous_key(self, cache_key): return self._invalidator.previous_key(cache_key) def _key_for(self, target): try: return self._cache_key_generator.key_for_target(target, transitive=self._invalidate_dependents, fingerprint_strategy=self._fingerprint_strategy) except Exception as e: # This is a catch-all for problems we haven't caught up with and given a better diagnostic. # TODO(Eric Ayers): If you see this exception, add a fix to catch the problem earlier. exc_info = sys.exc_info() new_exception = self.CacheValidationError("Problem validating target {} in {}: {}" .format(target.id, target.address.spec_path, e)) raise self.CacheValidationError, new_exception, exc_info[2]
class InvalidationCacheManager(object): """Manages cache checks, updates and invalidation keeping track of basic change and invalidation statistics. Note that this is distinct from the ArtifactCache concept, and should probably be renamed. """ class CacheValidationError(Exception): """Indicates a problem accessing the cache.""" _STABLE_DIR_NAME = 'current' def __init__(self, results_dir_root, cache_key_generator, build_invalidator_dir, invalidate_dependents, fingerprint_strategy=None, invalidation_report=None, task_name=None, task_version=None, artifact_write_callback=lambda _: None): """ :API: public """ self._cache_key_generator = cache_key_generator self._task_name = task_name or 'UNKNOWN' self._task_version = task_version or 'Unknown_0' self._invalidate_dependents = invalidate_dependents self._invalidator = BuildInvalidator(build_invalidator_dir) self._fingerprint_strategy = fingerprint_strategy self._artifact_write_callback = artifact_write_callback self.invalidation_report = invalidation_report # Create the task-versioned prefix of the results dir, and a stable symlink to it # (useful when debugging). self._results_dir_prefix = os.path.join( results_dir_root, sha1(self._task_version).hexdigest()[:12]) safe_mkdir(self._results_dir_prefix) stable_prefix = os.path.join(results_dir_root, self._STABLE_DIR_NAME) safe_delete(stable_prefix) relative_symlink(self._results_dir_prefix, stable_prefix) def update(self, vts): """Mark a changed or invalidated VersionedTargetSet as successfully processed.""" for vt in vts.versioned_targets: vt.ensure_legal() if not vt.valid: self._invalidator.update(vt.cache_key) vt.valid = True self._artifact_write_callback(vt) if not vts.valid: vts.ensure_legal() self._invalidator.update(vts.cache_key) vts.valid = True self._artifact_write_callback(vts) def force_invalidate(self, vts): """Force invalidation of a VersionedTargetSet.""" for vt in vts.versioned_targets: self._invalidator.force_invalidate(vt.cache_key) vt.valid = False self._invalidator.force_invalidate(vts.cache_key) vts.valid = False def check(self, targets, topological_order=False): """Checks whether each of the targets has changed and invalidates it if so. Returns a list of VersionedTargetSet objects (either valid or invalid). The returned sets 'cover' the input targets, with one caveat: if the FingerprintStrategy opted out of fingerprinting a target because it doesn't contribute to invalidation, then that target will be excluded from all_vts and invalid_vts. Callers can inspect these vts and rebuild the invalid ones, for example. """ all_vts = self.wrap_targets(targets, topological_order=topological_order) invalid_vts = filter(lambda vt: not vt.valid, all_vts) return InvalidationCheck(all_vts, invalid_vts) @property def task_name(self): return self._task_name def results_dir_path(self, key, stable): """Return a results directory path for the given key. :param key: A CacheKey to generate an id for. :param stable: True to use a stable subdirectory, false to use a portion of the cache key to generate a path unique to the key. """ # TODO: Shorten cache_key hashes in general? return os.path.join( self._results_dir_prefix, key.id, self._STABLE_DIR_NAME if stable else sha1(key.hash).hexdigest()[:12]) def wrap_targets(self, targets, topological_order=False): """Wrap targets and their computed cache keys in VersionedTargets. If the FingerprintStrategy opted out of providing a fingerprint for a target, that target will not have an associated VersionedTarget returned. Returns a list of VersionedTargets, each representing one input target. """ def vt_iter(): if topological_order: target_set = set(targets) sorted_targets = [ t for t in reversed(sort_targets(targets)) if t in target_set ] else: sorted_targets = sorted(targets) for target in sorted_targets: target_key = self._key_for(target) if target_key is not None: yield VersionedTarget(self, target, target_key) return list(vt_iter()) def previous_key(self, cache_key): return self._invalidator.previous_key(cache_key) def _key_for(self, target): try: return self._cache_key_generator.key_for_target( target, transitive=self._invalidate_dependents, fingerprint_strategy=self._fingerprint_strategy) except Exception as e: # This is a catch-all for problems we haven't caught up with and given a better diagnostic. # TODO(Eric Ayers): If you see this exception, add a fix to catch the problem earlier. exc_info = sys.exc_info() new_exception = self.CacheValidationError( "Problem validating target {} in {}: {}".format( target.id, target.address.spec_path, e)) raise self.CacheValidationError, new_exception, exc_info[2]