Home | History | Annotate | Download | only in gn
      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