OLD | NEW |
| (Empty) |
1 // Copyright 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 "chrome/browser/nacl_host/pnacl_translation_cache.h" | |
6 | |
7 #include <string> | |
8 | |
9 #include "base/callback.h" | |
10 #include "base/files/file_path.h" | |
11 #include "base/logging.h" | |
12 #include "base/strings/string_number_conversions.h" | |
13 #include "base/threading/thread_checker.h" | |
14 #include "components/nacl/common/pnacl_types.h" | |
15 #include "content/public/browser/browser_thread.h" | |
16 #include "net/base/io_buffer.h" | |
17 #include "net/base/net_errors.h" | |
18 #include "net/disk_cache/disk_cache.h" | |
19 | |
20 using base::IntToString; | |
21 using content::BrowserThread; | |
22 | |
23 namespace { | |
24 | |
25 void CloseDiskCacheEntry(disk_cache::Entry* entry) { entry->Close(); } | |
26 | |
27 } // namespace | |
28 | |
29 namespace pnacl { | |
30 // This is in pnacl namespace instead of static so they can be used | |
31 // by the unit test. | |
32 const int kMaxMemCacheSize = 100 * 1024 * 1024; | |
33 | |
34 ////////////////////////////////////////////////////////////////////// | |
35 // Handle Reading/Writing to Cache. | |
36 | |
37 // PnaclTranslationCacheEntry is a shim that provides storage for the | |
38 // 'key' and 'data' strings as the disk_cache is performing various async | |
39 // operations. It also tracks the open disk_cache::Entry | |
40 // and ensures that the entry is closed. | |
41 class PnaclTranslationCacheEntry | |
42 : public base::RefCounted<PnaclTranslationCacheEntry> { | |
43 public: | |
44 static PnaclTranslationCacheEntry* GetReadEntry( | |
45 base::WeakPtr<PnaclTranslationCache> cache, | |
46 const std::string& key, | |
47 const GetNexeCallback& callback); | |
48 static PnaclTranslationCacheEntry* GetWriteEntry( | |
49 base::WeakPtr<PnaclTranslationCache> cache, | |
50 const std::string& key, | |
51 net::DrainableIOBuffer* write_nexe, | |
52 const CompletionCallback& callback); | |
53 | |
54 void Start(); | |
55 | |
56 // Writes: --- | |
57 // v | | |
58 // Start -> Open Existing --------------> Write ---> Close | |
59 // \ ^ | |
60 // \ / | |
61 // --> Create -- | |
62 // Reads: | |
63 // Start -> Open --------Read ----> Close | |
64 // | ^ | |
65 // |__| | |
66 enum CacheStep { | |
67 UNINITIALIZED, | |
68 OPEN_ENTRY, | |
69 CREATE_ENTRY, | |
70 TRANSFER_ENTRY, | |
71 CLOSE_ENTRY, | |
72 FINISHED | |
73 }; | |
74 | |
75 private: | |
76 friend class base::RefCounted<PnaclTranslationCacheEntry>; | |
77 PnaclTranslationCacheEntry(base::WeakPtr<PnaclTranslationCache> cache, | |
78 const std::string& key, | |
79 bool is_read); | |
80 ~PnaclTranslationCacheEntry(); | |
81 | |
82 // Try to open an existing entry in the backend | |
83 void OpenEntry(); | |
84 // Create a new entry in the backend (for writes) | |
85 void CreateEntry(); | |
86 // Write |len| bytes to the backend, starting at |offset| | |
87 void WriteEntry(int offset, int len); | |
88 // Read |len| bytes from the backend, starting at |offset| | |
89 void ReadEntry(int offset, int len); | |
90 // If there was an error, doom the entry. Then post a task to the IO | |
91 // thread to close (and delete) it. | |
92 void CloseEntry(int rv); | |
93 // Call the user callback, and signal to the cache to delete this. | |
94 void Finish(int rv); | |
95 // Used as the callback for all operations to the backend. Handle state | |
96 // transitions, track bytes transferred, and call the other helper methods. | |
97 void DispatchNext(int rv); | |
98 | |
99 base::WeakPtr<PnaclTranslationCache> cache_; | |
100 std::string key_; | |
101 disk_cache::Entry* entry_; | |
102 CacheStep step_; | |
103 bool is_read_; | |
104 GetNexeCallback read_callback_; | |
105 CompletionCallback write_callback_; | |
106 scoped_refptr<net::DrainableIOBuffer> io_buf_; | |
107 base::ThreadChecker thread_checker_; | |
108 DISALLOW_COPY_AND_ASSIGN(PnaclTranslationCacheEntry); | |
109 }; | |
110 | |
111 // static | |
112 PnaclTranslationCacheEntry* PnaclTranslationCacheEntry::GetReadEntry( | |
113 base::WeakPtr<PnaclTranslationCache> cache, | |
114 const std::string& key, | |
115 const GetNexeCallback& callback) { | |
116 PnaclTranslationCacheEntry* entry( | |
117 new PnaclTranslationCacheEntry(cache, key, true)); | |
118 entry->read_callback_ = callback; | |
119 return entry; | |
120 } | |
121 | |
122 // static | |
123 PnaclTranslationCacheEntry* PnaclTranslationCacheEntry::GetWriteEntry( | |
124 base::WeakPtr<PnaclTranslationCache> cache, | |
125 const std::string& key, | |
126 net::DrainableIOBuffer* write_nexe, | |
127 const CompletionCallback& callback) { | |
128 PnaclTranslationCacheEntry* entry( | |
129 new PnaclTranslationCacheEntry(cache, key, false)); | |
130 entry->io_buf_ = write_nexe; | |
131 entry->write_callback_ = callback; | |
132 return entry; | |
133 } | |
134 | |
135 PnaclTranslationCacheEntry::PnaclTranslationCacheEntry( | |
136 base::WeakPtr<PnaclTranslationCache> cache, | |
137 const std::string& key, | |
138 bool is_read) | |
139 : cache_(cache), | |
140 key_(key), | |
141 entry_(NULL), | |
142 step_(UNINITIALIZED), | |
143 is_read_(is_read) {} | |
144 | |
145 PnaclTranslationCacheEntry::~PnaclTranslationCacheEntry() { | |
146 // Ensure we have called the user's callback | |
147 if (step_ != FINISHED) { | |
148 if (!read_callback_.is_null()) { | |
149 BrowserThread::PostTask( | |
150 BrowserThread::IO, | |
151 FROM_HERE, | |
152 base::Bind(read_callback_, | |
153 net::ERR_ABORTED, | |
154 scoped_refptr<net::DrainableIOBuffer>())); | |
155 } | |
156 if (!write_callback_.is_null()) { | |
157 BrowserThread::PostTask(BrowserThread::IO, | |
158 FROM_HERE, | |
159 base::Bind(write_callback_, net::ERR_ABORTED)); | |
160 } | |
161 } | |
162 } | |
163 | |
164 void PnaclTranslationCacheEntry::Start() { | |
165 DCHECK(thread_checker_.CalledOnValidThread()); | |
166 step_ = OPEN_ENTRY; | |
167 OpenEntry(); | |
168 } | |
169 | |
170 // OpenEntry, CreateEntry, WriteEntry, ReadEntry and CloseEntry are only called | |
171 // from DispatchNext, so they know that cache_ is still valid. | |
172 void PnaclTranslationCacheEntry::OpenEntry() { | |
173 int rv = cache_->backend()->OpenEntry( | |
174 key_, | |
175 &entry_, | |
176 base::Bind(&PnaclTranslationCacheEntry::DispatchNext, this)); | |
177 if (rv != net::ERR_IO_PENDING) | |
178 DispatchNext(rv); | |
179 } | |
180 | |
181 void PnaclTranslationCacheEntry::CreateEntry() { | |
182 int rv = cache_->backend()->CreateEntry( | |
183 key_, | |
184 &entry_, | |
185 base::Bind(&PnaclTranslationCacheEntry::DispatchNext, this)); | |
186 if (rv != net::ERR_IO_PENDING) | |
187 DispatchNext(rv); | |
188 } | |
189 | |
190 void PnaclTranslationCacheEntry::WriteEntry(int offset, int len) { | |
191 DCHECK(io_buf_->BytesRemaining() == len); | |
192 int rv = entry_->WriteData( | |
193 1, | |
194 offset, | |
195 io_buf_.get(), | |
196 len, | |
197 base::Bind(&PnaclTranslationCacheEntry::DispatchNext, this), | |
198 false); | |
199 if (rv != net::ERR_IO_PENDING) | |
200 DispatchNext(rv); | |
201 } | |
202 | |
203 void PnaclTranslationCacheEntry::ReadEntry(int offset, int len) { | |
204 int rv = entry_->ReadData( | |
205 1, | |
206 offset, | |
207 io_buf_.get(), | |
208 len, | |
209 base::Bind(&PnaclTranslationCacheEntry::DispatchNext, this)); | |
210 if (rv != net::ERR_IO_PENDING) | |
211 DispatchNext(rv); | |
212 } | |
213 | |
214 void PnaclTranslationCacheEntry::CloseEntry(int rv) { | |
215 DCHECK(entry_); | |
216 if (rv < 0) { | |
217 LOG(ERROR) << "Failed to close entry: " << net::ErrorToString(rv); | |
218 entry_->Doom(); | |
219 } | |
220 BrowserThread::PostTask( | |
221 BrowserThread::IO, FROM_HERE, base::Bind(&CloseDiskCacheEntry, entry_)); | |
222 Finish(rv); | |
223 } | |
224 | |
225 void PnaclTranslationCacheEntry::Finish(int rv) { | |
226 step_ = FINISHED; | |
227 if (is_read_) { | |
228 if (!read_callback_.is_null()) { | |
229 BrowserThread::PostTask(BrowserThread::IO, | |
230 FROM_HERE, | |
231 base::Bind(read_callback_, rv, io_buf_)); | |
232 } | |
233 } else { | |
234 if (!write_callback_.is_null()) { | |
235 BrowserThread::PostTask( | |
236 BrowserThread::IO, FROM_HERE, base::Bind(write_callback_, rv)); | |
237 } | |
238 } | |
239 cache_->OpComplete(this); | |
240 } | |
241 | |
242 void PnaclTranslationCacheEntry::DispatchNext(int rv) { | |
243 DCHECK(thread_checker_.CalledOnValidThread()); | |
244 if (!cache_) | |
245 return; | |
246 | |
247 switch (step_) { | |
248 case UNINITIALIZED: | |
249 case FINISHED: | |
250 LOG(ERROR) << "DispatchNext called uninitialized"; | |
251 break; | |
252 | |
253 case OPEN_ENTRY: | |
254 if (rv == net::OK) { | |
255 step_ = TRANSFER_ENTRY; | |
256 if (is_read_) { | |
257 int bytes_to_transfer = entry_->GetDataSize(1); | |
258 io_buf_ = new net::DrainableIOBuffer( | |
259 new net::IOBuffer(bytes_to_transfer), bytes_to_transfer); | |
260 ReadEntry(0, bytes_to_transfer); | |
261 } else { | |
262 WriteEntry(0, io_buf_->size()); | |
263 } | |
264 } else { | |
265 if (rv != net::ERR_FAILED) { | |
266 // ERROR_FAILED is what we expect if the entry doesn't exist. | |
267 LOG(ERROR) << "OpenEntry failed: " << net::ErrorToString(rv); | |
268 } | |
269 if (is_read_) { | |
270 // Just a cache miss, not necessarily an error. | |
271 entry_ = NULL; | |
272 Finish(rv); | |
273 } else { | |
274 step_ = CREATE_ENTRY; | |
275 CreateEntry(); | |
276 } | |
277 } | |
278 break; | |
279 | |
280 case CREATE_ENTRY: | |
281 if (rv == net::OK) { | |
282 step_ = TRANSFER_ENTRY; | |
283 WriteEntry(io_buf_->BytesConsumed(), io_buf_->BytesRemaining()); | |
284 } else { | |
285 LOG(ERROR) << "Failed to Create Entry: " << net::ErrorToString(rv); | |
286 Finish(rv); | |
287 } | |
288 break; | |
289 | |
290 case TRANSFER_ENTRY: | |
291 if (rv < 0) { | |
292 // We do not call DispatchNext directly if WriteEntry/ReadEntry returns | |
293 // ERR_IO_PENDING, and the callback should not return that value either. | |
294 LOG(ERROR) << "Failed to complete write to entry: " | |
295 << net::ErrorToString(rv); | |
296 step_ = CLOSE_ENTRY; | |
297 CloseEntry(rv); | |
298 break; | |
299 } else if (rv > 0) { | |
300 io_buf_->DidConsume(rv); | |
301 if (io_buf_->BytesRemaining() > 0) { | |
302 is_read_ | |
303 ? ReadEntry(io_buf_->BytesConsumed(), io_buf_->BytesRemaining()) | |
304 : WriteEntry(io_buf_->BytesConsumed(), io_buf_->BytesRemaining()); | |
305 break; | |
306 } | |
307 } | |
308 // rv == 0 or we fell through (i.e. we have transferred all the bytes) | |
309 step_ = CLOSE_ENTRY; | |
310 DCHECK(io_buf_->BytesConsumed() == io_buf_->size()); | |
311 if (is_read_) | |
312 io_buf_->SetOffset(0); | |
313 CloseEntry(0); | |
314 break; | |
315 | |
316 case CLOSE_ENTRY: | |
317 step_ = UNINITIALIZED; | |
318 break; | |
319 } | |
320 } | |
321 | |
322 ////////////////////////////////////////////////////////////////////// | |
323 void PnaclTranslationCache::OpComplete(PnaclTranslationCacheEntry* entry) { | |
324 open_entries_.erase(entry); | |
325 } | |
326 | |
327 ////////////////////////////////////////////////////////////////////// | |
328 // Construction and cache backend initialization | |
329 PnaclTranslationCache::PnaclTranslationCache() : in_memory_(false) {} | |
330 | |
331 PnaclTranslationCache::~PnaclTranslationCache() {} | |
332 | |
333 int PnaclTranslationCache::Init(net::CacheType cache_type, | |
334 const base::FilePath& cache_dir, | |
335 int cache_size, | |
336 const CompletionCallback& callback) { | |
337 int rv = disk_cache::CreateCacheBackend( | |
338 cache_type, | |
339 net::CACHE_BACKEND_DEFAULT, | |
340 cache_dir, | |
341 cache_size, | |
342 true /* force_initialize */, | |
343 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::CACHE).get(), | |
344 NULL, /* dummy net log */ | |
345 &disk_cache_, | |
346 base::Bind(&PnaclTranslationCache::OnCreateBackendComplete, AsWeakPtr())); | |
347 if (rv == net::ERR_IO_PENDING) { | |
348 init_callback_ = callback; | |
349 } | |
350 return rv; | |
351 } | |
352 | |
353 void PnaclTranslationCache::OnCreateBackendComplete(int rv) { | |
354 if (rv < 0) { | |
355 LOG(ERROR) << "Backend init failed:" << net::ErrorToString(rv); | |
356 } | |
357 // Invoke our client's callback function. | |
358 if (!init_callback_.is_null()) { | |
359 BrowserThread::PostTask( | |
360 BrowserThread::IO, FROM_HERE, base::Bind(init_callback_, rv)); | |
361 } | |
362 } | |
363 | |
364 ////////////////////////////////////////////////////////////////////// | |
365 // High-level API | |
366 | |
367 void PnaclTranslationCache::StoreNexe(const std::string& key, | |
368 net::DrainableIOBuffer* nexe_data, | |
369 const CompletionCallback& callback) { | |
370 PnaclTranslationCacheEntry* entry = PnaclTranslationCacheEntry::GetWriteEntry( | |
371 AsWeakPtr(), key, nexe_data, callback); | |
372 open_entries_[entry] = entry; | |
373 entry->Start(); | |
374 } | |
375 | |
376 void PnaclTranslationCache::GetNexe(const std::string& key, | |
377 const GetNexeCallback& callback) { | |
378 PnaclTranslationCacheEntry* entry = | |
379 PnaclTranslationCacheEntry::GetReadEntry(AsWeakPtr(), key, callback); | |
380 open_entries_[entry] = entry; | |
381 entry->Start(); | |
382 } | |
383 | |
384 int PnaclTranslationCache::InitOnDisk(const base::FilePath& cache_directory, | |
385 const CompletionCallback& callback) { | |
386 in_memory_ = false; | |
387 return Init(net::PNACL_CACHE, cache_directory, 0 /* auto size */, callback); | |
388 } | |
389 | |
390 int PnaclTranslationCache::InitInMemory(const CompletionCallback& callback) { | |
391 in_memory_ = true; | |
392 return Init(net::MEMORY_CACHE, base::FilePath(), kMaxMemCacheSize, callback); | |
393 } | |
394 | |
395 int PnaclTranslationCache::Size() { | |
396 if (!disk_cache_) | |
397 return -1; | |
398 return disk_cache_->GetEntryCount(); | |
399 } | |
400 | |
401 // static | |
402 std::string PnaclTranslationCache::GetKey(const nacl::PnaclCacheInfo& info) { | |
403 if (!info.pexe_url.is_valid() || info.abi_version < 0 || info.opt_level < 0) | |
404 return std::string(); | |
405 std::string retval("ABI:"); | |
406 retval += IntToString(info.abi_version) + ";" + "opt:" + | |
407 IntToString(info.opt_level) + ";" + "URL:"; | |
408 // Filter the username, password, and ref components from the URL | |
409 GURL::Replacements replacements; | |
410 replacements.ClearUsername(); | |
411 replacements.ClearPassword(); | |
412 replacements.ClearRef(); | |
413 GURL key_url(info.pexe_url.ReplaceComponents(replacements)); | |
414 retval += key_url.spec() + ";"; | |
415 // You would think that there is already code to format base::Time values | |
416 // somewhere, but I haven't found it yet. In any case, doing it ourselves | |
417 // here means we can keep the format stable. | |
418 base::Time::Exploded exploded; | |
419 info.last_modified.UTCExplode(&exploded); | |
420 if (info.last_modified.is_null() || !exploded.HasValidValues()) { | |
421 memset(&exploded, 0, sizeof(exploded)); | |
422 } | |
423 retval += "modified:" + IntToString(exploded.year) + ":" + | |
424 IntToString(exploded.month) + ":" + | |
425 IntToString(exploded.day_of_month) + ":" + | |
426 IntToString(exploded.hour) + ":" + IntToString(exploded.minute) + | |
427 ":" + IntToString(exploded.second) + ":" + | |
428 IntToString(exploded.millisecond) + ":UTC;"; | |
429 retval += "etag:" + info.etag; | |
430 return retval; | |
431 } | |
432 | |
433 int PnaclTranslationCache::DoomEntriesBetween( | |
434 base::Time initial, | |
435 base::Time end, | |
436 const CompletionCallback& callback) { | |
437 return disk_cache_->DoomEntriesBetween(initial, end, callback); | |
438 } | |
439 | |
440 } // namespace pnacl | |
OLD | NEW |