• 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 
DoLoadFile(const LocationRange & origin,const BuildSettings * build_settings,const SourceFile & name,InputFile * file,std::vector<Token> * tokens,scoped_ptr<ParseNode> * root,Err * err)23 bool DoLoadFile(const LocationRange& origin,
24                 const BuildSettings* build_settings,
25                 const SourceFile& name,
26                 InputFile* file,
27                 std::vector<Token>* tokens,
28                 scoped_ptr<ParseNode>* root,
29                 Err* err) {
30   // Do all of this stuff outside the lock. We should not give out file
31   // pointers until the read is complete.
32   if (g_scheduler->verbose_logging()) {
33     std::string logmsg = name.value();
34     if (origin.begin().file())
35       logmsg += " (referenced from " + origin.begin().Describe(false) + ")";
36     g_scheduler->Log("Loading", logmsg);
37   }
38 
39   // Read.
40   base::FilePath primary_path = build_settings->GetFullPath(name);
41   ScopedTrace load_trace(TraceItem::TRACE_FILE_LOAD, name.value());
42   if (!file->Load(primary_path)) {
43     if (!build_settings->secondary_source_path().empty()) {
44       // Fall back to secondary source tree.
45       base::FilePath secondary_path =
46           build_settings->GetFullPathSecondary(name);
47       if (!file->Load(secondary_path)) {
48         *err = Err(origin, "Can't load input file.",
49                    "Unable to load:\n  " +
50                    FilePathToUTF8(primary_path) + "\n"
51                    "I also checked in the secondary tree for:\n  " +
52                    FilePathToUTF8(secondary_path));
53         return false;
54       }
55     } else {
56       *err = Err(origin,
57                  "Unable to load \"" + FilePathToUTF8(primary_path) + "\".");
58       return false;
59     }
60   }
61   load_trace.Done();
62 
63   ScopedTrace exec_trace(TraceItem::TRACE_FILE_PARSE, name.value());
64 
65   // Tokenize.
66   *tokens = Tokenizer::Tokenize(file, err);
67   if (err->has_error())
68     return false;
69 
70   // Parse.
71   *root = Parser::Parse(*tokens, err);
72   if (err->has_error())
73     return false;
74 
75   exec_trace.Done();
76   return true;
77 }
78 
79 }  // namespace
80 
InputFileData(const SourceFile & file_name)81 InputFileManager::InputFileData::InputFileData(const SourceFile& file_name)
82     : file(file_name),
83       loaded(false),
84       sync_invocation(false) {
85 }
86 
~InputFileData()87 InputFileManager::InputFileData::~InputFileData() {
88 }
89 
InputFileManager()90 InputFileManager::InputFileManager() {
91 }
92 
~InputFileManager()93 InputFileManager::~InputFileManager() {
94   // Should be single-threaded by now.
95   STLDeleteContainerPairSecondPointers(input_files_.begin(),
96                                        input_files_.end());
97   STLDeleteContainerPointers(dynamic_inputs_.begin(), dynamic_inputs_.end());
98 }
99 
AsyncLoadFile(const LocationRange & origin,const BuildSettings * build_settings,const SourceFile & file_name,const FileLoadCallback & callback,Err * err)100 bool InputFileManager::AsyncLoadFile(const LocationRange& origin,
101                                      const BuildSettings* build_settings,
102                                      const SourceFile& file_name,
103                                      const FileLoadCallback& callback,
104                                      Err* err) {
105   // Try not to schedule callbacks while holding the lock. All cases that don't
106   // want to schedule should return early. Otherwise, this will be scheduled
107   // after we leave the lock.
108   base::Closure schedule_this;
109   {
110     base::AutoLock lock(lock_);
111 
112     InputFileMap::const_iterator found = input_files_.find(file_name);
113     if (found == input_files_.end()) {
114       // New file, schedule load.
115       InputFileData* data = new InputFileData(file_name);
116       data->scheduled_callbacks.push_back(callback);
117       input_files_[file_name] = data;
118 
119       schedule_this = base::Bind(&InputFileManager::BackgroundLoadFile,
120                                  this,
121                                  origin,
122                                  build_settings,
123                                  file_name,
124                                  &data->file);
125     } else {
126       InputFileData* data = found->second;
127 
128       // Prevent mixing async and sync loads. See SyncLoadFile for discussion.
129       if (data->sync_invocation) {
130         g_scheduler->FailWithError(Err(
131             origin, "Load type mismatch.",
132             "The file \"" + file_name.value() + "\" was previously loaded\n"
133             "synchronously (via an import) and now you're trying to load it "
134             "asynchronously\n(via a deps rule). This is a class 2 misdemeanor: "
135             "a single input file must\nbe loaded the same way each time to "
136             "avoid blowing my tiny, tiny mind."));
137         return false;
138       }
139 
140       if (data->loaded) {
141         // Can just directly issue the callback on the background thread.
142         schedule_this = base::Bind(&InvokeFileLoadCallback, callback,
143                                    data->parsed_root.get());
144       } else {
145         // Load is pending on this file, schedule the invoke.
146         data->scheduled_callbacks.push_back(callback);
147         return true;
148       }
149     }
150   }
151   g_scheduler->pool()->PostWorkerTaskWithShutdownBehavior(
152       FROM_HERE, schedule_this,
153       base::SequencedWorkerPool::BLOCK_SHUTDOWN);
154   return true;
155 }
156 
SyncLoadFile(const LocationRange & origin,const BuildSettings * build_settings,const SourceFile & file_name,Err * err)157 const ParseNode* InputFileManager::SyncLoadFile(
158     const LocationRange& origin,
159     const BuildSettings* build_settings,
160     const SourceFile& file_name,
161     Err* err) {
162   base::AutoLock lock(lock_);
163 
164   InputFileData* data = NULL;
165   InputFileMap::iterator found = input_files_.find(file_name);
166   if (found == input_files_.end()) {
167     // Haven't seen this file yet, start loading right now.
168     data = new InputFileData(file_name);
169     data->sync_invocation = true;
170     input_files_[file_name] = data;
171 
172     base::AutoUnlock unlock(lock_);
173     if (!LoadFile(origin, build_settings, file_name, &data->file, err))
174       return NULL;
175   } else {
176     // This file has either been loaded or is pending loading.
177     data = found->second;
178 
179     if (!data->sync_invocation) {
180       // Don't allow mixing of sync and async loads. If an async load is
181       // scheduled and then a bunch of threads need to load it synchronously
182       // and block on it loading, it could deadlock or at least cause a lot
183       // of wasted CPU while those threads wait for the load to complete (which
184       // may be far back in the input queue).
185       //
186       // We could work around this by promoting the load to a sync load. This
187       // requires a bunch of extra code to either check flags and likely do
188       // extra locking (bad) or to just do both types of load on the file and
189       // deal with the race condition.
190       //
191       // I have no practical way to test this, and generally we should have
192       // all include files processed synchronously and all build files
193       // processed asynchronously, so it doesn't happen in practice.
194       *err = Err(
195           origin, "Load type mismatch.",
196           "The file \"" + file_name.value() + "\" was previously loaded\n"
197           "asynchronously (via a deps rule) and now you're trying to load it "
198           "synchronously.\nThis is a class 2 misdemeanor: a single input file "
199           "must be loaded the same way\neach time to avoid blowing my tiny, "
200           "tiny mind.");
201       return NULL;
202     }
203 
204     if (!data->loaded) {
205       // Wait for the already-pending sync load to complete.
206       if (!data->completion_event)
207         data->completion_event.reset(new base::WaitableEvent(false, false));
208       {
209         base::AutoUnlock unlock(lock_);
210         data->completion_event->Wait();
211       }
212       // If there were multiple waiters on the same event, we now need to wake
213       // up the next one.
214       data->completion_event->Signal();
215     }
216   }
217 
218   // The other load could have failed. In this case that error will be printed
219   // to the console, but we need to return something here, so make up a
220   // dummy error.
221   if (!data->parsed_root)
222     *err = Err(origin, "File parse failed");
223   return data->parsed_root.get();
224 }
225 
AddDynamicInput(const SourceFile & name,InputFile ** file,std::vector<Token> ** tokens,scoped_ptr<ParseNode> ** parse_root)226 void InputFileManager::AddDynamicInput(const SourceFile& name,
227                                        InputFile** file,
228                                        std::vector<Token>** tokens,
229                                        scoped_ptr<ParseNode>** parse_root) {
230   InputFileData* data = new InputFileData(name);
231   {
232     base::AutoLock lock(lock_);
233     dynamic_inputs_.push_back(data);
234   }
235   *file = &data->file;
236   *tokens = &data->tokens;
237   *parse_root = &data->parsed_root;
238 }
239 
GetInputFileCount() const240 int InputFileManager::GetInputFileCount() const {
241   base::AutoLock lock(lock_);
242   return static_cast<int>(input_files_.size());
243 }
244 
GetAllPhysicalInputFileNames(std::vector<base::FilePath> * result) const245 void InputFileManager::GetAllPhysicalInputFileNames(
246     std::vector<base::FilePath>* result) const {
247   base::AutoLock lock(lock_);
248   result->reserve(input_files_.size());
249   for (InputFileMap::const_iterator i = input_files_.begin();
250        i != input_files_.end(); ++i) {
251     if (!i->second->file.physical_name().empty())
252       result->push_back(i->second->file.physical_name());
253   }
254 }
255 
BackgroundLoadFile(const LocationRange & origin,const BuildSettings * build_settings,const SourceFile & name,InputFile * file)256 void InputFileManager::BackgroundLoadFile(const LocationRange& origin,
257                                           const BuildSettings* build_settings,
258                                           const SourceFile& name,
259                                           InputFile* file) {
260   Err err;
261   if (!LoadFile(origin, build_settings, name, file, &err))
262     g_scheduler->FailWithError(err);
263 }
264 
LoadFile(const LocationRange & origin,const BuildSettings * build_settings,const SourceFile & name,InputFile * file,Err * err)265 bool InputFileManager::LoadFile(const LocationRange& origin,
266                                 const BuildSettings* build_settings,
267                                 const SourceFile& name,
268                                 InputFile* file,
269                                 Err* err) {
270   std::vector<Token> tokens;
271   scoped_ptr<ParseNode> root;
272   bool success = DoLoadFile(origin, build_settings, name, file,
273                             &tokens, &root, err);
274   // Can't return early. We have to ensure that the completion event is
275   // signaled in all cases bacause another thread could be blocked on this one.
276 
277   // Save this pointer for running the callbacks below, which happens after the
278   // scoped ptr ownership is taken away inside the lock.
279   ParseNode* unowned_root = root.get();
280 
281   std::vector<FileLoadCallback> callbacks;
282   {
283     base::AutoLock lock(lock_);
284     DCHECK(input_files_.find(name) != input_files_.end());
285 
286     InputFileData* data = input_files_[name];
287     data->loaded = true;
288     if (success) {
289       data->tokens.swap(tokens);
290       data->parsed_root = root.Pass();
291     }
292 
293     // Unblock waiters on this event.
294     //
295     // It's somewhat bad to signal this inside the lock. When it's used, it's
296     // lazily created inside the lock. So we need to do the check and signal
297     // inside the lock to avoid race conditions on the lazy creation of the
298     // lock.
299     //
300     // We could avoid this by creating the lock every time, but the lock is
301     // very seldom used and will generally be NULL, so my current theory is that
302     // several signals of a completion event inside a lock is better than
303     // creating about 1000 extra locks (one for each file).
304     if (data->completion_event)
305       data->completion_event->Signal();
306 
307     callbacks.swap(data->scheduled_callbacks);
308   }
309 
310   // Run pending invocations. Theoretically we could schedule each of these
311   // separately to get some parallelism. But normally there will only be one
312   // item in the list, so that's extra overhead and complexity for no gain.
313   if (success) {
314     for (size_t i = 0; i < callbacks.size(); i++)
315       callbacks[i].Run(unowned_root);
316   }
317   return success;
318 }
319