| 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 | 
|---|