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 #include "tools/gn/trace.h" 15 16 namespace { 17 18 void InvokeFileLoadCallback(const InputFileManager::FileLoadCallback& cb, 19 const ParseNode* node) { 20 cb.Run(node); 21 } 22 23 bool DoLoadFile(const LocationRange& origin, 24 const BuildSettings* build_settings, 25 const SourceFile& name, 26 InputFile* file, 27 std::vector<Token>* tokens, 28 scoped_ptr<ParseNode>* root, 29 Err* err) { 30 // Do all of this stuff outside the lock. We should not give out file 31 // pointers until the read is complete. 32 if (g_scheduler->verbose_logging()) { 33 std::string logmsg = name.value(); 34 if (origin.begin().file()) 35 logmsg += " (referenced from " + origin.begin().Describe(false) + ")"; 36 g_scheduler->Log("Loading", logmsg); 37 } 38 39 // Read. 40 base::FilePath primary_path = build_settings->GetFullPath(name); 41 ScopedTrace load_trace(TraceItem::TRACE_FILE_LOAD, name.value()); 42 if (!file->Load(primary_path)) { 43 if (!build_settings->secondary_source_path().empty()) { 44 // Fall back to secondary source tree. 45 base::FilePath secondary_path = 46 build_settings->GetFullPathSecondary(name); 47 if (!file->Load(secondary_path)) { 48 *err = Err(origin, "Can't load input file.", 49 "Unable to load:\n " + 50 FilePathToUTF8(primary_path) + "\n" 51 "I also checked in the secondary tree for:\n " + 52 FilePathToUTF8(secondary_path)); 53 return false; 54 } 55 } else { 56 *err = Err(origin, 57 "Unable to load \"" + FilePathToUTF8(primary_path) + "\"."); 58 return false; 59 } 60 } 61 load_trace.Done(); 62 63 ScopedTrace exec_trace(TraceItem::TRACE_FILE_PARSE, name.value()); 64 65 // Tokenize. 66 *tokens = Tokenizer::Tokenize(file, err); 67 if (err->has_error()) 68 return false; 69 70 // Parse. 71 *root = Parser::Parse(*tokens, err); 72 if (err->has_error()) 73 return false; 74 75 exec_trace.Done(); 76 return true; 77 } 78 79 } // namespace 80 81 InputFileManager::InputFileData::InputFileData(const SourceFile& file_name) 82 : file(file_name), 83 loaded(false), 84 sync_invocation(false) { 85 } 86 87 InputFileManager::InputFileData::~InputFileData() { 88 } 89 90 InputFileManager::InputFileManager() { 91 } 92 93 InputFileManager::~InputFileManager() { 94 // Should be single-threaded by now. 95 STLDeleteContainerPairSecondPointers(input_files_.begin(), 96 input_files_.end()); 97 STLDeleteContainerPointers(dynamic_inputs_.begin(), dynamic_inputs_.end()); 98 } 99 100 bool InputFileManager::AsyncLoadFile(const LocationRange& origin, 101 const BuildSettings* build_settings, 102 const SourceFile& file_name, 103 const FileLoadCallback& callback, 104 Err* err) { 105 // Try not to schedule callbacks while holding the lock. All cases that don't 106 // want to schedule should return early. Otherwise, this will be scheduled 107 // after we leave the lock. 108 base::Closure schedule_this; 109 { 110 base::AutoLock lock(lock_); 111 112 InputFileMap::const_iterator found = input_files_.find(file_name); 113 if (found == input_files_.end()) { 114 // New file, schedule load. 115 InputFileData* data = new InputFileData(file_name); 116 data->scheduled_callbacks.push_back(callback); 117 input_files_[file_name] = data; 118 119 schedule_this = base::Bind(&InputFileManager::BackgroundLoadFile, 120 this, 121 origin, 122 build_settings, 123 file_name, 124 &data->file); 125 } else { 126 InputFileData* data = found->second; 127 128 // Prevent mixing async and sync loads. See SyncLoadFile for discussion. 129 if (data->sync_invocation) { 130 g_scheduler->FailWithError(Err( 131 origin, "Load type mismatch.", 132 "The file \"" + file_name.value() + "\" was previously loaded\n" 133 "synchronously (via an import) and now you're trying to load it " 134 "asynchronously\n(via a deps rule). This is a class 2 misdemeanor: " 135 "a single input file must\nbe loaded the same way each time to " 136 "avoid blowing my tiny, tiny mind.")); 137 return false; 138 } 139 140 if (data->loaded) { 141 // Can just directly issue the callback on the background thread. 142 schedule_this = base::Bind(&InvokeFileLoadCallback, callback, 143 data->parsed_root.get()); 144 } else { 145 // Load is pending on this file, schedule the invoke. 146 data->scheduled_callbacks.push_back(callback); 147 return true; 148 } 149 } 150 } 151 g_scheduler->pool()->PostWorkerTaskWithShutdownBehavior( 152 FROM_HERE, schedule_this, 153 base::SequencedWorkerPool::BLOCK_SHUTDOWN); 154 return true; 155 } 156 157 const ParseNode* InputFileManager::SyncLoadFile( 158 const LocationRange& origin, 159 const BuildSettings* build_settings, 160 const SourceFile& file_name, 161 Err* err) { 162 base::AutoLock lock(lock_); 163 164 InputFileData* data = NULL; 165 InputFileMap::iterator found = input_files_.find(file_name); 166 if (found == input_files_.end()) { 167 // Haven't seen this file yet, start loading right now. 168 data = new InputFileData(file_name); 169 data->sync_invocation = true; 170 input_files_[file_name] = data; 171 172 base::AutoUnlock unlock(lock_); 173 if (!LoadFile(origin, build_settings, file_name, &data->file, err)) 174 return NULL; 175 } else { 176 // This file has either been loaded or is pending loading. 177 data = found->second; 178 179 if (!data->sync_invocation) { 180 // Don't allow mixing of sync and async loads. If an async load is 181 // scheduled and then a bunch of threads need to load it synchronously 182 // and block on it loading, it could deadlock or at least cause a lot 183 // of wasted CPU while those threads wait for the load to complete (which 184 // may be far back in the input queue). 185 // 186 // We could work around this by promoting the load to a sync load. This 187 // requires a bunch of extra code to either check flags and likely do 188 // extra locking (bad) or to just do both types of load on the file and 189 // deal with the race condition. 190 // 191 // I have no practical way to test this, and generally we should have 192 // all include files processed synchronously and all build files 193 // processed asynchronously, so it doesn't happen in practice. 194 *err = Err( 195 origin, "Load type mismatch.", 196 "The file \"" + file_name.value() + "\" was previously loaded\n" 197 "asynchronously (via a deps rule) and now you're trying to load it " 198 "synchronously.\nThis is a class 2 misdemeanor: a single input file " 199 "must be loaded the same way\neach time to avoid blowing my tiny, " 200 "tiny mind."); 201 return NULL; 202 } 203 204 if (!data->loaded) { 205 // Wait for the already-pending sync load to complete. 206 if (!data->completion_event) 207 data->completion_event.reset(new base::WaitableEvent(false, false)); 208 { 209 base::AutoUnlock unlock(lock_); 210 data->completion_event->Wait(); 211 } 212 // If there were multiple waiters on the same event, we now need to wake 213 // up the next one. 214 data->completion_event->Signal(); 215 } 216 } 217 218 // The other load could have failed. In this case that error will be printed 219 // to the console, but we need to return something here, so make up a 220 // dummy error. 221 if (!data->parsed_root) 222 *err = Err(origin, "File parse failed"); 223 return data->parsed_root.get(); 224 } 225 226 void InputFileManager::AddDynamicInput(const SourceFile& name, 227 InputFile** file, 228 std::vector<Token>** tokens, 229 scoped_ptr<ParseNode>** parse_root) { 230 InputFileData* data = new InputFileData(name); 231 { 232 base::AutoLock lock(lock_); 233 dynamic_inputs_.push_back(data); 234 } 235 *file = &data->file; 236 *tokens = &data->tokens; 237 *parse_root = &data->parsed_root; 238 } 239 240 int InputFileManager::GetInputFileCount() const { 241 base::AutoLock lock(lock_); 242 return static_cast<int>(input_files_.size()); 243 } 244 245 void InputFileManager::GetAllPhysicalInputFileNames( 246 std::vector<base::FilePath>* result) const { 247 base::AutoLock lock(lock_); 248 result->reserve(input_files_.size()); 249 for (InputFileMap::const_iterator i = input_files_.begin(); 250 i != input_files_.end(); ++i) { 251 if (!i->second->file.physical_name().empty()) 252 result->push_back(i->second->file.physical_name()); 253 } 254 } 255 256 void InputFileManager::BackgroundLoadFile(const LocationRange& origin, 257 const BuildSettings* build_settings, 258 const SourceFile& name, 259 InputFile* file) { 260 Err err; 261 if (!LoadFile(origin, build_settings, name, file, &err)) 262 g_scheduler->FailWithError(err); 263 } 264 265 bool InputFileManager::LoadFile(const LocationRange& origin, 266 const BuildSettings* build_settings, 267 const SourceFile& name, 268 InputFile* file, 269 Err* err) { 270 std::vector<Token> tokens; 271 scoped_ptr<ParseNode> root; 272 bool success = DoLoadFile(origin, build_settings, name, file, 273 &tokens, &root, err); 274 // Can't return early. We have to ensure that the completion event is 275 // signaled in all cases bacause another thread could be blocked on this one. 276 277 // Save this pointer for running the callbacks below, which happens after the 278 // scoped ptr ownership is taken away inside the lock. 279 ParseNode* unowned_root = root.get(); 280 281 std::vector<FileLoadCallback> callbacks; 282 { 283 base::AutoLock lock(lock_); 284 DCHECK(input_files_.find(name) != input_files_.end()); 285 286 InputFileData* data = input_files_[name]; 287 data->loaded = true; 288 if (success) { 289 data->tokens.swap(tokens); 290 data->parsed_root = root.Pass(); 291 } 292 293 // Unblock waiters on this event. 294 // 295 // It's somewhat bad to signal this inside the lock. When it's used, it's 296 // lazily created inside the lock. So we need to do the check and signal 297 // inside the lock to avoid race conditions on the lazy creation of the 298 // lock. 299 // 300 // We could avoid this by creating the lock every time, but the lock is 301 // very seldom used and will generally be NULL, so my current theory is that 302 // several signals of a completion event inside a lock is better than 303 // creating about 1000 extra locks (one for each file). 304 if (data->completion_event) 305 data->completion_event->Signal(); 306 307 callbacks.swap(data->scheduled_callbacks); 308 } 309 310 // Run pending invocations. Theoretically we could schedule each of these 311 // separately to get some parallelism. But normally there will only be one 312 // item in the list, so that's extra overhead and complexity for no gain. 313 if (success) { 314 for (size_t i = 0; i < callbacks.size(); i++) 315 callbacks[i].Run(unowned_root); 316 } 317 return success; 318 } 319