OLD | NEW |
(Empty) | |
| 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::GetAllInputFileNames( |
| 172 std::vector<SourceFile>* 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 result->push_back(i->second->file.name()); |
| 178 } |
| 179 } |
| 180 |
| 181 void InputFileManager::BackgroundLoadFile(const LocationRange& origin, |
| 182 const BuildSettings* build_settings, |
| 183 const SourceFile& name, |
| 184 InputFile* file) { |
| 185 Err err; |
| 186 if (!LoadFile(origin, build_settings, name, file, &err)) |
| 187 g_scheduler->FailWithError(err); |
| 188 } |
| 189 |
| 190 bool InputFileManager::LoadFile(const LocationRange& origin, |
| 191 const BuildSettings* build_settings, |
| 192 const SourceFile& name, |
| 193 InputFile* file, |
| 194 Err* err) { |
| 195 // Do all of this stuff outside the lock. We should not give out file |
| 196 // pointers until the read is complete. |
| 197 if (g_scheduler->verbose_logging()) |
| 198 g_scheduler->Log("Loading", name.value()); |
| 199 |
| 200 // Read. |
| 201 base::FilePath primary_path = build_settings->GetFullPath(name); |
| 202 if (!file->Load(primary_path)) { |
| 203 if (!build_settings->secondary_source_path().empty()) { |
| 204 // Fall back to secondary source tree. |
| 205 base::FilePath secondary_path = |
| 206 build_settings->GetFullPathSecondary(name); |
| 207 if (!file->Load(secondary_path)) { |
| 208 *err = Err(origin, "Can't load input file.", |
| 209 "Unable to load either \n" + |
| 210 FilePathToUTF8(primary_path) + " or \n" + |
| 211 FilePathToUTF8(secondary_path)); |
| 212 return false; |
| 213 } |
| 214 } else { |
| 215 *err = Err(origin, |
| 216 "Unable to load \"" + FilePathToUTF8(primary_path) + "\"."); |
| 217 return false; |
| 218 } |
| 219 } |
| 220 |
| 221 if (g_scheduler->verbose_logging()) |
| 222 g_scheduler->Log("Parsing", name.value()); |
| 223 |
| 224 // Tokenize. |
| 225 std::vector<Token> tokens = Tokenizer::Tokenize(file, err); |
| 226 if (err->has_error()) |
| 227 return false; |
| 228 |
| 229 // Parse. |
| 230 scoped_ptr<ParseNode> root = Parser::Parse(tokens, err); |
| 231 if (err->has_error()) |
| 232 return false; |
| 233 ParseNode* unowned_root = root.get(); |
| 234 |
| 235 std::vector<FileLoadCallback> callbacks; |
| 236 { |
| 237 base::AutoLock lock(lock_); |
| 238 DCHECK(input_files_.find(name) != input_files_.end()); |
| 239 |
| 240 InputFileData* data = input_files_[name]; |
| 241 data->loaded = true; |
| 242 data->tokens.swap(tokens); |
| 243 data->parsed_root = root.Pass(); |
| 244 |
| 245 callbacks.swap(data->scheduled_callbacks); |
| 246 } |
| 247 |
| 248 // Run pending invocations. Theoretically we could schedule each of these |
| 249 // separately to get some parallelism. But normally there will only be one |
| 250 // item in the list, so that's extra overhead and complexity for no gain. |
| 251 for (size_t i = 0; i < callbacks.size(); i++) |
| 252 callbacks[i].Run(unowned_root); |
| 253 return true; |
| 254 } |
OLD | NEW |