Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 import logging | 5 import logging |
| 6 import posixpath | 6 import posixpath |
| 7 import sys | 7 import sys |
| 8 import traceback | |
|
Ken Rockot(use gerrit already)
2015/05/26 00:26:23
unused - removed in ps#2
| |
| 8 | 9 |
| 9 from file_system import FileSystem, StatInfo, FileNotFoundError | 10 from file_system import FileSystem, StatInfo, FileNotFoundError |
| 10 from future import All, Future | 11 from future import All, Future |
| 11 from path_util import AssertIsDirectory, IsDirectory, ToDirectory | 12 from path_util import AssertIsDirectory, IsDirectory, ToDirectory |
| 12 from third_party.json_schema_compiler.memoize import memoize | 13 from third_party.json_schema_compiler.memoize import memoize |
| 13 | 14 |
| 14 | 15 |
| 15 class CachingFileSystem(FileSystem): | 16 class CachingFileSystem(FileSystem): |
| 16 '''FileSystem which implements a caching layer on top of |file_system|. If | 17 '''FileSystem which implements a caching layer on top of |file_system|. If |
| 17 |fail_on_miss| is True then cache misses throw a FileNotFoundError rather than | 18 |fail_on_miss| is True then cache misses throw a FileNotFoundError rather than |
| 18 falling back onto the underlying FileSystem. | 19 falling back onto the underlying FileSystem. |
| 19 | 20 |
| 20 If the underlying FileSystem is versioned (i.e., it implements GetVersion to | 21 If the underlying FileSystem is versioned (i.e., it implements GetVersion to |
| 21 return something other than None), this will create a persistent stat cache | 22 return something other than None), this will create a persistent stat cache |
| 22 (keyed on the FileSystem instance's version) as an additional optimization. | 23 (keyed on the FileSystem instance's version) as an additional optimization. |
| 23 ''' | 24 ''' |
| 24 def __init__(self, file_system, object_store_creator, fail_on_miss=False): | 25 def __init__(self, file_system, object_store_creator, fail_on_miss=False): |
| 25 self._file_system = file_system | 26 self._file_system = file_system |
| 26 self._fail_on_miss = fail_on_miss | 27 self._fail_on_miss = fail_on_miss |
| 27 def create_object_store(category, try_versioning=False, **optargs): | 28 def create_object_store(category, start_empty=True): |
| 28 version = file_system.GetVersion() | |
| 29 versioned = try_versioning and version is not None | |
| 30 if versioned: | |
| 31 identity = '%s/%s' % (file_system.GetIdentity(), version) | |
| 32 else: | |
| 33 identity = file_system.GetIdentity() | |
| 34 optargs['start_empty'] = optargs.get('start_empty', not versioned) | |
| 35 return object_store_creator.Create( | 29 return object_store_creator.Create( |
| 36 CachingFileSystem, | 30 CachingFileSystem, |
| 37 category='%s/%s' % (identity, category), | 31 category='%s/%s' % (file_system.GetIdentity(), category), |
| 38 **optargs) | 32 start_empty=start_empty) |
| 39 self._stat_cache = create_object_store('stat', try_versioning=True) | 33 # We only start the stat cache empty if |fail_on_miss| is False, i.e. if |
| 40 # The read caches can start populated (start_empty=False) because file | 34 # we're NOT running on a live instance and we can afford to fall back onto |
| 41 # updates are picked up by the stat, so it doesn't need the force-refresh | 35 # the underlying FileSystem impl. |
| 42 # which starting empty is designed for. Without this optimisation, cron | 36 self._stat_cache = create_object_store('stat', start_empty=not fail_on_miss) |
| 43 # runs are extra slow. | |
| 44 self._read_cache = create_object_store('read', start_empty=False) | 37 self._read_cache = create_object_store('read', start_empty=False) |
| 45 self._walk_cache = create_object_store('walk', start_empty=False) | 38 self._walk_cache = create_object_store('walk', start_empty=False) |
| 46 | 39 |
| 47 def Refresh(self): | 40 def Refresh(self): |
| 48 return self._file_system.Refresh() | 41 return self._file_system.Refresh() |
| 49 | 42 |
| 50 def StatAsync(self, path): | 43 def StatAsync(self, path): |
| 51 '''Stats the directory given, or if a file is given, stats the file's parent | 44 '''Stats the directory given, or if a file is given, stats the file's parent |
| 52 directory to get info about the file. | 45 directory to get info about the file. |
| 53 ''' | 46 ''' |
| (...skipping 16 matching lines...) Expand all Loading... | |
| 70 return StatInfo(file_version) | 63 return StatInfo(file_version) |
| 71 | 64 |
| 72 def raise_cache_miss(path): | 65 def raise_cache_miss(path): |
| 73 raise FileNotFoundError('Got cache miss when trying to stat %s' % path) | 66 raise FileNotFoundError('Got cache miss when trying to stat %s' % path) |
| 74 | 67 |
| 75 dir_stat = self._stat_cache.Get(dir_path).Get() | 68 dir_stat = self._stat_cache.Get(dir_path).Get() |
| 76 if dir_stat is not None: | 69 if dir_stat is not None: |
| 77 return Future(callback=lambda: make_stat_info(dir_stat)) | 70 return Future(callback=lambda: make_stat_info(dir_stat)) |
| 78 | 71 |
| 79 if self._fail_on_miss: | 72 if self._fail_on_miss: |
| 80 logging.warning('Bailing on stat cache miss for %s' % dir_path) | 73 logging.warning('Bailing on stat cache miss for %s on %s' % |
| 74 (dir_path, self.GetIdentity())) | |
| 81 return Future(callback=lambda: raise_cache_miss(dir_path)) | 75 return Future(callback=lambda: raise_cache_miss(dir_path)) |
| 82 | 76 |
| 83 def next(dir_stat): | 77 def next(dir_stat): |
| 84 assert dir_stat is not None # should have raised a FileNotFoundError | 78 assert dir_stat is not None # should have raised a FileNotFoundError |
| 85 # We only ever need to cache the dir stat. | 79 # We only ever need to cache the dir stat. |
| 86 self._stat_cache.Set(dir_path, dir_stat) | 80 self._stat_cache.Set(dir_path, dir_stat) |
| 87 return make_stat_info(dir_stat) | 81 return make_stat_info(dir_stat) |
| 88 return self._MemoizedStatAsyncFromFileSystem(dir_path).Then(next) | 82 return self._MemoizedStatAsyncFromFileSystem(dir_path).Then(next) |
| 89 | 83 |
| 90 @memoize | 84 @memoize |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 134 (path, data) for path, (data, version) in cached_read_values.iteritems() | 128 (path, data) for path, (data, version) in cached_read_values.iteritems() |
| 135 if version is not None and stat_futures[path].Get().version == version) | 129 if version is not None and stat_futures[path].Get().version == version) |
| 136 | 130 |
| 137 if skip_not_found: | 131 if skip_not_found: |
| 138 # Filter out paths which we know do not exist, i.e. if |path| is in | 132 # Filter out paths which we know do not exist, i.e. if |path| is in |
| 139 # |cached_read_values| *and* has a None version, then it doesn't exist. | 133 # |cached_read_values| *and* has a None version, then it doesn't exist. |
| 140 # See the above declaration of |cached_read_values| for more information. | 134 # See the above declaration of |cached_read_values| for more information. |
| 141 paths = [path for path in paths | 135 paths = [path for path in paths |
| 142 if cached_read_values.get(path, (None, True))[1]] | 136 if cached_read_values.get(path, (None, True))[1]] |
| 143 | 137 |
| 144 if len(up_to_date_data) == len(paths): | 138 remaining_paths = set(paths) - set(up_to_date_data.iterkeys()) |
| 139 if len(remaining_paths) == 0: | |
| 145 # Everything was cached and up-to-date. | 140 # Everything was cached and up-to-date. |
| 146 return Future(value=up_to_date_data) | 141 return Future(value=up_to_date_data) |
| 147 | 142 |
| 143 def raise_cache_miss(paths): | |
| 144 raise FileNotFoundError('Got cache miss when trying to stat %s' % paths) | |
| 145 | |
| 146 if self._fail_on_miss: | |
| 147 # Ignore missing values and return anyway. | |
| 148 logging.warn('Read cache miss for %s on %s' % | |
|
Ken Rockot(use gerrit already)
2015/05/26 00:26:23
This was pretty useful in debugging, so I was goin
| |
| 149 (remaining_paths, self.GetIdentity())) | |
| 150 return Future(callback=lambda: raise_cache_miss(remaining_paths)) | |
| 151 | |
| 148 def next(new_results): | 152 def next(new_results): |
| 149 # Update the cache. This is a path -> (data, version) mapping. | 153 # Update the cache. This is a path -> (data, version) mapping. |
| 150 self._read_cache.SetMulti( | 154 self._read_cache.SetMulti( |
| 151 dict((path, (new_result, stat_futures[path].Get().version)) | 155 dict((path, (new_result, stat_futures[path].Get().version)) |
| 152 for path, new_result in new_results.iteritems())) | 156 for path, new_result in new_results.iteritems())) |
| 153 # Update the read cache to include files that weren't found, to prevent | 157 # Update the read cache to include files that weren't found, to prevent |
| 154 # constantly trying to read a file we now know doesn't exist. | 158 # constantly trying to read a file we now know doesn't exist. |
| 155 self._read_cache.SetMulti( | 159 self._read_cache.SetMulti( |
| 156 dict((path, (None, None)) for path in paths | 160 dict((path, (None, None)) for path in paths |
| 157 if stat_futures[path].Get() is None)) | 161 if stat_futures[path].Get() is None)) |
| 158 new_results.update(up_to_date_data) | 162 new_results.update(up_to_date_data) |
| 159 return new_results | 163 return new_results |
| 164 | |
| 160 # Read in the values that were uncached or old. | 165 # Read in the values that were uncached or old. |
| 161 return self._file_system.Read(set(paths) - set(up_to_date_data.iterkeys()), | 166 return self._file_system.Read(remaining_paths, |
| 162 skip_not_found=skip_not_found).Then(next) | 167 skip_not_found=skip_not_found).Then(next) |
| 163 | 168 |
| 164 def GetCommitID(self): | 169 def GetCommitID(self): |
| 165 return self._file_system.GetCommitID() | 170 return self._file_system.GetCommitID() |
| 166 | 171 |
| 167 def GetPreviousCommitID(self): | 172 def GetPreviousCommitID(self): |
| 168 return self._file_system.GetPreviousCommitID() | 173 return self._file_system.GetPreviousCommitID() |
| 169 | 174 |
| 170 def Walk(self, root, depth=-1): | 175 def Walk(self, root, depth=-1): |
| 171 '''Overrides FileSystem.Walk() to provide caching functionality. | 176 '''Overrides FileSystem.Walk() to provide caching functionality. |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 190 return self._file_system.Walk(root, depth=depth, file_lister=file_lister) | 195 return self._file_system.Walk(root, depth=depth, file_lister=file_lister) |
| 191 | 196 |
| 192 def GetIdentity(self): | 197 def GetIdentity(self): |
| 193 return self._file_system.GetIdentity() | 198 return self._file_system.GetIdentity() |
| 194 | 199 |
| 195 def GetVersion(self): | 200 def GetVersion(self): |
| 196 return self._file_system.GetVersion() | 201 return self._file_system.GetVersion() |
| 197 | 202 |
| 198 def __repr__(self): | 203 def __repr__(self): |
| 199 return '%s of <%s>' % (type(self).__name__, repr(self._file_system)) | 204 return '%s of <%s>' % (type(self).__name__, repr(self._file_system)) |
| OLD | NEW |