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
15namespace {
16
17void InvokeFileLoadCallback(const InputFileManager::FileLoadCallback& cb,
18                            const ParseNode* node) {
19  cb.Run(node);
20}
21
22}  // namespace
23
24InputFileManager::InputFileData::InputFileData(const SourceFile& file_name)
25    : file(file_name),
26      loaded(false),
27      sync_invocation(false) {
28}
29
30InputFileManager::InputFileData::~InputFileData() {
31}
32
33InputFileManager::InputFileManager() {
34}
35
36InputFileManager::~InputFileManager() {
37  // Should be single-threaded by now.
38  STLDeleteContainerPairSecondPointers(input_files_.begin(),
39                                       input_files_.end());
40}
41
42bool 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
99const 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
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    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