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