Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(237)

Side by Side Diff: storage/browser/blob/blob_memory_controller.cc

Issue 2339933004: [BlobStorage] BlobMemoryController & tests (Closed)
Patch Set: comments, more tests are next Created 4 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2016 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 "storage/browser/blob/blob_memory_controller.h"
6
7 #include <algorithm>
8
9 #include "base/callback.h"
10 #include "base/callback_helpers.h"
11 #include "base/containers/small_map.h"
12 #include "base/files/file_util.h"
13 #include "base/guid.h"
14 #include "base/location.h"
15 #include "base/memory/ptr_util.h"
16 #include "base/metrics/histogram_macros.h"
17 #include "base/numerics/safe_conversions.h"
18 #include "base/numerics/safe_math.h"
19 #include "base/single_thread_task_runner.h"
20 #include "base/stl_util.h"
21 #include "base/strings/string_number_conversions.h"
22 #include "base/task_runner.h"
23 #include "base/task_runner_util.h"
24 #include "base/threading/thread_restrictions.h"
25 #include "base/time/time.h"
26 #include "base/trace_event/trace_event.h"
27 #include "base/tuple.h"
28 #include "storage/browser/blob/blob_data_builder.h"
29 #include "storage/browser/blob/blob_data_item.h"
30 #include "storage/browser/blob/shareable_blob_data_item.h"
31 #include "storage/browser/blob/shareable_file_reference.h"
32 #include "storage/common/data_element.h"
33
34 using base::File;
35 using base::FilePath;
36 using FileCreationInfo = storage::BlobMemoryController::FileCreationInfo;
37
38 namespace storage {
39 namespace {
40 using QuotaAllocationTask = BlobMemoryController::QuotaAllocationTask;
41
42 File::Error CreateBlobDirectory(const FilePath& blob_storage_dir) {
43 File::Error error;
44 bool success = base::CreateDirectoryAndGetError(blob_storage_dir, &error);
45 UMA_HISTOGRAM_BOOLEAN("Storage.Blob.DirectorySuccess", success);
46 if (!success) {
47 UMA_HISTOGRAM_ENUMERATION("Storage.Blob.DirectorySuccess.Error", -error,
48 -File::FILE_ERROR_MAX);
49 return error;
50 }
51 return File::FILE_OK;
52 }
53
54 // Used for new unpopulated file items. Caller must populate file reference in
55 // returned FileCreationInfos.
56 std::vector<FileCreationInfo> CreateEmptyFiles(
57 const FilePath& blob_storage_dir,
58 std::vector<base::FilePath> file_paths) {
59 base::ThreadRestrictions::AssertIOAllowed();
60
61 if (CreateBlobDirectory(blob_storage_dir) != File::FILE_OK)
62 return std::vector<FileCreationInfo>();
63
64 std::vector<FileCreationInfo> result;
65 for (const base::FilePath& file_path : file_paths) {
66 FileCreationInfo creation_info;
67 // Try to open our file.
68 File file(file_path, File::FLAG_CREATE_ALWAYS | File::FLAG_WRITE);
69 creation_info.path = std::move(file_path);
70 creation_info.error = file.error_details();
71 UMA_HISTOGRAM_ENUMERATION("Storage.Blob.TransportFileCreate",
72 -creation_info.error, -File::FILE_ERROR_MAX);
73 if (creation_info.error != File::FILE_OK)
74 return std::vector<FileCreationInfo>();
75
76 // Grab the file info to get the "last modified" time and store the file.
77 File::Info file_info;
78 bool success = file.GetInfo(&file_info);
79 UMA_HISTOGRAM_BOOLEAN("Storage.Blob.TransportFileInfoSuccess", success);
80 creation_info.error = success ? File::FILE_OK : File::FILE_ERROR_FAILED;
81 if (!success)
82 return std::vector<FileCreationInfo>();
83 creation_info.file = std::move(file);
84
85 result.push_back(std::move(creation_info));
86 }
87 return result;
88 }
89
90 // Used to evict multiple memory items out to a single file. Caller must
91 // populate file reference in returned FileCreationInfo.
92 FileCreationInfo CreateFileAndWriteItems(const FilePath& blob_storage_dir,
93 const FilePath& file_path,
94 std::vector<DataElement*> items,
95 size_t total_size_bytes) {
96 DCHECK_NE(0u, total_size_bytes);
97 UMA_HISTOGRAM_MEMORY_KB("Storage.Blob.PageFileSize", total_size_bytes / 1024);
98 base::ThreadRestrictions::AssertIOAllowed();
99
100 FileCreationInfo creation_info;
101 creation_info.error = CreateBlobDirectory(blob_storage_dir);
102 if (creation_info.error != File::FILE_OK)
103 return creation_info;
104
105 // Create the page file.
106 File file(file_path, File::FLAG_CREATE_ALWAYS | File::FLAG_WRITE);
107 creation_info.path = std::move(file_path);
michaeln 2016/10/04 00:11:54 stupid c++ question? does this do anything the cal
dmurph 2016/10/06 00:45:39 Ah, actually, this does a copy. If we std::move'd
108 creation_info.error = file.error_details();
109 UMA_HISTOGRAM_ENUMERATION("Storage.Blob.PageFileCreate", -creation_info.error,
110 -File::FILE_ERROR_MAX);
111 if (creation_info.error != File::FILE_OK)
112 return creation_info;
113
114 // Write data.
115 file.SetLength(total_size_bytes);
116 int bytes_written = 0;
117 for (DataElement* element : items) {
118 DCHECK_EQ(DataElement::TYPE_BYTES, element->type());
119 size_t length = base::checked_cast<size_t>(element->length());
120 size_t bytes_left = length;
121 while (bytes_left > 0) {
122 bytes_written =
123 file.WriteAtCurrentPos(element->bytes() + (length - bytes_left),
124 base::saturated_cast<int>(bytes_left));
125 if (bytes_written < 0)
126 break;
127 DCHECK_LE(static_cast<size_t>(bytes_written), bytes_left);
128 bytes_left -= bytes_written;
129 }
130 if (bytes_written < 0)
131 break;
132 }
133 UMA_HISTOGRAM_BOOLEAN("Storage.Blob.PageFileWriteSuccess", bytes_written > 0);
134
135 File::Info info;
136 bool success = file.GetInfo(&info);
137 UMA_HISTOGRAM_BOOLEAN("Storage.Blob.PageFileInfoSuccess", success);
138 creation_info.error =
139 bytes_written < 0 || !success ? File::FILE_ERROR_FAILED : File::FILE_OK;
140 creation_info.last_modified = info.last_modified;
141 return creation_info;
142 }
143
144 std::vector<scoped_refptr<ShareableBlobDataItem>> WrapInRefPtrs(
michaeln 2016/10/04 00:11:54 if the sig of the methods are changed, might not n
dmurph 2016/10/06 00:45:39 Done.
145 const std::vector<ShareableBlobDataItem*> items_ptrs) {
146 std::vector<scoped_refptr<ShareableBlobDataItem>> result;
147 for (ShareableBlobDataItem* item : items_ptrs) {
148 result.push_back(make_scoped_refptr(item));
149 }
150 return result;
151 }
152
153 uint64_t GetTotalSizeAndFileSizes(
154 const std::vector<scoped_refptr<ShareableBlobDataItem>>&
155 unreserved_file_items,
156 std::vector<uint64_t>* file_sizes_output) {
157 uint64_t total_size_output = 0;
158 base::SmallMap<std::map<uint64_t, uint64_t>> file_sizes;
159 for (const auto& item : unreserved_file_items) {
160 const DataElement& element = item->item()->data_element();
161 uint64_t file_id = BlobDataBuilder::GetFutureFileID(element);
162 auto it = file_sizes.find(file_id);
163 if (it != file_sizes.end()) {
164 it->second = std::max(it->second, element.offset() + element.length());
165 } else {
166 file_sizes[file_id] = element.offset() + element.length();
167 }
168 total_size_output += element.length();
169 }
170 for (const auto& size_pair : file_sizes) {
171 file_sizes_output->push_back(size_pair.second);
michaeln 2016/10/04 00:11:55 does the order of values in the array matter?
dmurph 2016/10/06 00:45:39 Yeah, it needs to be in file_id order, which is en
172 }
173 return total_size_output;
174 }
175
176 } // namespace
177
178 BlobMemoryController::QuotaAllocationTask::~QuotaAllocationTask() {}
179
180 // The my_list_position_ iterator is stored so that we can remove ourself from
181 // the task list when it is cancelled.
182 template <typename T>
183 class BlobMemoryController::BaseQuotaAllocationTask
184 : public BlobMemoryController::QuotaAllocationTask {
185 public:
186 BaseQuotaAllocationTask() : allocation_size_(0) {}
187 explicit BaseQuotaAllocationTask(uint64_t allocation_size)
188 : allocation_size_(allocation_size) {}
189
190 ~BaseQuotaAllocationTask() override {}
191
192 void set_my_list_position(typename T::iterator my_list_position) {
193 my_list_position_ = my_list_position;
194 }
195 typename T::iterator my_list_position() { return my_list_position_; }
196
197 uint64_t allocation_size() { return allocation_size_; }
198 void set_allocation_size(uint64_t allocation_size) {
199 allocation_size_ = allocation_size;
200 }
201
202 private:
203 uint64_t allocation_size_;
204 typename T::iterator my_list_position_;
205 };
206
207 class BlobMemoryController::MemoryQuotaAllocationTask
208 : public BlobMemoryController::BaseQuotaAllocationTask<
209 PendingMemoryQuotaTaskList> {
210 public:
211 MemoryQuotaAllocationTask(
212 BlobMemoryController* controller,
213 uint64_t quota_request_size,
214 std::vector<scoped_refptr<ShareableBlobDataItem>> pending_items,
215 MemoryQuotaRequestCallback done_callback)
216 : BaseQuotaAllocationTask(quota_request_size),
217 controller_(controller),
218 pending_items_(std::move(pending_items)),
219 done_callback_(done_callback),
220 weak_factory_(this) {}
221
222 ~MemoryQuotaAllocationTask() override = default;
223
224 void RunDoneCallback(bool success) const {
225 if (success) {
226 for (const auto& item : pending_items_) {
227 item->set_state(ShareableBlobDataItem::QUOTA_GRANTED);
228 }
229 }
230 done_callback_.Run(success);
231 }
232
233 void Cancel() override {
234 controller_->pending_memory_quota_total_size_ -= allocation_size();
235 // This call destroys this object.
236 controller_->pending_memory_quota_tasks_.erase(my_list_position());
237 }
238
239 BlobMemoryController* controller_;
240 std::vector<scoped_refptr<ShareableBlobDataItem>> pending_items_;
241 MemoryQuotaRequestCallback done_callback_;
242
243 base::WeakPtrFactory<MemoryQuotaAllocationTask> weak_factory_;
244 DISALLOW_COPY_AND_ASSIGN(MemoryQuotaAllocationTask);
245 };
246
247 class BlobMemoryController::FileQuotaAllocationTask
248 : public BlobMemoryController::BaseQuotaAllocationTask<
249 PendingFileQuotaTaskList> {
250 public:
251 FileQuotaAllocationTask(
252 BlobMemoryController* memory_controller,
253 std::vector<scoped_refptr<ShareableBlobDataItem>> unreserved_file_items,
254 const FileQuotaRequestCallback& done_callback)
255 : controller_(memory_controller),
256 done_callback_(done_callback),
257 weak_factory_(this) {
258 // Get the file sizes and total size.
259 std::vector<uint64_t> file_sizes;
260 uint64_t total_size =
261 GetTotalSizeAndFileSizes(unreserved_file_items, &file_sizes);
262 DCHECK_LE(total_size, controller_->GetAvailableFileSpaceForBlobs());
263 set_allocation_size(total_size);
264
265 // Check & set our item states.
266 for (const auto& shareable_item : unreserved_file_items) {
267 DCHECK_EQ(ShareableBlobDataItem::QUOTA_NEEDED, shareable_item->state());
268 DCHECK_EQ(DataElement::TYPE_FILE, shareable_item->item()->type());
269 shareable_item->set_state(ShareableBlobDataItem::QUOTA_REQUESTED);
270 }
271 pending_items_ = std::move(unreserved_file_items);
272
273 // Increment disk usage and create our file references.
274 controller_->disk_used_ += total_size;
michaeln 2016/10/04 00:11:54 nit: i was looking around for += allocation_size()
dmurph 2016/10/06 00:45:39 Done.
275 std::vector<base::FilePath> file_paths;
276 std::vector<scoped_refptr<ShareableFileReference>> references;
277 for (size_t i = 0; i < file_sizes.size(); i++) {
278 file_paths.push_back(controller_->GenerateNextPageFileName());
279 references.push_back(ShareableFileReference::GetOrCreate(
280 file_paths.back(), ShareableFileReference::DELETE_ON_FINAL_RELEASE,
281 controller_->file_runner_.get()));
282 references.back()->AddFinalReleaseCallback(
283 base::Bind(&BlobMemoryController::OnBlobFileDelete,
284 controller_->weak_factory_.GetWeakPtr(), file_sizes[i]));
285 }
286
287 // Send file creation task to file thread.
288 base::PostTaskAndReplyWithResult(
289 controller_->file_runner_.get(), FROM_HERE,
290 base::Bind(&CreateEmptyFiles, controller_->blob_storage_dir_,
291 base::Passed(&file_paths)),
292 base::Bind(&FileQuotaAllocationTask::OnCreateEmptyFiles,
293 weak_factory_.GetWeakPtr(), base::Passed(&references)));
294 controller_->RecordTracingCounters();
295 }
296 ~FileQuotaAllocationTask() override {}
297
298 void RunDoneCallback(bool success,
299 std::vector<FileCreationInfo> file_info) const {
300 done_callback_.Run(success, std::move(file_info));
301 }
302
303 void Cancel() override {
304 controller_->disk_used_ -= allocation_size();
305 // This call destroys this object.
306 controller_->pending_file_quota_tasks_.erase(my_list_position());
307 }
308
309 void OnCreateEmptyFiles(
310 std::vector<scoped_refptr<ShareableFileReference>> references,
311 std::vector<FileCreationInfo> files) {
312 if (files.empty()) {
313 controller_->disk_used_ -= allocation_size();
314 // This will call call our callback and delete the object correctly.
315 controller_->DisableFilePaging();
316 return;
317 }
318 DCHECK_EQ(files.size(), references.size());
319 for (size_t i = 0; i < files.size(); i++) {
320 files[i].file_reference = std::move(references[i]);
321 }
322 for (const auto& item : pending_items_) {
323 item->set_state(ShareableBlobDataItem::QUOTA_GRANTED);
324 }
325 RunDoneCallback(true, std::move(files));
326 }
327
328 BlobMemoryController* controller_;
329 std::vector<scoped_refptr<ShareableBlobDataItem>> pending_items_;
330 scoped_refptr<base::TaskRunner> file_runner_;
331 FileQuotaRequestCallback done_callback_;
332
333 base::WeakPtrFactory<FileQuotaAllocationTask> weak_factory_;
334 DISALLOW_COPY_AND_ASSIGN(FileQuotaAllocationTask);
335 };
336
337 FileCreationInfo::FileCreationInfo() = default;
338 FileCreationInfo::~FileCreationInfo() = default;
339 FileCreationInfo::FileCreationInfo(FileCreationInfo&&) = default;
340 FileCreationInfo& FileCreationInfo::operator=(FileCreationInfo&&) = default;
341
342 BlobMemoryController::BlobMemoryController(
343 const base::FilePath& storage_directory,
344 scoped_refptr<base::TaskRunner> file_runner)
345 : file_paging_enabled_(file_runner.get() != nullptr),
346 blob_storage_dir_(storage_directory),
347 file_runner_(std::move(file_runner)),
348 recent_item_cache_(
349 base::MRUCache<uint64_t, ShareableBlobDataItem*>::NO_AUTO_EVICT),
350 weak_factory_(this) {}
351
352 BlobMemoryController::~BlobMemoryController() {}
353
354 void BlobMemoryController::DisableFilePaging() {
355 file_paging_enabled_ = false;
356 blob_memory_used_ += in_flight_memory_used_;
357 in_flight_memory_used_ = 0;
358 items_saving_to_disk_.clear();
359 for (const auto& file_request : pending_file_quota_tasks_) {
360 DCHECK_GE(disk_used_, file_request->allocation_size());
361 disk_used_ -= file_request->allocation_size();
362 }
363 pending_evictions_ = 0;
364 pending_memory_quota_total_size_ = 0;
365 recent_item_cache_.Clear();
366 recent_item_cache_bytes_ = 0;
367 file_runner_ = nullptr;
368
369 PendingMemoryQuotaTaskList old_memory_tasks;
370 PendingFileQuotaTaskList old_file_tasks;
371 std::swap(old_memory_tasks, pending_memory_quota_tasks_);
372 std::swap(old_file_tasks, pending_file_quota_tasks_);
373 // Kill all disk operation callbacks.
374 weak_factory_.InvalidateWeakPtrs();
375
376 // Don't call the callbacks until we have a consistant state.
377 for (const auto& memory_request : old_memory_tasks) {
378 memory_request->RunDoneCallback(false);
379 }
380 for (const auto& file_request : old_file_tasks) {
381 file_request->RunDoneCallback(false, std::vector<FileCreationInfo>());
382 }
383 }
384
385 BlobMemoryController::Strategy BlobMemoryController::DetermineStrategy(
386 size_t preemptive_transported_bytes,
387 uint64_t total_transportation_bytes) const {
388 if (total_transportation_bytes == 0) {
389 return Strategy::NONE_NEEDED;
390 }
391 if (!CanReserveQuota(total_transportation_bytes)) {
392 return Strategy::TOO_LARGE;
393 }
394 // Handle the case where we have all the bytes preemptively transported, and
395 // we can also fit them.
396 if (preemptive_transported_bytes == total_transportation_bytes &&
397 pending_memory_quota_tasks_.empty() &&
398 preemptive_transported_bytes < GetAvailableMemoryForBlobs()) {
399 return Strategy::NONE_NEEDED;
400 }
401 if (file_paging_enabled_ &&
402 (total_transportation_bytes > limits_.max_blob_in_memory_space)) {
403 return Strategy::FILE;
404 }
405 if (total_transportation_bytes > limits_.max_ipc_memory_size) {
406 return Strategy::SHARED_MEMORY;
407 }
408 return Strategy::IPC;
409 }
410
411 bool BlobMemoryController::CanReserveQuota(uint64_t size) const {
412 // We check each size independently as a blob can't be constructed in both
413 // disk and memory.
414 return size <= GetAvailableMemoryForBlobs() ||
415 size <= GetAvailableFileSpaceForBlobs();
416 }
417
418 base::WeakPtr<QuotaAllocationTask> BlobMemoryController::ReserveMemoryQuota(
419 std::vector<ShareableBlobDataItem*> unreserved_memory_items,
420 const MemoryQuotaRequestCallback& done_callback) {
421 base::CheckedNumeric<uint64_t> unsafe_total_bytes_needed = 0;
422 for (ShareableBlobDataItem* item : unreserved_memory_items) {
423 DCHECK_EQ(ShareableBlobDataItem::QUOTA_NEEDED, item->state());
424 DCHECK(item->item()->type() == DataElement::TYPE_BYTES_DESCRIPTION ||
425 item->item()->type() == DataElement::TYPE_BYTES);
426 DCHECK(item->item()->length() > 0);
427 unsafe_total_bytes_needed += item->item()->length();
428 item->set_state(ShareableBlobDataItem::QUOTA_REQUESTED);
429 }
430
431 uint64_t total_bytes_needed = unsafe_total_bytes_needed.ValueOrDie();
432 if (total_bytes_needed == 0) {
433 done_callback.Run(true);
434 return base::WeakPtr<QuotaAllocationTask>();
435 }
436
437 // If we're currently waiting for blobs to page already, then we add
438 // ourselves to the end of the queue. Once paging is complete, we'll schedule
439 // more paging for any more pending blobs.
440 if (!pending_memory_quota_tasks_.empty()) {
michaeln 2016/10/04 00:11:54 it'd might be good to share this block of code wit
dmurph 2016/10/06 00:45:39 Done.
441 DCHECK(!file_paging_enabled_);
442 std::vector<scoped_refptr<ShareableBlobDataItem>> item_handles =
443 WrapInRefPtrs(unreserved_memory_items);
444
445 pending_memory_quota_total_size_ += total_bytes_needed;
446 pending_memory_quota_tasks_.push_back(
447 base::MakeUnique<MemoryQuotaAllocationTask>(
448 this, total_bytes_needed, std::move(item_handles), done_callback));
449 pending_memory_quota_tasks_.back()->set_my_list_position(
450 --pending_memory_quota_tasks_.end());
451
452 return pending_memory_quota_tasks_.back()->weak_factory_.GetWeakPtr();
453 }
454
455 // Store right away if we can.
456 if (total_bytes_needed <= GetAvailableMemoryForBlobs()) {
457 // If we're past our blob memory limit, then schedule our paging.
458 blob_memory_used_ += total_bytes_needed;
459 for (ShareableBlobDataItem* item : unreserved_memory_items) {
460 item->set_state(ShareableBlobDataItem::QUOTA_GRANTED);
461 }
462 if (file_paging_enabled_)
463 MaybeScheduleEvictionUntilSystemHealthy();
464 done_callback.Run(true);
465 return base::WeakPtr<QuotaAllocationTask>();
466 }
467
468 // Size is larger than available memory.
469 DCHECK(pending_memory_quota_tasks_.empty());
470 DCHECK_EQ(0u, pending_memory_quota_total_size_);
471 std::vector<scoped_refptr<ShareableBlobDataItem>> item_handles =
472 WrapInRefPtrs(unreserved_memory_items);
473
474 pending_memory_quota_total_size_ = total_bytes_needed;
475 pending_memory_quota_tasks_.push_back(
476 base::MakeUnique<MemoryQuotaAllocationTask>(
477 this, total_bytes_needed, std::move(item_handles), done_callback));
478 pending_memory_quota_tasks_.back()->set_my_list_position(
479 --pending_memory_quota_tasks_.end());
480
481 auto weak_ptr =
482 pending_memory_quota_tasks_.back()->weak_factory_.GetWeakPtr();
483 MaybeScheduleEvictionUntilSystemHealthy();
484 return weak_ptr;
485 }
486
487 base::WeakPtr<QuotaAllocationTask> BlobMemoryController::ReserveFileQuota(
488 std::vector<scoped_refptr<ShareableBlobDataItem>> unreserved_file_items,
489 const FileQuotaRequestCallback& done_callback) {
490 pending_file_quota_tasks_.push_back(base::MakeUnique<FileQuotaAllocationTask>(
491 this, std::move(unreserved_file_items), done_callback));
492 pending_file_quota_tasks_.back()->set_my_list_position(
493 --pending_file_quota_tasks_.end());
494 return pending_file_quota_tasks_.back()->weak_factory_.GetWeakPtr();
495 }
496
497 void BlobMemoryController::MaybeFreeMemoryQuotaForItems(
498 const std::vector<scoped_refptr<ShareableBlobDataItem>>& items) {
499 base::CheckedNumeric<size_t> memory_to_be_freed = 0;
500 std::unordered_set<uint64_t> visited_items;
501 for (const scoped_refptr<ShareableBlobDataItem>& item : items) {
502 if (!item->HasGrantedQuota())
503 continue;
504 // We only care about bytes items that don't have blobs referencing them,
505 // and we remove duplicates.
506 DataElement::Type type = item->item()->type();
507 if (!item->referencing_blobs().empty() ||
508 (type != DataElement::TYPE_BYTES &&
509 type != DataElement::TYPE_BYTES_DESCRIPTION) ||
510 base::ContainsKey(visited_items, item->item_id()))
511 continue;
512 visited_items.insert(item->item_id());
513 memory_to_be_freed += base::checked_cast<size_t>(item->item()->length());
514 RemoveItemInRecents(*item);
515 }
516 size_t checked_memory = memory_to_be_freed.ValueOrDie();
517 if (checked_memory != 0) {
518 DCHECK_GE(blob_memory_used_, checked_memory);
519 blob_memory_used_ -= checked_memory;
520 MaybeGrantPendingQuotaRequests();
521 }
522 }
523
524 void BlobMemoryController::ForceFreeMemoryQuotaForItem(
525 const scoped_refptr<ShareableBlobDataItem>& item) {
526 CHECK(item->HasGrantedQuota());
527 size_t size = base::checked_cast<size_t>(item->item()->length());
528 DCHECK_GE(blob_memory_used_, size);
529 blob_memory_used_ -= size;
530 RemoveItemInRecents(*item);
531 MaybeGrantPendingQuotaRequests();
532 }
533
534 void BlobMemoryController::NotifyMemoryItemUsed(ShareableBlobDataItem* item) {
535 DCHECK_EQ(DataElement::TYPE_BYTES, item->item()->type());
536 DCHECK_EQ(ShareableBlobDataItem::POPULATED_WITH_QUOTA, item->state());
537 // We don't want to re-add the item if we're currently paging it to disk.
538 if (items_saving_to_disk_.find(item->item_id()) !=
539 items_saving_to_disk_.end())
540 return;
541 auto iterator = recent_item_cache_.Get(item->item_id());
542 if (iterator == recent_item_cache_.end()) {
543 recent_item_cache_bytes_ += static_cast<size_t>(item->item()->length());
544 recent_item_cache_.Put(item->item_id(), item);
545 MaybeScheduleEvictionUntilSystemHealthy();
546 }
547 }
548
549 void BlobMemoryController::RemoveItemInRecents(
550 const ShareableBlobDataItem& item) {
551 auto iterator = recent_item_cache_.Get(item.item_id());
552 if (iterator == recent_item_cache_.end())
553 return;
554 size_t size = base::checked_cast<size_t>(item.item()->length());
555 DCHECK_GE(recent_item_cache_bytes_, size);
556 recent_item_cache_bytes_ -= size;
557 recent_item_cache_.Erase(iterator);
558 }
559
560 void BlobMemoryController::MaybeGrantPendingQuotaRequests() {
561 while (!pending_memory_quota_tasks_.empty() &&
562 limits_.max_blob_in_memory_space - blob_memory_used_ >=
563 pending_memory_quota_tasks_.front()->allocation_size()) {
564 std::unique_ptr<MemoryQuotaAllocationTask> memory_task =
565 std::move(pending_memory_quota_tasks_.front());
566 pending_memory_quota_tasks_.pop_front();
567 size_t request_size = memory_task->allocation_size();
568 pending_memory_quota_total_size_ -= request_size;
569 blob_memory_used_ += request_size;
570 memory_task->RunDoneCallback(true);
571 }
572 RecordTracingCounters();
573 }
574
575 size_t BlobMemoryController::CollectItemsForEviction(
576 std::vector<scoped_refptr<ShareableBlobDataItem>>* output) {
577 base::CheckedNumeric<size_t> total_items_size = 0;
578 // Process the recent item list and remove items until we have at least
579 while (total_items_size.ValueOrDie() < limits_.min_page_file_size &&
580 !recent_item_cache_.empty()) {
581 auto iterator = --recent_item_cache_.end();
582 ShareableBlobDataItem* item = iterator->second;
583 DCHECK(item);
584 DCHECK_EQ(item->item()->type(), DataElement::TYPE_BYTES);
585 recent_item_cache_.Erase(iterator);
586 size_t size = base::checked_cast<size_t>(item->item()->length());
587 recent_item_cache_bytes_ -= size;
588 total_items_size += size;
589 output->push_back(make_scoped_refptr(item));
590 }
591 return total_items_size.ValueOrDie();
592 }
593
594 void BlobMemoryController::MaybeScheduleEvictionUntilSystemHealthy() {
595 // Don't do eviction when others are happening, as we don't change our
596 // pending_memory_quota_total_size_ value until after the paging files have
597 // been written.
598 if (pending_evictions_ != 0 || !file_paging_enabled_)
599 return;
600
601 // We try to page items to disk until our current system size + requested
602 // memory is below our size limit.
603 while (pending_memory_quota_total_size_ + blob_memory_used_ >
604 limits_.max_blob_in_memory_space) {
605 // We only page when we have enough items to fill a whole page file.
606 if (recent_item_cache_bytes_ < limits_.min_page_file_size)
607 break;
608 DCHECK_LE(limits_.min_page_file_size,
609 static_cast<uint64_t>(blob_memory_used_));
610
611 std::vector<scoped_refptr<ShareableBlobDataItem>> items_to_swap;
612 size_t total_items_size = CollectItemsForEviction(&items_to_swap);
613 if (total_items_size == 0)
614 break;
615
616 std::vector<DataElement*> items_for_disk;
617 for (const auto& shared_blob_item : items_to_swap) {
618 items_saving_to_disk_.insert(shared_blob_item->item_id());
619 items_for_disk.push_back(shared_blob_item->item()->data_element_ptr());
620 }
621
622 // Update our bookkeeping.
623 pending_evictions_++;
624 disk_used_ += total_items_size;
625 DCHECK_GE(blob_memory_used_, total_items_size);
626 blob_memory_used_ -= total_items_size;
627 in_flight_memory_used_ += total_items_size;
628
629 // Create our file reference.
630 scoped_refptr<ShareableFileReference> file_reference =
631 ShareableFileReference::GetOrCreate(
632 GenerateNextPageFileName(),
633 ShareableFileReference::DELETE_ON_FINAL_RELEASE,
634 file_runner_.get());
635 // Add the release callback so we decrement our disk usage on file deletion.
636 file_reference->AddFinalReleaseCallback(
637 base::Bind(&BlobMemoryController::OnBlobFileDelete,
638 weak_factory_.GetWeakPtr(), total_items_size));
639
640 // Post the file writing task.
641 base::PostTaskAndReplyWithResult(
642 file_runner_.get(), FROM_HERE,
643 base::Bind(&CreateFileAndWriteItems, blob_storage_dir_,
644 file_reference->path(), base::Passed(&items_for_disk),
645 total_items_size),
646 base::Bind(&BlobMemoryController::OnEvictionComplete,
647 weak_factory_.GetWeakPtr(), base::Passed(&file_reference),
648 base::Passed(&items_to_swap), total_items_size));
649 }
650 RecordTracingCounters();
651 }
652
653 void BlobMemoryController::OnEvictionComplete(
654 scoped_refptr<ShareableFileReference> file_reference,
655 std::vector<scoped_refptr<ShareableBlobDataItem>> items,
656 size_t total_items_size,
657 FileCreationInfo result) {
658 if (!file_paging_enabled_)
659 return;
660
661 if (result.error != File::FILE_OK) {
662 UMA_HISTOGRAM_ENUMERATION("Storage.Blob.PagingError", -result.error,
663 -File::FILE_ERROR_MAX);
664 disk_used_ -= total_items_size;
665 DisableFilePaging();
666 return;
667 }
668 result.file_reference = std::move(file_reference);
669
670 DCHECK_LT(0u, pending_evictions_);
671 pending_evictions_--;
672
673 // Switch item from memory to the new file.
674 uint64_t offset = 0;
675 for (const scoped_refptr<ShareableBlobDataItem>& shareable_item : items) {
676 scoped_refptr<BlobDataItem> new_item(new BlobDataItem(
677 base::WrapUnique(new DataElement()), result.file_reference));
678 new_item->data_element_ptr()->SetToFilePathRange(
679 result.file_reference->path(), offset, shareable_item->item()->length(),
680 result.last_modified);
681 shareable_item->set_item(new_item);
682 items_saving_to_disk_.erase(shareable_item->item_id());
683 offset += shareable_item->item()->length();
684 }
685 in_flight_memory_used_ -= total_items_size;
686
687 // We want callback on blobs up to the amount we've freed.
688 MaybeGrantPendingQuotaRequests();
689
690 // If we still have more blobs waiting and we're not waiting on more paging
691 // operations, schedule more.
692 MaybeScheduleEvictionUntilSystemHealthy();
693 }
694
695 FilePath BlobMemoryController::GenerateNextPageFileName() {
696 std::string file_name = base::Uint64ToString(current_file_num_++);
697 return blob_storage_dir_.Append(file_name);
698 }
699
700 void BlobMemoryController::RecordTracingCounters() const {
701 TRACE_COUNTER2("Blob", "MemoryUsage", "RegularStorage", blob_memory_used_,
702 "InFlightToDisk", in_flight_memory_used_);
703 TRACE_COUNTER1("Blob", "DiskUsage", disk_used_);
704 TRACE_COUNTER1("Blob", "TranfersPendingOnDisk",
705 pending_memory_quota_tasks_.size());
706 TRACE_COUNTER1("Blob", "TranfersBytesPendingOnDisk",
707 pending_memory_quota_total_size_);
708 }
709
710 size_t BlobMemoryController::GetAvailableMemoryForBlobs() const {
711 if (limits_.total_memory_space() < memory_usage())
712 return 0;
713 return limits_.total_memory_space() - memory_usage();
714 }
715
716 uint64_t BlobMemoryController::GetAvailableFileSpaceForBlobs() const {
717 if (!file_paging_enabled_)
718 return 0;
719 return limits_.max_blob_disk_space - disk_used_ -
720 pending_memory_quota_total_size_;
721 }
722
723 void BlobMemoryController::OnBlobFileDelete(uint64_t size,
724 const FilePath& path) {
725 DCHECK_LE(size, disk_used_);
726 disk_used_ -= size;
727 }
728
729 } // namespace storage
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698