1 // Copyright (c) 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 5 #include "tools/gn/input_file_manager.h" 6 7 #include "base/bind.h" 8 #include "base/stl_util.h" 9 #include "tools/gn/filesystem_utils.h" 10 #include "tools/gn/parser.h" 11 #include "tools/gn/scheduler.h" 12 #include "tools/gn/scope_per_file_provider.h" 13 #include "tools/gn/tokenizer.h" 14 15 namespace { 16 17 void InvokeFileLoadCallback(const InputFileManager::FileLoadCallback& cb, 18 const ParseNode* node) { 19 cb.Run(node); 20 } 21 22 } // namespace 23 24 InputFileManager::InputFileData::InputFileData(const SourceFile& file_name) 25 : file(file_name), 26 loaded(false), 27 sync_invocation(false) { 28 } 29 30 InputFileManager::InputFileData::~InputFileData() { 31 } 32 33 InputFileManager::InputFileManager() { 34 } 35 36 InputFileManager::~InputFileManager() { 37 // Should be single-threaded by now. 38 STLDeleteContainerPairSecondPointers(input_files_.begin(), 39 input_files_.end()); 40 } 41 42 bool InputFileManager::AsyncLoadFile(const LocationRange& origin, 43 const BuildSettings* build_settings, 44 const SourceFile& file_name, 45 const FileLoadCallback& callback, 46 Err* err) { 47 // Try not to schedule callbacks while holding the lock. All cases that don't 48 // want to schedule should return early. Otherwise, this will be scheduled 49 // after we leave the lock. 50 base::Closure schedule_this; 51 { 52 base::AutoLock lock(lock_); 53 54 InputFileMap::const_iterator found = input_files_.find(file_name); 55 if (found == input_files_.end()) { 56 // New file, schedule load. 57 InputFileData* data = new InputFileData(file_name); 58 data->scheduled_callbacks.push_back(callback); 59 input_files_[file_name] = data; 60 61 schedule_this = base::Bind(&InputFileManager::BackgroundLoadFile, 62 this, 63 origin, 64 build_settings, 65 file_name, 66 &data->file); 67 } else { 68 InputFileData* data = found->second; 69 70 // Prevent mixing async and sync loads. See SyncLoadFile for discussion. 71 if (data->sync_invocation) { 72 g_scheduler->FailWithError(Err( 73 origin, "Load type mismatch.", 74 "The file \"" + file_name.value() + "\" was previously loaded\n" 75 "synchronously (via an import) and now you're trying to load it " 76 "asynchronously\n(via a deps rule). This is a class 2 misdemeanor: " 77 "a single input file must\nbe loaded the same way each time to " 78 "avoid blowing my tiny, tiny mind.")); 79 return false; 80 } 81 82 if (data->loaded) { 83 // Can just directly issue the callback on the background thread. 84 schedule_this = base::Bind(&InvokeFileLoadCallback, callback, 85 data->parsed_root.get()); 86 } else { 87 // Load is pending on this file, schedule the invoke. 88 data->scheduled_callbacks.push_back(callback); 89 return true; 90 } 91 } 92 } 93 g_scheduler->pool()->PostWorkerTaskWithShutdownBehavior( 94 FROM_HERE, schedule_this, 95 base::SequencedWorkerPool::BLOCK_SHUTDOWN); 96 return true; 97 } 98 99 const ParseNode* InputFileManager::SyncLoadFile( 100 const LocationRange& origin, 101 const BuildSettings* build_settings, 102 const SourceFile& file_name, 103 Err* err) { 104 base::AutoLock lock(lock_); 105 106 InputFileData* data = NULL; 107 InputFileMap::iterator found = input_files_.find(file_name); 108 if (found == input_files_.end()) { 109 base::AutoUnlock unlock(lock_); 110 111 // Haven't seen this file yet, start loading right now. 112 data = new InputFileData(file_name); 113 data->sync_invocation = true; 114 input_files_[file_name] = data; 115 116 if (!LoadFile(origin, build_settings, file_name, &data->file, err)) 117 return NULL; 118 } else { 119 // This file has either been loaded or is pending loading. 120 data = found->second; 121 122 if (!data->sync_invocation) { 123 // Don't allow mixing of sync and async loads. If an async load is 124 // scheduled and then a bunch of threads need to load it synchronously 125 // and block on it loading, it could deadlock or at least cause a lot 126 // of wasted CPU while those threads wait for the load to complete (which 127 // may be far back in the input queue). 128 // 129 // We could work around this by promoting the load to a sync load. This 130 // requires a bunch of extra code to either check flags and likely do 131 // extra locking (bad) or to just do both types of load on the file and 132 // deal with the race condition. 133 // 134 // I have no practical way to test this, and generally we should have 135 // all include files processed synchronously and all build files 136 // processed asynchronously, so it doesn't happen in practice. 137 *err = Err( 138 origin, "Load type mismatch.", 139 "The file \"" + file_name.value() + "\" was previously loaded\n" 140 "asynchronously (via a deps rule) and now you're trying to load it " 141 "synchronously.\nThis is a class 2 misdemeanor: a single input file " 142 "must be loaded the same way\neach time to avoid blowing my tiny, " 143 "tiny mind."); 144 return NULL; 145 } 146 147 if (!data->loaded) { 148 // Wait for the already-pending sync load to complete. 149 if (!data->completion_event) 150 data->completion_event.reset(new base::WaitableEvent(false, false)); 151 { 152 base::AutoUnlock unlock(lock_); 153 data->completion_event->Wait(); 154 } 155 } 156 } 157 158 // The other load could have failed. In this case that error will be printed 159 // to the console, but we need to return something here, so make up a 160 // dummy error. 161 if (!data->parsed_root) 162 *err = Err(origin, "File parse failed"); 163 return data->parsed_root.get(); 164 } 165 166 int InputFileManager::GetInputFileCount() const { 167 base::AutoLock lock(lock_); 168 return input_files_.size(); 169 } 170 171 void InputFileManager::GetAllPhysicalInputFileNames( 172 std::vector<base::FilePath>* result) const { 173 base::AutoLock lock(lock_); 174 result->reserve(input_files_.size()); 175 for (InputFileMap::const_iterator i = input_files_.begin(); 176 i != input_files_.end(); ++i) { 177 if (!i->second->file.physical_name().empty()) 178 result->push_back(i->second->file.physical_name()); 179 } 180 } 181 182 void InputFileManager::BackgroundLoadFile(const LocationRange& origin, 183 const BuildSettings* build_settings, 184 const SourceFile& name, 185 InputFile* file) { 186 Err err; 187 if (!LoadFile(origin, build_settings, name, file, &err)) 188 g_scheduler->FailWithError(err); 189 } 190 191 bool InputFileManager::LoadFile(const LocationRange& origin, 192 const BuildSettings* build_settings, 193 const SourceFile& name, 194 InputFile* file, 195 Err* err) { 196 // Do all of this stuff outside the lock. We should not give out file 197 // pointers until the read is complete. 198 if (g_scheduler->verbose_logging()) 199 g_scheduler->Log("Loading", name.value()); 200 201 // Read. 202 base::FilePath primary_path = build_settings->GetFullPath(name); 203 if (!file->Load(primary_path)) { 204 if (!build_settings->secondary_source_path().empty()) { 205 // Fall back to secondary source tree. 206 base::FilePath secondary_path = 207 build_settings->GetFullPathSecondary(name); 208 if (!file->Load(secondary_path)) { 209 *err = Err(origin, "Can't load input file.", 210 "Unable to load either \n" + 211 FilePathToUTF8(primary_path) + " or \n" + 212 FilePathToUTF8(secondary_path)); 213 return false; 214 } 215 } else { 216 *err = Err(origin, 217 "Unable to load \"" + FilePathToUTF8(primary_path) + "\"."); 218 return false; 219 } 220 } 221 222 // Tokenize. 223 std::vector<Token> tokens = Tokenizer::Tokenize(file, err); 224 if (err->has_error()) 225 return false; 226 227 // Parse. 228 scoped_ptr<ParseNode> root = Parser::Parse(tokens, err); 229 if (err->has_error()) 230 return false; 231 ParseNode* unowned_root = root.get(); 232 233 std::vector<FileLoadCallback> callbacks; 234 { 235 base::AutoLock lock(lock_); 236 DCHECK(input_files_.find(name) != input_files_.end()); 237 238 InputFileData* data = input_files_[name]; 239 data->loaded = true; 240 data->tokens.swap(tokens); 241 data->parsed_root = root.Pass(); 242 243 callbacks.swap(data->scheduled_callbacks); 244 } 245 246 // Run pending invocations. Theoretically we could schedule each of these 247 // separately to get some parallelism. But normally there will only be one 248 // item in the list, so that's extra overhead and complexity for no gain. 249 for (size_t i = 0; i < callbacks.size(); i++) 250 callbacks[i].Run(unowned_root); 251 return true; 252 } 253