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 from file_system import FileSystem | 5 import logging |
| 6 import time |
| 7 |
| 8 from file_system import FileSystem, StatInfo |
6 from future import Future | 9 from future import Future |
7 import appengine_memcache as memcache | 10 import appengine_memcache as memcache |
8 | 11 |
9 class MemcacheFileSystem(FileSystem): | 12 class MemcacheFileSystem(FileSystem): |
10 """FileSystem implementation which memcaches the results of Read. | 13 """FileSystem implementation which memcaches the results of Read. |
11 """ | 14 """ |
12 def __init__(self, file_system, memcache): | 15 def __init__(self, file_system, memcache): |
13 self._file_system = file_system | 16 self._file_system = file_system |
14 self._memcache = memcache | 17 self._memcache = memcache |
15 | 18 |
16 def Stat(self, path): | 19 def Stat(self, path): |
| 20 start_time = time.time() |
| 21 try: |
| 22 return self._DoStat(path) |
| 23 finally: |
| 24 logging.info("(cached) Stat of %s took %sms", |
| 25 path, |
| 26 (time.time() - start_time) * 1000) |
| 27 |
| 28 def _DoStat(self, path): |
17 """Stats the directory given, or if a file is given, stats the files parent | 29 """Stats the directory given, or if a file is given, stats the files parent |
18 directory to get info about the file. | 30 directory to get info about the file. |
19 """ | 31 """ |
| 32 # TODO(kalman): store the whole stat info, not just the version. |
20 version = self._memcache.Get(path, memcache.MEMCACHE_FILE_SYSTEM_STAT) | 33 version = self._memcache.Get(path, memcache.MEMCACHE_FILE_SYSTEM_STAT) |
21 if version is None: | 34 if version is not None: |
22 stat_info = self._file_system.Stat(path) | 35 return StatInfo(version) |
23 self._memcache.Set(path, | 36 |
24 stat_info.version, | 37 # Always stat the parent directory, since it will have the stat of the child |
| 38 # anyway, and this gives us an entire directory's stat info at once. |
| 39 if path.endswith('/'): |
| 40 dir_path = path |
| 41 else: |
| 42 dir_path = path.rsplit('/', 1)[0] + '/' |
| 43 |
| 44 # TODO: batch set? |
| 45 dir_stat = self._file_system.Stat(dir_path) |
| 46 if path == dir_path: |
| 47 version = dir_stat.version |
| 48 |
| 49 self._memcache.Set(path, |
| 50 dir_stat.version, |
| 51 memcache.MEMCACHE_FILE_SYSTEM_STAT) |
| 52 |
| 53 for child_path, child_version in dir_stat.child_versions.iteritems(): |
| 54 child_path = dir_path + child_path |
| 55 if path == child_path: |
| 56 version = child_version |
| 57 self._memcache.Set(child_path, |
| 58 child_version, |
25 memcache.MEMCACHE_FILE_SYSTEM_STAT) | 59 memcache.MEMCACHE_FILE_SYSTEM_STAT) |
26 if stat_info.child_versions is not None: | 60 return StatInfo(version) |
27 for child_path, child_version in stat_info.child_versions.iteritems(): | |
28 self._memcache.Set(path.rsplit('/', 1)[0] + '/' + child_path, | |
29 child_version, | |
30 memcache.MEMCACHE_FILE_SYSTEM_STAT) | |
31 else: | |
32 stat_info = self.StatInfo(version) | |
33 return stat_info | |
34 | 61 |
35 def Read(self, paths, binary=False): | 62 def Read(self, paths, binary=False): |
| 63 start_time = time.time() |
| 64 try: |
| 65 return self._DoRead(paths, binary=binary) |
| 66 finally: |
| 67 logging.info("(cached) Read of %s took %sms", |
| 68 paths, |
| 69 (time.time() - start_time) * 1000) |
| 70 |
| 71 def _DoRead(self, paths, binary=False): |
36 """Reads a list of files. If a file is in memcache and it is not out of | 72 """Reads a list of files. If a file is in memcache and it is not out of |
37 date, it is returned. Otherwise, the file is retrieved from the file system. | 73 date, it is returned. Otherwise, the file is retrieved from the file system. |
38 """ | 74 """ |
39 result = {} | 75 result = {} |
40 uncached = [] | 76 uncached = [] |
41 for path in paths: | 77 for path in paths: |
42 cached_result = self._memcache.Get(path, | 78 cached_result = self._memcache.Get(path, |
43 memcache.MEMCACHE_FILE_SYSTEM_READ) | 79 memcache.MEMCACHE_FILE_SYSTEM_READ) |
44 if cached_result is None: | 80 if cached_result is None: |
45 uncached.append(path) | 81 uncached.append(path) |
46 continue | 82 continue |
47 data, version = cached_result | 83 data, version = cached_result |
48 if self.Stat(path).version != version: | 84 if self.Stat(path).version != version: |
49 self._memcache.Delete(path, memcache.MEMCACHE_FILE_SYSTEM_READ) | 85 self._memcache.Delete(path, memcache.MEMCACHE_FILE_SYSTEM_READ) |
50 uncached.append(path) | 86 uncached.append(path) |
51 continue | 87 continue |
52 result[path] = data | 88 result[path] = data |
53 if uncached: | 89 |
54 # TODO(cduvall): if there are uncached items we should return an | 90 if not uncached: |
55 # asynchronous future. http://crbug.com/142013 | 91 return Future(value=result) |
56 new_items = self._file_system.Read(uncached, binary=binary).Get() | 92 |
57 for item in new_items: | 93 # TODO(cduvall): if there are uncached items we should return an |
58 version = self.Stat(item).version | 94 # asynchronous future. http://crbug.com/142013 |
59 value = new_items[item] | 95 new_items = self._file_system.Read(uncached, binary=binary).Get() |
60 # Cache this file forever. | 96 for item in new_items: |
61 self._memcache.Set(item, | 97 version = self.Stat(item).version |
62 (value, version), | 98 value = new_items[item] |
63 memcache.MEMCACHE_FILE_SYSTEM_READ, | 99 self._memcache.Set(item, |
64 time=0) | 100 (value, version), |
65 result[item] = value | 101 memcache.MEMCACHE_FILE_SYSTEM_READ, |
| 102 # Forever. |
| 103 time=0) |
| 104 result[item] = value |
66 return Future(value=result) | 105 return Future(value=result) |
OLD | NEW |