input_file_manager.cc revision 68043e1e95eeb07d5cae7aca370b26518b0867d6
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 16namespace { 17 18void InvokeFileLoadCallback(const InputFileManager::FileLoadCallback& cb, 19 const ParseNode* node) { 20 cb.Run(node); 21} 22 23} // namespace 24 25InputFileManager::InputFileData::InputFileData(const SourceFile& file_name) 26 : file(file_name), 27 loaded(false), 28 sync_invocation(false) { 29} 30 31InputFileManager::InputFileData::~InputFileData() { 32} 33 34InputFileManager::InputFileManager() { 35} 36 37InputFileManager::~InputFileManager() { 38 // Should be single-threaded by now. 39 STLDeleteContainerPairSecondPointers(input_files_.begin(), 40 input_files_.end()); 41} 42 43bool InputFileManager::AsyncLoadFile(const LocationRange& origin, 44 const BuildSettings* build_settings, 45 const SourceFile& file_name, 46 const FileLoadCallback& callback, 47 Err* err) { 48 // Try not to schedule callbacks while holding the lock. All cases that don't 49 // want to schedule should return early. Otherwise, this will be scheduled 50 // after we leave the lock. 51 base::Closure schedule_this; 52 { 53 base::AutoLock lock(lock_); 54 55 InputFileMap::const_iterator found = input_files_.find(file_name); 56 if (found == input_files_.end()) { 57 // New file, schedule load. 58 InputFileData* data = new InputFileData(file_name); 59 data->scheduled_callbacks.push_back(callback); 60 input_files_[file_name] = data; 61 62 schedule_this = base::Bind(&InputFileManager::BackgroundLoadFile, 63 this, 64 origin, 65 build_settings, 66 file_name, 67 &data->file); 68 } else { 69 InputFileData* data = found->second; 70 71 // Prevent mixing async and sync loads. See SyncLoadFile for discussion. 72 if (data->sync_invocation) { 73 g_scheduler->FailWithError(Err( 74 origin, "Load type mismatch.", 75 "The file \"" + file_name.value() + "\" was previously loaded\n" 76 "synchronously (via an import) and now you're trying to load it " 77 "asynchronously\n(via a deps rule). This is a class 2 misdemeanor: " 78 "a single input file must\nbe loaded the same way each time to " 79 "avoid blowing my tiny, tiny mind.")); 80 return false; 81 } 82 83 if (data->loaded) { 84 // Can just directly issue the callback on the background thread. 85 schedule_this = base::Bind(&InvokeFileLoadCallback, callback, 86 data->parsed_root.get()); 87 } else { 88 // Load is pending on this file, schedule the invoke. 89 data->scheduled_callbacks.push_back(callback); 90 return true; 91 } 92 } 93 } 94 g_scheduler->pool()->PostWorkerTaskWithShutdownBehavior( 95 FROM_HERE, schedule_this, 96 base::SequencedWorkerPool::BLOCK_SHUTDOWN); 97 return true; 98} 99 100const ParseNode* InputFileManager::SyncLoadFile( 101 const LocationRange& origin, 102 const BuildSettings* build_settings, 103 const SourceFile& file_name, 104 Err* err) { 105 base::AutoLock lock(lock_); 106 107 InputFileData* data = NULL; 108 InputFileMap::iterator found = input_files_.find(file_name); 109 if (found == input_files_.end()) { 110 // Haven't seen this file yet, start loading right now. 111 data = new InputFileData(file_name); 112 data->sync_invocation = true; 113 input_files_[file_name] = data; 114 115 base::AutoUnlock unlock(lock_); 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 166int InputFileManager::GetInputFileCount() const { 167 base::AutoLock lock(lock_); 168 return input_files_.size(); 169} 170 171void 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 182void 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 191bool 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 std::string logmsg = name.value(); 200 if (origin.begin().file()) 201 logmsg += " (referenced from " + origin.begin().Describe(false) + ")"; 202 g_scheduler->Log("Loading", logmsg); 203 } 204 205 // Read. 206 base::FilePath primary_path = build_settings->GetFullPath(name); 207 ScopedTrace load_trace(TraceItem::TRACE_FILE_LOAD, name.value()); 208 if (!file->Load(primary_path)) { 209 if (!build_settings->secondary_source_path().empty()) { 210 // Fall back to secondary source tree. 211 base::FilePath secondary_path = 212 build_settings->GetFullPathSecondary(name); 213 if (!file->Load(secondary_path)) { 214 *err = Err(origin, "Can't load input file.", 215 "Unable to load either \n" + 216 FilePathToUTF8(primary_path) + " or \n" + 217 FilePathToUTF8(secondary_path)); 218 return false; 219 } 220 } else { 221 *err = Err(origin, 222 "Unable to load \"" + FilePathToUTF8(primary_path) + "\"."); 223 return false; 224 } 225 } 226 load_trace.Done(); 227 228 ScopedTrace exec_trace(TraceItem::TRACE_FILE_PARSE, name.value()); 229 230 // Tokenize. 231 std::vector<Token> tokens = Tokenizer::Tokenize(file, err); 232 if (err->has_error()) 233 return false; 234 235 // Parse. 236 scoped_ptr<ParseNode> root = Parser::Parse(tokens, err); 237 if (err->has_error()) 238 return false; 239 ParseNode* unowned_root = root.get(); 240 241 exec_trace.Done(); 242 243 std::vector<FileLoadCallback> callbacks; 244 { 245 base::AutoLock lock(lock_); 246 DCHECK(input_files_.find(name) != input_files_.end()); 247 248 InputFileData* data = input_files_[name]; 249 data->loaded = true; 250 data->tokens.swap(tokens); 251 data->parsed_root = root.Pass(); 252 253 // Unblock waiters on this event. 254 // 255 // It's somewhat bad to signal this inside the lock. When it's used, it's 256 // lazily created inside the lock. So we need to do the check and signal 257 // inside the lock to avoid race conditions on the lazy creation of the 258 // lock. 259 // 260 // We could avoid this by creating the lock every time, but the lock is 261 // very seldom used and will generally be NULL, so my current theory is that 262 // several signals of a completion event inside a lock is better than 263 // creating about 1000 extra locks (one for each file). 264 if (data->completion_event) 265 data->completion_event->Signal(); 266 267 callbacks.swap(data->scheduled_callbacks); 268 } 269 270 // Run pending invocations. Theoretically we could schedule each of these 271 // separately to get some parallelism. But normally there will only be one 272 // item in the list, so that's extra overhead and complexity for no gain. 273 for (size_t i = 0; i < callbacks.size(); i++) 274 callbacks[i].Run(unowned_root); 275 return true; 276} 277