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