• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
16 namespace {
17 
InvokeFileLoadCallback(const InputFileManager::FileLoadCallback & cb,const ParseNode * node)18 void InvokeFileLoadCallback(const InputFileManager::FileLoadCallback& cb,
19                             const ParseNode* node) {
20   cb.Run(node);
21 }
22 
23 }  // namespace
24 
InputFileData(const SourceFile & file_name)25 InputFileManager::InputFileData::InputFileData(const SourceFile& file_name)
26     : file(file_name),
27       loaded(false),
28       sync_invocation(false) {
29 }
30 
~InputFileData()31 InputFileManager::InputFileData::~InputFileData() {
32 }
33 
InputFileManager()34 InputFileManager::InputFileManager() {
35 }
36 
~InputFileManager()37 InputFileManager::~InputFileManager() {
38   // Should be single-threaded by now.
39   STLDeleteContainerPairSecondPointers(input_files_.begin(),
40                                        input_files_.end());
41 }
42 
AsyncLoadFile(const LocationRange & origin,const BuildSettings * build_settings,const SourceFile & file_name,const FileLoadCallback & callback,Err * err)43 bool 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 
SyncLoadFile(const LocationRange & origin,const BuildSettings * build_settings,const SourceFile & file_name,Err * err)100 const 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       // If there were multiple waiters on the same event, we now need to wake
156       // up the next one.
157       data->completion_event->Signal();
158     }
159   }
160 
161   // The other load could have failed. In this case that error will be printed
162   // to the console, but we need to return something here, so make up a
163   // dummy error.
164   if (!data->parsed_root)
165     *err = Err(origin, "File parse failed");
166   return data->parsed_root.get();
167 }
168 
GetInputFileCount() const169 int InputFileManager::GetInputFileCount() const {
170   base::AutoLock lock(lock_);
171   return static_cast<int>(input_files_.size());
172 }
173 
GetAllPhysicalInputFileNames(std::vector<base::FilePath> * result) const174 void InputFileManager::GetAllPhysicalInputFileNames(
175     std::vector<base::FilePath>* result) const {
176   base::AutoLock lock(lock_);
177   result->reserve(input_files_.size());
178   for (InputFileMap::const_iterator i = input_files_.begin();
179        i != input_files_.end(); ++i) {
180     if (!i->second->file.physical_name().empty())
181       result->push_back(i->second->file.physical_name());
182   }
183 }
184 
BackgroundLoadFile(const LocationRange & origin,const BuildSettings * build_settings,const SourceFile & name,InputFile * file)185 void InputFileManager::BackgroundLoadFile(const LocationRange& origin,
186                                           const BuildSettings* build_settings,
187                                           const SourceFile& name,
188                                           InputFile* file) {
189   Err err;
190   if (!LoadFile(origin, build_settings, name, file, &err))
191     g_scheduler->FailWithError(err);
192 }
193 
LoadFile(const LocationRange & origin,const BuildSettings * build_settings,const SourceFile & name,InputFile * file,Err * err)194 bool InputFileManager::LoadFile(const LocationRange& origin,
195                                 const BuildSettings* build_settings,
196                                 const SourceFile& name,
197                                 InputFile* file,
198                                 Err* err) {
199   // Do all of this stuff outside the lock. We should not give out file
200   // pointers until the read is complete.
201   if (g_scheduler->verbose_logging()) {
202     std::string logmsg = name.value();
203     if (origin.begin().file())
204       logmsg += " (referenced from " + origin.begin().Describe(false) + ")";
205     g_scheduler->Log("Loading", logmsg);
206   }
207 
208   // Read.
209   base::FilePath primary_path = build_settings->GetFullPath(name);
210   ScopedTrace load_trace(TraceItem::TRACE_FILE_LOAD, name.value());
211   if (!file->Load(primary_path)) {
212     if (!build_settings->secondary_source_path().empty()) {
213       // Fall back to secondary source tree.
214       base::FilePath secondary_path =
215           build_settings->GetFullPathSecondary(name);
216       if (!file->Load(secondary_path)) {
217         *err = Err(origin, "Can't load input file.",
218                    "Unable to load either \n" +
219                    FilePathToUTF8(primary_path) + " or \n" +
220                    FilePathToUTF8(secondary_path));
221         return false;
222       }
223     } else {
224       *err = Err(origin,
225                  "Unable to load \"" + FilePathToUTF8(primary_path) + "\".");
226       return false;
227     }
228   }
229   load_trace.Done();
230 
231   ScopedTrace exec_trace(TraceItem::TRACE_FILE_PARSE, name.value());
232 
233   // Tokenize.
234   std::vector<Token> tokens = Tokenizer::Tokenize(file, err);
235   if (err->has_error())
236     return false;
237 
238   // Parse.
239   scoped_ptr<ParseNode> root = Parser::Parse(tokens, err);
240   if (err->has_error())
241     return false;
242   ParseNode* unowned_root = root.get();
243 
244   exec_trace.Done();
245 
246   std::vector<FileLoadCallback> callbacks;
247   {
248     base::AutoLock lock(lock_);
249     DCHECK(input_files_.find(name) != input_files_.end());
250 
251     InputFileData* data = input_files_[name];
252     data->loaded = true;
253     data->tokens.swap(tokens);
254     data->parsed_root = root.Pass();
255 
256     // Unblock waiters on this event.
257     //
258     // It's somewhat bad to signal this inside the lock. When it's used, it's
259     // lazily created inside the lock. So we need to do the check and signal
260     // inside the lock to avoid race conditions on the lazy creation of the
261     // lock.
262     //
263     // We could avoid this by creating the lock every time, but the lock is
264     // very seldom used and will generally be NULL, so my current theory is that
265     // several signals of a completion event inside a lock is better than
266     // creating about 1000 extra locks (one for each file).
267     if (data->completion_event)
268       data->completion_event->Signal();
269 
270     callbacks.swap(data->scheduled_callbacks);
271   }
272 
273   // Run pending invocations. Theoretically we could schedule each of these
274   // separately to get some parallelism. But normally there will only be one
275   // item in the list, so that's extra overhead and complexity for no gain.
276   for (size_t i = 0; i < callbacks.size(); i++)
277     callbacks[i].Run(unowned_root);
278   return true;
279 }
280