Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(226)

Unified Diff: client/named_cache.py

Issue 2875113002: Revert "named caches: move instead of symlinking" (Closed)
Patch Set: Created 3 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | client/run_isolated.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: client/named_cache.py
diff --git a/client/named_cache.py b/client/named_cache.py
index 2f8f8cf659e40326f38a9cb86f9252886fa05435..e3bf5e81c865e62c1235b0d4cb1f13917159c237 100644
--- a/client/named_cache.py
+++ b/client/named_cache.py
@@ -28,15 +28,15 @@ class Error(Exception):
class CacheManager(object):
- """Manages cache directories exposed to a task.
+ """Manages cache directories exposed to a task as symlinks.
A task can specify that caches should be present on a bot. A cache is
tuple (name, path), where
name is a short identifier that describes the contents of the cache, e.g.
"git_v8" could be all git repositories required by v8 builds, or
"build_chromium" could be build artefacts of the Chromium.
- path is a directory path relative to the task run dir. Cache installation
- puts the requested cache directory at the path.
+ path is a directory path relative to the task run dir. It will be mapped
+ to the cache directory persisted on the bot.
"""
def __init__(self, root_dir):
@@ -44,9 +44,8 @@ class CacheManager(object):
|root_dir| is a directory for persistent cache storage.
"""
- assert isinstance(root_dir, unicode), root_dir
assert file_path.isabs(root_dir), root_dir
- self.root_dir = root_dir
+ self.root_dir = unicode(root_dir)
self._lock = threading_utils.LockWithAssert()
# LRU {cache_name -> cache_location}
# It is saved to |root_dir|/state.json.
@@ -54,9 +53,9 @@ class CacheManager(object):
@contextlib.contextmanager
def open(self, time_fn=None):
- """Opens NamedCaches for mutation operations, such as install.
+ """Opens NamedCaches for mutation operations, such as request or trim.
- Only one caller can open the cache manager at a time. If the same thread
+ Only on caller can open the cache manager at a time. If the same thread
calls this function after opening it earlier, the call will deadlock.
time_fn is a function that returns timestamp (float) and used to take
@@ -87,14 +86,48 @@ class CacheManager(object):
def __len__(self):
"""Returns number of items in the cache.
- NamedCache must be open.
+ Requires NamedCache to be open.
"""
return len(self._lru)
+ def request(self, name):
+ """Returns an absolute path to the directory of the named cache.
+
+ Creates a cache directory if it does not exist yet.
+
+ Requires NamedCache to be open.
+ """
+ self._lock.assert_locked()
+ assert isinstance(name, basestring), name
+ path = self._lru.get(name)
+ create_named_link = False
+ if path is None:
+ path = self._allocate_dir()
+ create_named_link = True
+ logging.info('Created %r for %r', path, name)
+ abs_path = os.path.join(self.root_dir, path)
+
+ # TODO(maruel): That's weird, it should exist already.
+ file_path.ensure_tree(abs_path)
+ self._lru.add(name, path)
+
+ if create_named_link:
+ # Create symlink <root_dir>/<named>/<name> -> <root_dir>/<short name>
+ # for user convenience.
+ named_path = self._get_named_path(name)
+ if os.path.exists(named_path):
+ file_path.remove(named_path)
+ else:
+ file_path.ensure_tree(os.path.dirname(named_path))
+ logging.info('Symlink %r to %r', named_path, abs_path)
+ fs.symlink(abs_path, named_path)
+
+ return abs_path
+
def get_oldest(self):
"""Returns name of the LRU cache or None.
- NamedCache must be open.
+ Requires NamedCache to be open.
"""
self._lock.assert_locked()
try:
@@ -105,7 +138,7 @@ class CacheManager(object):
def get_timestamp(self, name):
"""Returns timestamp of last use of an item.
- NamedCache must be open.
+ Requires NamedCache to be open.
Raises KeyError if cache is not found.
"""
@@ -113,96 +146,46 @@ class CacheManager(object):
assert isinstance(name, basestring), name
return self._lru.get_timestamp(name)
- @property
- def available(self):
- """Returns a set of names of available caches.
-
- NamedCache must be open.
- """
- self._lock.assert_locked()
- return self._lru.keys_set()
+ @contextlib.contextmanager
+ def create_symlinks(self, root, named_caches):
+ """Creates symlinks in |root| for the specified named_caches.
- def install(self, path, name):
- """Moves the directory for the specified named cache to |path|.
+ named_caches must be a list of (name, path) tuples.
- NamedCache must be open. path must be absolute, unicode and must not exist.
+ Requires NamedCache to be open.
- Raises Error if cannot install the cache.
+ Raises Error if cannot create a symlink.
"""
self._lock.assert_locked()
- logging.info('Installing named cache %r to %r', name, path)
- try:
- _check_abs(path)
- if os.path.isdir(path):
- raise Error('installation directory %r already exists' % path)
-
- rel_cache = self._lru.get(name)
- if rel_cache:
- abs_cache = os.path.join(self.root_dir, rel_cache)
- if os.path.isdir(abs_cache):
- logging.info('Moving %r to %r', abs_cache, path)
- file_path.ensure_tree(os.path.dirname(path))
- fs.rename(abs_cache, path)
- self._remove(name)
- return
-
- logging.warning('directory for named cache %r does not exist', name)
- self._remove(name)
-
- # The named cache does not exist, create an empty directory.
- # When uninstalling, we will move it back to the cache and create an
- # an entry.
- file_path.ensure_tree(path)
- except (OSError, Error) as ex:
- raise Error(
- 'cannot install cache named %r at %r: %s' % (
- name, path, ex))
-
- def uninstall(self, path, name):
- """Moves the cache directory back. Opposite to install().
-
- NamedCache must be open. path must be absolute and unicode.
-
- Raises Error if cannot uninstall the cache.
+ for name, path in named_caches:
+ logging.info('Named cache %r -> %r', name, path)
+ try:
+ _validate_named_cache_path(path)
+ symlink_path = os.path.abspath(os.path.join(root, path))
+ file_path.ensure_tree(os.path.dirname(symlink_path))
+ requested = self.request(name)
+ logging.info('Symlink %r to %r', symlink_path, requested)
+ fs.symlink(requested, symlink_path)
+ except (OSError, Error) as ex:
+ raise Error(
+ 'cannot create a symlink for cache named "%s" at "%s": %s' % (
+ name, symlink_path, ex))
+
+ def delete_symlinks(self, root, named_caches):
+ """Deletes symlinks from |root| for the specified named_caches.
+
+ named_caches must be a list of (name, path) tuples.
"""
- logging.info('Uninstalling named cache %r from %r', name, path)
- try:
- _check_abs(path)
- if not os.path.isdir(path):
- logging.warning(
- 'Directory %r does not exist anymore. Cache lost.', path)
- return
-
- rel_cache = self._lru.get(name)
- if rel_cache:
- # Do not crash because cache already exists.
- logging.warning('overwriting an existing named cache %r', name)
- create_named_link = False
- else:
- rel_cache = self._allocate_dir()
- create_named_link = True
-
- # Move the dir and create an entry for the named cache.
- abs_cache = os.path.join(self.root_dir, rel_cache)
- logging.info('Moving %r to %r', path, abs_cache)
- file_path.ensure_tree(os.path.dirname(abs_cache))
- fs.rename(path, abs_cache)
- self._lru.add(name, rel_cache)
-
- if create_named_link:
- # Create symlink <root_dir>/<named>/<name> -> <root_dir>/<short name>
- # for user convenience.
- named_path = self._get_named_path(name)
- if os.path.exists(named_path):
- file_path.remove(named_path)
- else:
- file_path.ensure_tree(os.path.dirname(named_path))
- fs.symlink(abs_cache, named_path)
- logging.info('Created symlink %r to %r', named_path, abs_cache)
- except (OSError, Error) as ex:
- raise Error(
- 'cannot uninstall cache named %r at %r: %s' % (
- name, path, ex))
+ for name, path in named_caches:
+ logging.info('Unlinking named cache "%s"', name)
+ try:
+ _validate_named_cache_path(path)
+ symlink_path = os.path.abspath(os.path.join(root, path))
+ fs.unlink(symlink_path)
+ except (OSError, Error) as ex:
+ raise Error(
+ 'cannot unlink cache named "%s" at "%s": %s' % (
+ name, symlink_path, ex))
def trim(self, min_free_space):
"""Purges cache.
@@ -212,7 +195,7 @@ class CacheManager(object):
If min_free_space is None, disk free space is not checked.
- NamedCache must be open.
+ Requires NamedCache to be open.
Returns:
Number of caches deleted.
@@ -228,16 +211,22 @@ class CacheManager(object):
while ((min_free_space and free_space < min_free_space)
or len(self._lru) > MAX_CACHE_SIZE):
logging.info(
- 'Making space for named cache %d > %d or %d > %d',
+ 'Making space for named cache %s > %s or %s > %s',
free_space, min_free_space, len(self._lru), MAX_CACHE_SIZE)
try:
- name, _ = self._lru.get_oldest()
+ name, (path, _) = self._lru.get_oldest()
except KeyError:
return total
- logging.info('Removing named cache %r', name)
- self._remove(name)
+ named_dir = self._get_named_path(name)
+ if fs.islink(named_dir):
+ fs.unlink(named_dir)
+ path_abs = os.path.join(self.root_dir, path)
+ if os.path.isdir(path_abs):
+ logging.info('Removing named cache %s', path_abs)
+ file_path.rmtree(path_abs)
if min_free_space:
free_space = file_path.get_free_space(self.root_dir)
+ self._lru.pop(name)
total += 1
return total
@@ -262,28 +251,6 @@ class CacheManager(object):
tried.add(rel_path)
raise Error('could not allocate a new cache dir, too many cache dirs')
- def _remove(self, name):
- """Removes a cache directory and entry.
-
- NamedCache must be open.
-
- Returns:
- Number of caches deleted.
- """
- self._lock.assert_locked()
- rel_path = self._lru.get(name)
- if not rel_path:
- return
-
- named_dir = self._get_named_path(name)
- if fs.islink(named_dir):
- fs.unlink(named_dir)
-
- abs_path = os.path.join(self.root_dir, rel_path)
- if os.path.isdir(abs_path):
- file_path.rmtree(abs_path)
- self._lru.pop(name)
-
def _get_named_path(self, name):
return os.path.join(self.root_dir, 'named', name)
@@ -299,7 +266,7 @@ def add_named_cache_options(parser):
help='A named cache to request. Accepts two arguments, name and path. '
'name identifies the cache, must match regex [a-z0-9_]{1,4096}. '
'path is a path relative to the run dir where the cache directory '
- 'must be put to. '
+ 'must be symlinked to. '
'This option can be specified more than once.')
group.add_option(
'--named-cache-root',
@@ -314,16 +281,16 @@ def process_named_cache_options(parser, options):
for name, path in options.named_caches:
if not CACHE_NAME_RE.match(name):
parser.error(
- 'cache name %r does not match %r' % (name, CACHE_NAME_RE.pattern))
+ 'cache name "%s" does not match %s' % (name, CACHE_NAME_RE.pattern))
if not path:
parser.error('cache path cannot be empty')
if options.named_cache_root:
- return CacheManager(unicode(os.path.abspath(options.named_cache_root)))
+ return CacheManager(os.path.abspath(options.named_cache_root))
return None
-def _check_abs(path):
- if not isinstance(path, unicode):
- raise Error('named cache installation path must be unicode')
- if not os.path.isabs(path):
- raise Error('named cache installation path must be absolute')
+def _validate_named_cache_path(path):
+ if os.path.isabs(path):
+ raise Error('named cache path must not be absolute')
+ if '..' in path.split(os.path.sep):
+ raise Error('named cache path must not contain ".."')
« no previous file with comments | « no previous file | client/run_isolated.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698