chained_compiled_file_system.py revision 4e180b6a0b4720a9b8e9e959a882386f690f08ff
1# Copyright 2013 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5from compiled_file_system import CompiledFileSystem 6from file_system import FileNotFoundError 7from future import Gettable, Future 8 9 10class ChainedCompiledFileSystem(object): 11 '''A CompiledFileSystem implementation that fetches data from a chain of 12 possible FileSystems. The chain consists of some number of FileSystems which 13 may have cached data for their CompiledFileSystem instances (injected on 14 Factory construction) + the main FileSystem (injected at Creation time). 15 16 The expected configuration is that the main FileSystem is a PatchedFileSystem 17 and the chain the FileSystem which it patches, but with file systems 18 constructed via the HostFileSystemIterator the main FileSystems could be 19 anything. 20 21 This slightly unusual configuration is primarily needed to avoid re-compiling 22 data for PatchedFileSystems, which are very similar to the FileSystem which 23 they patch. Re-compiling data is expensive and a waste of memory resources. 24 ChainedCompiledFileSystem shares the data. 25 ''' 26 class Factory(CompiledFileSystem.Factory): 27 def __init__(self, file_system_chain, object_store): 28 self._file_system_chain = file_system_chain 29 self._object_store = object_store 30 31 def Create(self, file_system, populate_function, cls, category=None): 32 return ChainedCompiledFileSystem(tuple( 33 CompiledFileSystem.Factory(self._object_store).Create( 34 fs, populate_function, cls, category=category) 35 for fs in [file_system] + self._file_system_chain)) 36 37 def __init__(self, compiled_fs_chain): 38 '''|compiled_fs_chain| is a list of tuples (compiled_fs, file_system). 39 ''' 40 assert len(compiled_fs_chain) > 0 41 self._compiled_fs_chain = compiled_fs_chain 42 43 def GetFromFile(self, path, binary=False): 44 return self._GetImpl( 45 path, 46 lambda compiled_fs: compiled_fs.GetFromFile(path, binary=binary), 47 lambda compiled_fs: compiled_fs.GetFileVersion(path)) 48 49 def GetFromFileListing(self, path): 50 if not path.endswith('/'): 51 path += '/' 52 return self._GetImpl( 53 path, 54 lambda compiled_fs: compiled_fs.GetFromFileListing(path), 55 lambda compiled_fs: compiled_fs.GetFileListingVersion(path)) 56 57 def _GetImpl(self, path, reader, version_getter): 58 # Strategy: Get the current version of |path| in main FileSystem, then run 59 # through |_compiled_fs_chain| in *reverse* to find the "oldest" FileSystem 60 # with an up-to-date version of that file. 61 # 62 # Obviously, if files have been added in the main FileSystem then none of 63 # the older FileSystems will be able to find it. 64 read_futures = [(reader(compiled_fs), compiled_fs) 65 for compiled_fs in self._compiled_fs_chain] 66 67 def resolve(): 68 try: 69 first_compiled_fs = self._compiled_fs_chain[0] 70 # The first file system contains both files of a newer version and 71 # files shared with other compiled file systems. We are going to try 72 # each compiled file system in the reverse order and return the data 73 # when version matches. Data cached in other compiled file system will 74 # be reused whenever possible so that we don't need to recompile things 75 # that are not changed across these file systems. 76 first_version = version_getter(first_compiled_fs) 77 for read_future, compiled_fs in reversed(read_futures): 78 if version_getter(compiled_fs) == first_version: 79 return read_future.Get() 80 except FileNotFoundError: 81 pass 82 # Try an arbitrary operation again to generate a realistic stack trace. 83 return read_futures[0][0].Get() 84 85 return Future(delegate=Gettable(resolve)) 86