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