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