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