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.
5 #include "tools/gn/input_file_manager.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"
18 void InvokeFileLoadCallback(const InputFileManager::FileLoadCallback
& cb
,
19 const ParseNode
* node
) {
23 bool DoLoadFile(const LocationRange
& origin
,
24 const BuildSettings
* build_settings
,
25 const SourceFile
& name
,
27 std::vector
<Token
>* tokens
,
28 scoped_ptr
<ParseNode
>* root
,
30 // Do all of this stuff outside the lock. We should not give out file
31 // pointers until the read is complete.
32 if (g_scheduler
->verbose_logging()) {
33 std::string logmsg
= name
.value();
34 if (origin
.begin().file())
35 logmsg
+= " (referenced from " + origin
.begin().Describe(false) + ")";
36 g_scheduler
->Log("Loading", logmsg
);
40 base::FilePath primary_path
= build_settings
->GetFullPath(name
);
41 ScopedTrace
load_trace(TraceItem::TRACE_FILE_LOAD
, name
.value());
42 if (!file
->Load(primary_path
)) {
43 if (!build_settings
->secondary_source_path().empty()) {
44 // Fall back to secondary source tree.
45 base::FilePath secondary_path
=
46 build_settings
->GetFullPathSecondary(name
);
47 if (!file
->Load(secondary_path
)) {
48 *err
= Err(origin
, "Can't load input file.",
49 "Unable to load:\n " +
50 FilePathToUTF8(primary_path
) + "\n"
51 "I also checked in the secondary tree for:\n " +
52 FilePathToUTF8(secondary_path
));
57 "Unable to load \"" + FilePathToUTF8(primary_path
) + "\".");
63 ScopedTrace
exec_trace(TraceItem::TRACE_FILE_PARSE
, name
.value());
66 *tokens
= Tokenizer::Tokenize(file
, err
);
71 *root
= Parser::Parse(*tokens
, err
);
81 InputFileManager::InputFileData::InputFileData(const SourceFile
& file_name
)
84 sync_invocation(false) {
87 InputFileManager::InputFileData::~InputFileData() {
90 InputFileManager::InputFileManager() {
93 InputFileManager::~InputFileManager() {
94 // Should be single-threaded by now.
95 STLDeleteContainerPairSecondPointers(input_files_
.begin(),
97 STLDeleteContainerPointers(dynamic_inputs_
.begin(), dynamic_inputs_
.end());
100 bool InputFileManager::AsyncLoadFile(const LocationRange
& origin
,
101 const BuildSettings
* build_settings
,
102 const SourceFile
& file_name
,
103 const FileLoadCallback
& callback
,
105 // Try not to schedule callbacks while holding the lock. All cases that don't
106 // want to schedule should return early. Otherwise, this will be scheduled
107 // after we leave the lock.
108 base::Closure schedule_this
;
110 base::AutoLock
lock(lock_
);
112 InputFileMap::const_iterator found
= input_files_
.find(file_name
);
113 if (found
== input_files_
.end()) {
114 // New file, schedule load.
115 InputFileData
* data
= new InputFileData(file_name
);
116 data
->scheduled_callbacks
.push_back(callback
);
117 input_files_
[file_name
] = data
;
119 schedule_this
= base::Bind(&InputFileManager::BackgroundLoadFile
,
126 InputFileData
* data
= found
->second
;
128 // Prevent mixing async and sync loads. See SyncLoadFile for discussion.
129 if (data
->sync_invocation
) {
130 g_scheduler
->FailWithError(Err(
131 origin
, "Load type mismatch.",
132 "The file \"" + file_name
.value() + "\" was previously loaded\n"
133 "synchronously (via an import) and now you're trying to load it "
134 "asynchronously\n(via a deps rule). This is a class 2 misdemeanor: "
135 "a single input file must\nbe loaded the same way each time to "
136 "avoid blowing my tiny, tiny mind."));
141 // Can just directly issue the callback on the background thread.
142 schedule_this
= base::Bind(&InvokeFileLoadCallback
, callback
,
143 data
->parsed_root
.get());
145 // Load is pending on this file, schedule the invoke.
146 data
->scheduled_callbacks
.push_back(callback
);
151 g_scheduler
->pool()->PostWorkerTaskWithShutdownBehavior(
152 FROM_HERE
, schedule_this
,
153 base::SequencedWorkerPool::BLOCK_SHUTDOWN
);
157 const ParseNode
* InputFileManager::SyncLoadFile(
158 const LocationRange
& origin
,
159 const BuildSettings
* build_settings
,
160 const SourceFile
& file_name
,
162 base::AutoLock
lock(lock_
);
164 InputFileData
* data
= nullptr;
165 InputFileMap::iterator found
= input_files_
.find(file_name
);
166 if (found
== input_files_
.end()) {
167 // Haven't seen this file yet, start loading right now.
168 data
= new InputFileData(file_name
);
169 data
->sync_invocation
= true;
170 input_files_
[file_name
] = data
;
172 base::AutoUnlock
unlock(lock_
);
173 if (!LoadFile(origin
, build_settings
, file_name
, &data
->file
, err
))
176 // This file has either been loaded or is pending loading.
177 data
= found
->second
;
179 if (!data
->sync_invocation
) {
180 // Don't allow mixing of sync and async loads. If an async load is
181 // scheduled and then a bunch of threads need to load it synchronously
182 // and block on it loading, it could deadlock or at least cause a lot
183 // of wasted CPU while those threads wait for the load to complete (which
184 // may be far back in the input queue).
186 // We could work around this by promoting the load to a sync load. This
187 // requires a bunch of extra code to either check flags and likely do
188 // extra locking (bad) or to just do both types of load on the file and
189 // deal with the race condition.
191 // I have no practical way to test this, and generally we should have
192 // all include files processed synchronously and all build files
193 // processed asynchronously, so it doesn't happen in practice.
195 origin
, "Load type mismatch.",
196 "The file \"" + file_name
.value() + "\" was previously loaded\n"
197 "asynchronously (via a deps rule) and now you're trying to load it "
198 "synchronously.\nThis is a class 2 misdemeanor: a single input file "
199 "must be loaded the same way\neach time to avoid blowing my tiny, "
205 // Wait for the already-pending sync load to complete.
206 if (!data
->completion_event
)
207 data
->completion_event
.reset(new base::WaitableEvent(false, false));
209 base::AutoUnlock
unlock(lock_
);
210 data
->completion_event
->Wait();
212 // If there were multiple waiters on the same event, we now need to wake
214 data
->completion_event
->Signal();
218 // The other load could have failed. It is possible that this thread's error
219 // will be reported to the scheduler before the other thread's (and the first
220 // error reported "wins"). Forward the parse error from the other load for
221 // this thread so that the error message is useful.
222 if (!data
->parsed_root
)
223 *err
= data
->parse_error
;
224 return data
->parsed_root
.get();
227 void InputFileManager::AddDynamicInput(const SourceFile
& name
,
229 std::vector
<Token
>** tokens
,
230 scoped_ptr
<ParseNode
>** parse_root
) {
231 InputFileData
* data
= new InputFileData(name
);
233 base::AutoLock
lock(lock_
);
234 dynamic_inputs_
.push_back(data
);
237 *tokens
= &data
->tokens
;
238 *parse_root
= &data
->parsed_root
;
241 int InputFileManager::GetInputFileCount() const {
242 base::AutoLock
lock(lock_
);
243 return static_cast<int>(input_files_
.size());
246 void InputFileManager::GetAllPhysicalInputFileNames(
247 std::vector
<base::FilePath
>* result
) const {
248 base::AutoLock
lock(lock_
);
249 result
->reserve(input_files_
.size());
250 for (const auto& file
: input_files_
) {
251 if (!file
.second
->file
.physical_name().empty())
252 result
->push_back(file
.second
->file
.physical_name());
256 void InputFileManager::BackgroundLoadFile(const LocationRange
& origin
,
257 const BuildSettings
* build_settings
,
258 const SourceFile
& name
,
261 if (!LoadFile(origin
, build_settings
, name
, file
, &err
))
262 g_scheduler
->FailWithError(err
);
265 bool InputFileManager::LoadFile(const LocationRange
& origin
,
266 const BuildSettings
* build_settings
,
267 const SourceFile
& name
,
270 std::vector
<Token
> tokens
;
271 scoped_ptr
<ParseNode
> root
;
272 bool success
= DoLoadFile(origin
, build_settings
, name
, file
,
273 &tokens
, &root
, err
);
274 // Can't return early. We have to ensure that the completion event is
275 // signaled in all cases bacause another thread could be blocked on this one.
277 // Save this pointer for running the callbacks below, which happens after the
278 // scoped ptr ownership is taken away inside the lock.
279 ParseNode
* unowned_root
= root
.get();
281 std::vector
<FileLoadCallback
> callbacks
;
283 base::AutoLock
lock(lock_
);
284 DCHECK(input_files_
.find(name
) != input_files_
.end());
286 InputFileData
* data
= input_files_
[name
];
289 data
->tokens
.swap(tokens
);
290 data
->parsed_root
= root
.Pass();
292 data
->parse_error
= *err
;
295 // Unblock waiters on this event.
297 // It's somewhat bad to signal this inside the lock. When it's used, it's
298 // lazily created inside the lock. So we need to do the check and signal
299 // inside the lock to avoid race conditions on the lazy creation of the
302 // We could avoid this by creating the lock every time, but the lock is
303 // very seldom used and will generally be NULL, so my current theory is that
304 // several signals of a completion event inside a lock is better than
305 // creating about 1000 extra locks (one for each file).
306 if (data
->completion_event
)
307 data
->completion_event
->Signal();
309 callbacks
.swap(data
->scheduled_callbacks
);
312 // Run pending invocations. Theoretically we could schedule each of these
313 // separately to get some parallelism. But normally there will only be one
314 // item in the list, so that's extra overhead and complexity for no gain.
316 for (const auto& cb
: callbacks
)
317 cb
.Run(unowned_root
);