OLD | NEW |
| (Empty) |
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 | |
15 namespace { | |
16 | |
17 void InvokeFileLoadCallback(const InputFileManager::FileLoadCallback& cb, | |
18 const ParseNode* node) { | |
19 cb.Run(node); | |
20 } | |
21 | |
22 } // namespace | |
23 | |
24 InputFileManager::InputFileData::InputFileData(const SourceFile& file_name) | |
25 : file(file_name), | |
26 loaded(false), | |
27 sync_invocation(false) { | |
28 } | |
29 | |
30 InputFileManager::InputFileData::~InputFileData() { | |
31 } | |
32 | |
33 InputFileManager::InputFileManager() { | |
34 } | |
35 | |
36 InputFileManager::~InputFileManager() { | |
37 // Should be single-threaded by now. | |
38 STLDeleteContainerPairSecondPointers(input_files_.begin(), | |
39 input_files_.end()); | |
40 } | |
41 | |
42 bool InputFileManager::AsyncLoadFile(const LocationRange& origin, | |
43 const BuildSettings* build_settings, | |
44 const SourceFile& file_name, | |
45 const FileLoadCallback& callback, | |
46 Err* err) { | |
47 // Try not to schedule callbacks while holding the lock. All cases that don't | |
48 // want to schedule should return early. Otherwise, this will be scheduled | |
49 // after we leave the lock. | |
50 base::Closure schedule_this; | |
51 { | |
52 base::AutoLock lock(lock_); | |
53 | |
54 InputFileMap::const_iterator found = input_files_.find(file_name); | |
55 if (found == input_files_.end()) { | |
56 // New file, schedule load. | |
57 InputFileData* data = new InputFileData(file_name); | |
58 data->scheduled_callbacks.push_back(callback); | |
59 input_files_[file_name] = data; | |
60 | |
61 schedule_this = base::Bind(&InputFileManager::BackgroundLoadFile, | |
62 this, | |
63 origin, | |
64 build_settings, | |
65 file_name, | |
66 &data->file); | |
67 } else { | |
68 InputFileData* data = found->second; | |
69 | |
70 // Prevent mixing async and sync loads. See SyncLoadFile for discussion. | |
71 if (data->sync_invocation) { | |
72 g_scheduler->FailWithError(Err( | |
73 origin, "Load type mismatch.", | |
74 "The file \"" + file_name.value() + "\" was previously loaded\n" | |
75 "synchronously (via an import) and now you're trying to load it " | |
76 "asynchronously\n(via a deps rule). This is a class 2 misdemeanor: " | |
77 "a single input file must\nbe loaded the same way each time to " | |
78 "avoid blowing my tiny, tiny mind.")); | |
79 return false; | |
80 } | |
81 | |
82 if (data->loaded) { | |
83 // Can just directly issue the callback on the background thread. | |
84 schedule_this = base::Bind(&InvokeFileLoadCallback, callback, | |
85 data->parsed_root.get()); | |
86 } else { | |
87 // Load is pending on this file, schedule the invoke. | |
88 data->scheduled_callbacks.push_back(callback); | |
89 return true; | |
90 } | |
91 } | |
92 } | |
93 g_scheduler->pool()->PostWorkerTaskWithShutdownBehavior( | |
94 FROM_HERE, schedule_this, | |
95 base::SequencedWorkerPool::BLOCK_SHUTDOWN); | |
96 return true; | |
97 } | |
98 | |
99 const ParseNode* InputFileManager::SyncLoadFile( | |
100 const LocationRange& origin, | |
101 const BuildSettings* build_settings, | |
102 const SourceFile& file_name, | |
103 Err* err) { | |
104 base::AutoLock lock(lock_); | |
105 | |
106 InputFileData* data = NULL; | |
107 InputFileMap::iterator found = input_files_.find(file_name); | |
108 if (found == input_files_.end()) { | |
109 base::AutoUnlock unlock(lock_); | |
110 | |
111 // Haven't seen this file yet, start loading right now. | |
112 data = new InputFileData(file_name); | |
113 data->sync_invocation = true; | |
114 input_files_[file_name] = data; | |
115 | |
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 } | |
156 } | |
157 | |
158 // The other load could have failed. In this case that error will be printed | |
159 // to the console, but we need to return something here, so make up a | |
160 // dummy error. | |
161 if (!data->parsed_root) | |
162 *err = Err(origin, "File parse failed"); | |
163 return data->parsed_root.get(); | |
164 } | |
165 | |
166 int InputFileManager::GetInputFileCount() const { | |
167 base::AutoLock lock(lock_); | |
168 return input_files_.size(); | |
169 } | |
170 | |
171 void InputFileManager::GetAllInputFileNames( | |
172 std::vector<SourceFile>* result) const { | |
173 base::AutoLock lock(lock_); | |
174 result->reserve(input_files_.size()); | |
175 for (InputFileMap::const_iterator i = input_files_.begin(); | |
176 i != input_files_.end(); ++i) { | |
177 result->push_back(i->second->file.name()); | |
178 } | |
179 } | |
180 | |
181 void InputFileManager::BackgroundLoadFile(const LocationRange& origin, | |
182 const BuildSettings* build_settings, | |
183 const SourceFile& name, | |
184 InputFile* file) { | |
185 Err err; | |
186 if (!LoadFile(origin, build_settings, name, file, &err)) | |
187 g_scheduler->FailWithError(err); | |
188 } | |
189 | |
190 bool InputFileManager::LoadFile(const LocationRange& origin, | |
191 const BuildSettings* build_settings, | |
192 const SourceFile& name, | |
193 InputFile* file, | |
194 Err* err) { | |
195 // Do all of this stuff outside the lock. We should not give out file | |
196 // pointers until the read is complete. | |
197 if (g_scheduler->verbose_logging()) | |
198 g_scheduler->Log("Loading", name.value()); | |
199 | |
200 // Read. | |
201 base::FilePath primary_path = build_settings->GetFullPath(name); | |
202 if (!file->Load(primary_path)) { | |
203 if (!build_settings->secondary_source_path().empty()) { | |
204 // Fall back to secondary source tree. | |
205 base::FilePath secondary_path = | |
206 build_settings->GetFullPathSecondary(name); | |
207 if (!file->Load(secondary_path)) { | |
208 *err = Err(origin, "Can't load input file.", | |
209 "Unable to load either \n" + | |
210 FilePathToUTF8(primary_path) + " or \n" + | |
211 FilePathToUTF8(secondary_path)); | |
212 return false; | |
213 } | |
214 } else { | |
215 *err = Err(origin, | |
216 "Unable to load \"" + FilePathToUTF8(primary_path) + "\"."); | |
217 return false; | |
218 } | |
219 } | |
220 | |
221 if (g_scheduler->verbose_logging()) | |
222 g_scheduler->Log("Parsing", name.value()); | |
223 | |
224 // Tokenize. | |
225 std::vector<Token> tokens = Tokenizer::Tokenize(file, err); | |
226 if (err->has_error()) | |
227 return false; | |
228 | |
229 // Parse. | |
230 scoped_ptr<ParseNode> root = Parser::Parse(tokens, err); | |
231 if (err->has_error()) | |
232 return false; | |
233 ParseNode* unowned_root = root.get(); | |
234 | |
235 std::vector<FileLoadCallback> callbacks; | |
236 { | |
237 base::AutoLock lock(lock_); | |
238 DCHECK(input_files_.find(name) != input_files_.end()); | |
239 | |
240 InputFileData* data = input_files_[name]; | |
241 data->loaded = true; | |
242 data->tokens.swap(tokens); | |
243 data->parsed_root = root.Pass(); | |
244 | |
245 callbacks.swap(data->scheduled_callbacks); | |
246 } | |
247 | |
248 // Run pending invocations. Theoretically we could schedule each of these | |
249 // separately to get some parallelism. But normally there will only be one | |
250 // item in the list, so that's extra overhead and complexity for no gain. | |
251 for (size_t i = 0; i < callbacks.size(); i++) | |
252 callbacks[i].Run(unowned_root); | |
253 return true; | |
254 } | |
OLD | NEW |