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