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 posixpath | 5 import posixpath |
6 import sys | 6 import sys |
7 | 7 |
8 from file_system import FileSystem, StatInfo, FileNotFoundError | 8 from file_system import FileSystem, StatInfo, FileNotFoundError |
9 from future import Future | 9 from future import Future |
10 from path_util import IsDirectory, ToDirectory | 10 from path_util import IsDirectory, ToDirectory |
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
50 return dir_stat | 50 return dir_stat |
51 # Was a file stat. Extract that file. | 51 # Was a file stat. Extract that file. |
52 file_version = dir_stat.child_versions.get(file_path) | 52 file_version = dir_stat.child_versions.get(file_path) |
53 if file_version is None: | 53 if file_version is None: |
54 raise FileNotFoundError('No stat found for %s in %s (found %s)' % | 54 raise FileNotFoundError('No stat found for %s in %s (found %s)' % |
55 (path, dir_path, dir_stat.child_versions)) | 55 (path, dir_path, dir_stat.child_versions)) |
56 return StatInfo(file_version) | 56 return StatInfo(file_version) |
57 | 57 |
58 dir_stat = self._stat_object_store.Get(dir_path).Get() | 58 dir_stat = self._stat_object_store.Get(dir_path).Get() |
59 if dir_stat is not None: | 59 if dir_stat is not None: |
60 return Future(value=make_stat_info(dir_stat)) | 60 return Future(callback=lambda: make_stat_info(dir_stat)) |
61 | 61 |
62 def next(dir_stat): | 62 def next(dir_stat): |
63 assert dir_stat is not None # should have raised a FileNotFoundError | 63 assert dir_stat is not None # should have raised a FileNotFoundError |
64 # We only ever need to cache the dir stat. | 64 # We only ever need to cache the dir stat. |
65 self._stat_object_store.Set(dir_path, dir_stat) | 65 self._stat_object_store.Set(dir_path, dir_stat) |
66 return make_stat_info(dir_stat) | 66 return make_stat_info(dir_stat) |
67 return self._MemoizedStatAsyncFromFileSystem(dir_path).Then(next) | 67 return self._MemoizedStatAsyncFromFileSystem(dir_path).Then(next) |
68 | 68 |
69 @memoize | 69 @memoize |
70 def _MemoizedStatAsyncFromFileSystem(self, dir_path): | 70 def _MemoizedStatAsyncFromFileSystem(self, dir_path): |
71 '''This is a simple wrapper to memoize Futures to directory stats, since | 71 '''This is a simple wrapper to memoize Futures to directory stats, since |
72 StatAsync makes heavy use of it. Only cache directories so that the | 72 StatAsync makes heavy use of it. Only cache directories so that the |
73 memoized cache doesn't blow up. | 73 memoized cache doesn't blow up. |
74 ''' | 74 ''' |
75 assert IsDirectory(dir_path) | 75 assert IsDirectory(dir_path) |
76 return self._file_system.StatAsync(dir_path) | 76 return self._file_system.StatAsync(dir_path) |
77 | 77 |
78 def Read(self, paths, skip_not_found=False): | 78 def Read(self, paths, skip_not_found=False): |
79 '''Reads a list of files. If a file is in memcache and it is not out of | 79 '''Reads a list of files. If a file is cached and it is not out of |
80 date, it is returned. Otherwise, the file is retrieved from the file system. | 80 date, it is returned. Otherwise, the file is retrieved from the file system. |
81 ''' | 81 ''' |
| 82 # Files which aren't found are cached in the read object store as |
| 83 # (path, None, None). This is to prevent re-reads of files we know |
| 84 # do not exist. |
82 cached_read_values = self._read_object_store.GetMulti(paths).Get() | 85 cached_read_values = self._read_object_store.GetMulti(paths).Get() |
83 cached_stat_values = self._stat_object_store.GetMulti(paths).Get() | 86 cached_stat_values = self._stat_object_store.GetMulti(paths).Get() |
84 | 87 |
85 # Populate a map of paths to Futures to their stat. They may have already | 88 # Populate a map of paths to Futures to their stat. They may have already |
86 # been cached in which case their Future will already have been constructed | 89 # been cached in which case their Future will already have been constructed |
87 # with a value. | 90 # with a value. |
88 stat_futures = {} | 91 stat_futures = {} |
89 | 92 |
90 def handle(error): | 93 def handle(error): |
91 if isinstance(error, FileNotFoundError): | 94 if isinstance(error, FileNotFoundError): |
92 return None | 95 return None |
93 raise error | 96 raise error |
94 | 97 |
95 for path in paths: | 98 for path in paths: |
96 stat_value = cached_stat_values.get(path) | 99 stat_value = cached_stat_values.get(path) |
97 if stat_value is None: | 100 if stat_value is None: |
98 stat_future = self.StatAsync(path) | 101 stat_future = self.StatAsync(path) |
99 if skip_not_found: | 102 if skip_not_found: |
100 stat_future = stat_future.Then(lambda x: x, handle) | 103 stat_future = stat_future.Then(lambda x: x, handle) |
101 else: | 104 else: |
102 stat_future = Future(value=stat_value) | 105 stat_future = Future(value=stat_value) |
103 stat_futures[path] = stat_future | 106 stat_futures[path] = stat_future |
104 | 107 |
105 # Filter only the cached data which is fresh by comparing to the latest | 108 # Filter only the cached data which is up to date by comparing to the latest |
106 # stat. The cached read data includes the cached version. Remove it for | 109 # stat. The cached read data includes the cached version. Remove it for |
107 # the result returned to callers. | 110 # the result returned to callers. |version| == None implies a non-existent |
108 fresh_data = dict( | 111 # file, so skip it. |
| 112 up_to_date_data = dict( |
109 (path, data) for path, (data, version) in cached_read_values.iteritems() | 113 (path, data) for path, (data, version) in cached_read_values.iteritems() |
110 if stat_futures[path].Get().version == version) | 114 if version is not None and stat_futures[path].Get().version == version) |
111 | 115 |
112 if len(fresh_data) == len(paths): | 116 if skip_not_found: |
| 117 # Filter out paths which we know do not exist, i.e. if |path| is in |
| 118 # |cached_read_values| *and* has a None version, then it doesn't exist. |
| 119 # See the above declaration of |cached_read_values| for more information. |
| 120 paths = [path for path in paths |
| 121 if cached_read_values.get(path, (None, True))[1]] |
| 122 |
| 123 if len(up_to_date_data) == len(paths): |
113 # Everything was cached and up-to-date. | 124 # Everything was cached and up-to-date. |
114 return Future(value=fresh_data) | 125 return Future(value=up_to_date_data) |
115 | 126 |
116 def next(new_results): | 127 def next(new_results): |
117 # Update the cache. This is a path -> (data, version) mapping. | 128 # Update the cache. This is a path -> (data, version) mapping. |
118 self._read_object_store.SetMulti( | 129 self._read_object_store.SetMulti( |
119 dict((path, (new_result, stat_futures[path].Get().version)) | 130 dict((path, (new_result, stat_futures[path].Get().version)) |
120 for path, new_result in new_results.iteritems())) | 131 for path, new_result in new_results.iteritems())) |
121 new_results.update(fresh_data) | 132 # Update the read cache to include files that weren't found, to prevent |
| 133 # constantly trying to read a file we now know doesn't exist. |
| 134 self._read_object_store.SetMulti( |
| 135 dict((path, (None, None)) for path in paths |
| 136 if stat_futures[path].Get() is None)) |
| 137 new_results.update(up_to_date_data) |
122 return new_results | 138 return new_results |
123 # Read in the values that were uncached or old. | 139 # Read in the values that were uncached or old. |
124 return self._file_system.Read(set(paths) - set(fresh_data.iterkeys()), | 140 return self._file_system.Read(set(paths) - set(up_to_date_data.iterkeys()), |
125 skip_not_found=skip_not_found).Then(next) | 141 skip_not_found=skip_not_found).Then(next) |
126 | 142 |
127 def GetIdentity(self): | 143 def GetIdentity(self): |
128 return self._file_system.GetIdentity() | 144 return self._file_system.GetIdentity() |
129 | 145 |
130 def __repr__(self): | 146 def __repr__(self): |
131 return '%s of <%s>' % (type(self).__name__, repr(self._file_system)) | 147 return '%s of <%s>' % (type(self).__name__, repr(self._file_system)) |
OLD | NEW |