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 #include <memory>
9 #include <numeric>
10
11 #include "base/bind.h"
12 #include "base/bind_helpers.h"
13 #include "base/callback.h"
14 #include "base/callback_helpers.h"
15 #include "base/command_line.h"
16 #include "base/containers/small_map.h"
17 #include "base/files/file_util.h"
18 #include "base/guid.h"
19 #include "base/location.h"
20 #include "base/memory/ptr_util.h"
21 #include "base/metrics/histogram_functions.h"
22 #include "base/metrics/histogram_macros.h"
23 #include "base/numerics/safe_conversions.h"
24 #include "base/numerics/safe_math.h"
25 #include "base/single_thread_task_runner.h"
26 #include "base/stl_util.h"
27 #include "base/strings/string_number_conversions.h"
28 #include "base/system/sys_info.h"
29 #include "base/task_runner.h"
30 #include "base/task_runner_util.h"
31 #include "base/threading/scoped_blocking_call.h"
32 #include "base/time/time.h"
33 #include "base/trace_event/trace_event.h"
34 #include "storage/browser/blob/blob_data_builder.h"
35 #include "storage/browser/blob/blob_data_item.h"
36 #include "storage/browser/blob/shareable_blob_data_item.h"
37 #include "storage/browser/blob/shareable_file_reference.h"
38
39 using base::File;
40 using base::FilePath;
41
42 namespace storage {
43 namespace {
44 constexpr int64_t kUnknownDiskAvailability = -1ll;
45 constexpr uint64_t kMegabyte = 1024ull * 1024;
46 const int64_t kMinSecondsForPressureEvictions = 30;
47
48 using FileCreationInfo = BlobMemoryController::FileCreationInfo;
49 using MemoryAllocation = BlobMemoryController::MemoryAllocation;
50 using QuotaAllocationTask = BlobMemoryController::QuotaAllocationTask;
51 using DiskSpaceFuncPtr = BlobMemoryController::DiskSpaceFuncPtr;
52
CreateBlobDirectory(const FilePath & blob_storage_dir)53 File::Error CreateBlobDirectory(const FilePath& blob_storage_dir) {
54 File::Error error = File::FILE_OK;
55 base::CreateDirectoryAndGetError(blob_storage_dir, &error);
56 UMA_HISTOGRAM_ENUMERATION("Storage.Blob.CreateDirectoryResult", -error,
57 -File::FILE_ERROR_MAX);
58 DLOG_IF(ERROR, error != File::FILE_OK)
59 << "Error creating blob storage directory: " << error;
60 return error;
61 }
62
63 // CrOS:
64 // * Ram - 20%
65 // * Disk - 50%
66 // Note: The disk is the user partition, so the operating system can still
67 // function if this is full.
68 // Android:
69 // * RAM - 1%
70 // * Disk - 6%
71 // Desktop:
72 // * Ram - 20%, or 2 GB if x64.
73 // * Disk - 10%
CalculateBlobStorageLimitsImpl(const FilePath & storage_dir,bool disk_enabled,base::Optional<int64_t> optional_memory_size_for_testing)74 BlobStorageLimits CalculateBlobStorageLimitsImpl(
75 const FilePath& storage_dir,
76 bool disk_enabled,
77 base::Optional<int64_t> optional_memory_size_for_testing) {
78 int64_t disk_size = 0ull;
79 int64_t memory_size = optional_memory_size_for_testing
80 ? optional_memory_size_for_testing.value()
81 : base::SysInfo::AmountOfPhysicalMemory();
82 if (disk_enabled && CreateBlobDirectory(storage_dir) == base::File::FILE_OK)
83 disk_size = base::SysInfo::AmountOfTotalDiskSpace(storage_dir);
84
85 BlobStorageLimits limits;
86
87 // Don't do specialty configuration for error size (-1).
88 if (memory_size > 0) {
89 #if !defined(OS_CHROMEOS) && !defined(OS_ANDROID) && defined(ARCH_CPU_64_BITS)
90 constexpr size_t kTwoGigabytes = 2ull * 1024 * 1024 * 1024;
91 limits.max_blob_in_memory_space = kTwoGigabytes;
92 #elif defined(OS_ANDROID)
93 limits.max_blob_in_memory_space = static_cast<size_t>(memory_size / 100ll);
94 #else
95 limits.max_blob_in_memory_space = static_cast<size_t>(memory_size / 5ll);
96 #endif
97 }
98 // Devices just on the edge (RAM == 256MB) should not fail because
99 // max_blob_in_memory_space turns out smaller than min_page_file_size
100 // causing the CHECK below to fail.
101 if (limits.max_blob_in_memory_space < limits.min_page_file_size)
102 limits.max_blob_in_memory_space = limits.min_page_file_size;
103
104 // Don't do specialty configuration for error size (-1). Allow no disk.
105 if (disk_size >= 0) {
106 #if defined(OS_CHROMEOS)
107 limits.desired_max_disk_space = static_cast<uint64_t>(disk_size / 2ll);
108 #elif defined(OS_ANDROID)
109 limits.desired_max_disk_space = static_cast<uint64_t>(3ll * disk_size / 50);
110 #else
111 limits.desired_max_disk_space = static_cast<uint64_t>(disk_size / 10ll);
112 #endif
113 }
114 if (disk_enabled) {
115 UMA_HISTOGRAM_COUNTS_1M("Storage.Blob.MaxDiskSpace2",
116 limits.desired_max_disk_space / kMegabyte);
117 }
118 limits.effective_max_disk_space = limits.desired_max_disk_space;
119
120 CHECK(limits.IsValid());
121
122 return limits;
123 }
124
DestructFile(File infos_without_references)125 void DestructFile(File infos_without_references) {}
126
DeleteFiles(std::vector<FileCreationInfo> files)127 void DeleteFiles(std::vector<FileCreationInfo> files) {
128 for (FileCreationInfo& file_info : files) {
129 file_info.file.Close();
130 base::DeleteFile(file_info.path, false);
131 }
132 }
133
134 struct EmptyFilesResult {
135 EmptyFilesResult() = default;
EmptyFilesResultstorage::__anonca7d78f30111::EmptyFilesResult136 EmptyFilesResult(std::vector<FileCreationInfo> files,
137 File::Error file_error,
138 int64_t disk_availability)
139 : files(std::move(files)),
140 file_error(file_error),
141 disk_availability(disk_availability) {}
142 ~EmptyFilesResult() = default;
143 EmptyFilesResult(EmptyFilesResult&& o) = default;
144 EmptyFilesResult& operator=(EmptyFilesResult&& other) = default;
145
146 std::vector<FileCreationInfo> files;
147 File::Error file_error = File::FILE_ERROR_FAILED;
148 int64_t disk_availability = 0;
149 };
150
151 // Used for new unpopulated file items. Caller must populate file reference in
152 // returned FileCreationInfos. Also returns the currently available disk space
153 // (without the future size of these files).
CreateEmptyFiles(const FilePath & blob_storage_dir,DiskSpaceFuncPtr disk_space_function,scoped_refptr<base::TaskRunner> file_task_runner,std::vector<base::FilePath> file_paths)154 EmptyFilesResult CreateEmptyFiles(
155 const FilePath& blob_storage_dir,
156 DiskSpaceFuncPtr disk_space_function,
157 scoped_refptr<base::TaskRunner> file_task_runner,
158 std::vector<base::FilePath> file_paths) {
159 base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
160 base::BlockingType::MAY_BLOCK);
161
162 File::Error dir_create_status = CreateBlobDirectory(blob_storage_dir);
163 if (dir_create_status != File::FILE_OK) {
164 return EmptyFilesResult(std::vector<FileCreationInfo>(), dir_create_status,
165 kUnknownDiskAvailability);
166 }
167
168 int64_t free_disk_space = disk_space_function(blob_storage_dir);
169
170 std::vector<FileCreationInfo> result;
171 for (const base::FilePath& file_path : file_paths) {
172 FileCreationInfo creation_info;
173 // Try to open our file.
174 File file(file_path, File::FLAG_CREATE_ALWAYS | File::FLAG_WRITE);
175 creation_info.path = std::move(file_path);
176 creation_info.file_deletion_runner = file_task_runner;
177 creation_info.error = file.error_details();
178 if (creation_info.error != File::FILE_OK) {
179 return EmptyFilesResult(std::vector<FileCreationInfo>(),
180 creation_info.error, free_disk_space);
181 }
182 creation_info.file = std::move(file);
183
184 result.push_back(std::move(creation_info));
185 }
186 return EmptyFilesResult(std::move(result), File::FILE_OK, free_disk_space);
187 }
188
189 // Used to evict multiple memory items out to a single file. Caller must
190 // populate file reference in returned FileCreationInfo. Also returns the free
191 // disk space AFTER creating this file.
CreateFileAndWriteItems(const FilePath & blob_storage_dir,DiskSpaceFuncPtr disk_space_function,const FilePath & file_path,scoped_refptr<base::TaskRunner> file_task_runner,std::vector<base::span<const uint8_t>> data,size_t total_size_bytes)192 std::pair<FileCreationInfo, int64_t> CreateFileAndWriteItems(
193 const FilePath& blob_storage_dir,
194 DiskSpaceFuncPtr disk_space_function,
195 const FilePath& file_path,
196 scoped_refptr<base::TaskRunner> file_task_runner,
197 std::vector<base::span<const uint8_t>> data,
198 size_t total_size_bytes) {
199 DCHECK_NE(0u, total_size_bytes);
200 UMA_HISTOGRAM_MEMORY_KB("Storage.Blob.PageFileSize", total_size_bytes / 1024);
201 base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
202 base::BlockingType::MAY_BLOCK);
203
204 FileCreationInfo creation_info;
205 creation_info.file_deletion_runner = std::move(file_task_runner);
206 creation_info.error = CreateBlobDirectory(blob_storage_dir);
207 if (creation_info.error != File::FILE_OK)
208 return std::make_pair(std::move(creation_info), kUnknownDiskAvailability);
209
210 int64_t free_disk_space = disk_space_function(blob_storage_dir);
211
212 // Fail early instead of creating the files if we fill the disk.
213 if (free_disk_space != kUnknownDiskAvailability &&
214 free_disk_space < static_cast<int64_t>(total_size_bytes)) {
215 creation_info.error = File::FILE_ERROR_NO_SPACE;
216 return std::make_pair(std::move(creation_info), free_disk_space);
217 }
218 int64_t disk_availability =
219 free_disk_space == kUnknownDiskAvailability
220 ? kUnknownDiskAvailability
221 : free_disk_space - static_cast<int64_t>(total_size_bytes);
222
223 // Create the page file.
224 File file(file_path, File::FLAG_CREATE_ALWAYS | File::FLAG_WRITE);
225 creation_info.path = file_path;
226 creation_info.error = file.error_details();
227 if (creation_info.error != File::FILE_OK)
228 return std::make_pair(std::move(creation_info), free_disk_space);
229
230 // Write data.
231 file.SetLength(total_size_bytes);
232 int bytes_written = 0;
233 for (const auto& item : data) {
234 size_t length = item.size();
235 size_t bytes_left = length;
236 while (bytes_left > 0) {
237 bytes_written = file.WriteAtCurrentPos(
238 reinterpret_cast<const char*>(item.data() + (length - bytes_left)),
239 base::saturated_cast<int>(bytes_left));
240 if (bytes_written < 0)
241 break;
242 DCHECK_LE(static_cast<size_t>(bytes_written), bytes_left);
243 bytes_left -= bytes_written;
244 }
245 if (bytes_written < 0)
246 break;
247 }
248 if (!file.Flush()) {
249 file.Close();
250 base::DeleteFile(file_path, false);
251 creation_info.error = File::FILE_ERROR_FAILED;
252 return std::make_pair(std::move(creation_info), free_disk_space);
253 }
254
255 File::Info info;
256 bool success = file.GetInfo(&info);
257 creation_info.error =
258 bytes_written < 0 || !success ? File::FILE_ERROR_FAILED : File::FILE_OK;
259 creation_info.last_modified = info.last_modified;
260 return std::make_pair(std::move(creation_info), disk_availability);
261 }
262
GetTotalSizeAndFileSizes(const std::vector<scoped_refptr<ShareableBlobDataItem>> & unreserved_file_items,std::vector<uint64_t> * file_sizes_output)263 uint64_t GetTotalSizeAndFileSizes(
264 const std::vector<scoped_refptr<ShareableBlobDataItem>>&
265 unreserved_file_items,
266 std::vector<uint64_t>* file_sizes_output) {
267 uint64_t total_size_output = 0;
268 base::small_map<std::map<uint64_t, uint64_t>> file_id_to_sizes;
269 for (const auto& item : unreserved_file_items) {
270 uint64_t file_id = item->item()->GetFutureFileID();
271 auto it = file_id_to_sizes.find(file_id);
272 if (it != file_id_to_sizes.end())
273 it->second =
274 std::max(it->second, item->item()->offset() + item->item()->length());
275 else
276 file_id_to_sizes[file_id] =
277 item->item()->offset() + item->item()->length();
278 total_size_output += item->item()->length();
279 }
280 for (const auto& size_pair : file_id_to_sizes) {
281 file_sizes_output->push_back(size_pair.second);
282 }
283 DCHECK_EQ(std::accumulate(file_sizes_output->begin(),
284 file_sizes_output->end(), 0ull),
285 total_size_output)
286 << "Illegal builder configuration, temporary files must be totally used.";
287 return total_size_output;
288 }
289
290 } // namespace
291
292 FileCreationInfo::FileCreationInfo() = default;
~FileCreationInfo()293 FileCreationInfo::~FileCreationInfo() {
294 if (file.IsValid()) {
295 DCHECK(file_deletion_runner);
296 file_deletion_runner->PostTask(
297 FROM_HERE, base::BindOnce(&DestructFile, std::move(file)));
298 }
299 }
300 FileCreationInfo::FileCreationInfo(FileCreationInfo&&) = default;
301 FileCreationInfo& FileCreationInfo::operator=(FileCreationInfo&&) = default;
302
MemoryAllocation(base::WeakPtr<BlobMemoryController> controller,uint64_t item_id,size_t length)303 MemoryAllocation::MemoryAllocation(
304 base::WeakPtr<BlobMemoryController> controller,
305 uint64_t item_id,
306 size_t length)
307 : controller_(std::move(controller)), item_id_(item_id), length_(length) {}
308
~MemoryAllocation()309 MemoryAllocation::~MemoryAllocation() {
310 if (controller_)
311 controller_->RevokeMemoryAllocation(item_id_, length_);
312 }
313
314 BlobMemoryController::QuotaAllocationTask::~QuotaAllocationTask() = default;
315
316 class BlobMemoryController::MemoryQuotaAllocationTask
317 : public BlobMemoryController::QuotaAllocationTask {
318 public:
MemoryQuotaAllocationTask(BlobMemoryController * controller,size_t quota_request_size,std::vector<scoped_refptr<ShareableBlobDataItem>> pending_items,MemoryQuotaRequestCallback done_callback)319 MemoryQuotaAllocationTask(
320 BlobMemoryController* controller,
321 size_t quota_request_size,
322 std::vector<scoped_refptr<ShareableBlobDataItem>> pending_items,
323 MemoryQuotaRequestCallback done_callback)
324 : controller_(controller),
325 pending_items_(std::move(pending_items)),
326 done_callback_(std::move(done_callback)),
327 allocation_size_(quota_request_size) {}
328
329 ~MemoryQuotaAllocationTask() override = default;
330
RunDoneCallback(bool success)331 void RunDoneCallback(bool success) {
332 // Make sure we clear the weak pointers we gave to the caller beforehand.
333 weak_factory_.InvalidateWeakPtrs();
334 if (success)
335 controller_->GrantMemoryAllocations(&pending_items_, allocation_size_);
336 std::move(done_callback_).Run(success);
337 }
338
GetWeakPtr()339 base::WeakPtr<QuotaAllocationTask> GetWeakPtr() {
340 return weak_factory_.GetWeakPtr();
341 }
342
Cancel()343 void Cancel() override {
344 DCHECK_GE(controller_->pending_memory_quota_total_size_, allocation_size_);
345 controller_->pending_memory_quota_total_size_ -= allocation_size_;
346 // This call destroys this object.
347 controller_->pending_memory_quota_tasks_.erase(my_list_position_);
348 }
349
350 // The my_list_position_ iterator is stored so that we can remove ourself
351 // from the task list when we are cancelled.
set_my_list_position(PendingMemoryQuotaTaskList::iterator my_list_position)352 void set_my_list_position(
353 PendingMemoryQuotaTaskList::iterator my_list_position) {
354 my_list_position_ = my_list_position;
355 }
356
allocation_size() const357 size_t allocation_size() const { return allocation_size_; }
358
359 private:
360 BlobMemoryController* controller_;
361 std::vector<scoped_refptr<ShareableBlobDataItem>> pending_items_;
362 MemoryQuotaRequestCallback done_callback_;
363
364 size_t allocation_size_;
365 PendingMemoryQuotaTaskList::iterator my_list_position_;
366
367 base::WeakPtrFactory<MemoryQuotaAllocationTask> weak_factory_{this};
368 DISALLOW_COPY_AND_ASSIGN(MemoryQuotaAllocationTask);
369 };
370
371 class BlobMemoryController::FileQuotaAllocationTask
372 : public BlobMemoryController::QuotaAllocationTask {
373 public:
374 // We post a task to create the file for the items right away.
FileQuotaAllocationTask(BlobMemoryController * memory_controller,DiskSpaceFuncPtr disk_space_function,std::vector<scoped_refptr<ShareableBlobDataItem>> unreserved_file_items,FileQuotaRequestCallback done_callback)375 FileQuotaAllocationTask(
376 BlobMemoryController* memory_controller,
377 DiskSpaceFuncPtr disk_space_function,
378 std::vector<scoped_refptr<ShareableBlobDataItem>> unreserved_file_items,
379 FileQuotaRequestCallback done_callback)
380 : controller_(memory_controller),
381 done_callback_(std::move(done_callback)) {
382 // Get the file sizes and total size.
383 uint64_t total_size =
384 GetTotalSizeAndFileSizes(unreserved_file_items, &file_sizes_);
385
386 // When we do perf tests that force the file strategy, these often run
387 // before |CalculateBlobStorageLimitsImpl| is complete. The disk isn't
388 // enabled until after this call returns (|file_paging_enabled_| is false)
389 // and |GetAvailableFileSpaceForBlobs()| will thus return 0. So skip this
390 // check when we have a custom file transportation trigger.
391 #if DCHECK_IS_ON()
392 base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
393 if (LIKELY(
394 !command_line->HasSwitch(kBlobFileTransportByFileTriggerSwitch))) {
395 DCHECK_LE(total_size, controller_->GetAvailableFileSpaceForBlobs());
396 }
397 #endif
398 allocation_size_ = total_size;
399
400 // Check & set our item states.
401 for (auto& shareable_item : unreserved_file_items) {
402 DCHECK_EQ(ShareableBlobDataItem::QUOTA_NEEDED, shareable_item->state());
403 DCHECK_EQ(BlobDataItem::Type::kFile, shareable_item->item()->type());
404 shareable_item->set_state(ShareableBlobDataItem::QUOTA_REQUESTED);
405 }
406 pending_items_ = std::move(unreserved_file_items);
407
408 // Increment disk usage and create our file references.
409 controller_->disk_used_ += allocation_size_;
410 std::vector<base::FilePath> file_paths;
411 std::vector<scoped_refptr<ShareableFileReference>> references;
412 for (size_t i = 0; i < file_sizes_.size(); i++) {
413 file_paths.push_back(controller_->GenerateNextPageFileName());
414 references.push_back(ShareableFileReference::GetOrCreate(
415 file_paths.back(), ShareableFileReference::DELETE_ON_FINAL_RELEASE,
416 controller_->file_runner_.get()));
417 }
418 // Send file creation task to file thread.
419 base::PostTaskAndReplyWithResult(
420 controller_->file_runner_.get(), FROM_HERE,
421 base::BindOnce(&CreateEmptyFiles, controller_->blob_storage_dir_,
422 disk_space_function, controller_->file_runner_,
423 std::move(file_paths)),
424 base::BindOnce(&FileQuotaAllocationTask::OnCreateEmptyFiles,
425 weak_factory_.GetWeakPtr(), std::move(references),
426 allocation_size_));
427 controller_->RecordTracingCounters();
428 }
429 ~FileQuotaAllocationTask() override = default;
430
RunDoneCallback(std::vector<FileCreationInfo> file_info,bool success)431 void RunDoneCallback(std::vector<FileCreationInfo> file_info, bool success) {
432 // Make sure we clear the weak pointers we gave to the caller beforehand.
433 weak_factory_.InvalidateWeakPtrs();
434
435 // We want to destroy this object on the exit of this method if we were
436 // successful.
437 std::unique_ptr<FileQuotaAllocationTask> this_object;
438 if (success) {
439 // Register the disk space accounting callback.
440 DCHECK_EQ(file_info.size(), file_sizes_.size());
441 for (size_t i = 0; i < file_sizes_.size(); i++) {
442 file_info[i].file_reference->AddFinalReleaseCallback(base::BindOnce(
443 &BlobMemoryController::OnBlobFileDelete,
444 controller_->weak_factory_.GetWeakPtr(), file_sizes_[i]));
445 }
446 for (auto& item : pending_items_) {
447 item->set_state(ShareableBlobDataItem::QUOTA_GRANTED);
448 }
449 this_object = std::move(*my_list_position_);
450 controller_->pending_file_quota_tasks_.erase(my_list_position_);
451 }
452
453 std::move(done_callback_).Run(std::move(file_info), success);
454 }
455
GetWeakPtr()456 base::WeakPtr<QuotaAllocationTask> GetWeakPtr() {
457 return weak_factory_.GetWeakPtr();
458 }
459
Cancel()460 void Cancel() override {
461 DCHECK_GE(controller_->disk_used_, allocation_size_);
462 controller_->disk_used_ -= allocation_size_;
463 // This call destroys this object.
464 controller_->pending_file_quota_tasks_.erase(my_list_position_);
465 }
466
OnCreateEmptyFiles(std::vector<scoped_refptr<ShareableFileReference>> references,uint64_t new_files_total_size,EmptyFilesResult result)467 void OnCreateEmptyFiles(
468 std::vector<scoped_refptr<ShareableFileReference>> references,
469 uint64_t new_files_total_size,
470 EmptyFilesResult result) {
471 int64_t avail_disk_space = result.disk_availability;
472 if (result.files.empty()) {
473 DCHECK_NE(result.file_error, File::FILE_OK);
474 DCHECK_GE(controller_->disk_used_, allocation_size_);
475 controller_->disk_used_ -= allocation_size_;
476 // This will call our callback and delete the object correctly.
477 controller_->DisableFilePaging(result.file_error);
478 return;
479 }
480 // The allocation won't fit at all. Cancel this request. The disk will be
481 // decremented when the file is deleted through AddFinalReleaseCallback.
482 if (avail_disk_space != kUnknownDiskAvailability &&
483 base::checked_cast<uint64_t>(avail_disk_space) < new_files_total_size) {
484 DCHECK_GE(controller_->disk_used_, allocation_size_);
485 controller_->disk_used_ -= allocation_size_;
486 controller_->AdjustDiskUsage(static_cast<uint64_t>(avail_disk_space));
487 controller_->file_runner_->PostTask(
488 FROM_HERE, base::BindOnce(&DeleteFiles, std::move(result.files)));
489 std::unique_ptr<FileQuotaAllocationTask> this_object =
490 std::move(*my_list_position_);
491 controller_->pending_file_quota_tasks_.erase(my_list_position_);
492 RunDoneCallback(std::vector<FileCreationInfo>(), false);
493 return;
494 }
495 if (avail_disk_space != kUnknownDiskAvailability) {
496 controller_->AdjustDiskUsage(base::checked_cast<uint64_t>(
497 avail_disk_space - new_files_total_size));
498 }
499 DCHECK_EQ(result.files.size(), references.size());
500 for (size_t i = 0; i < result.files.size(); i++) {
501 result.files[i].file_reference = std::move(references[i]);
502 }
503 RunDoneCallback(std::move(result.files), true);
504 }
505
506 // The my_list_position_ iterator is stored so that we can remove ourself
507 // from the task list when we are cancelled.
set_my_list_position(PendingFileQuotaTaskList::iterator my_list_position)508 void set_my_list_position(
509 PendingFileQuotaTaskList::iterator my_list_position) {
510 my_list_position_ = my_list_position;
511 }
512
allocation_size() const513 size_t allocation_size() const { return allocation_size_; }
514
515 private:
516 BlobMemoryController* controller_;
517 std::vector<uint64_t> file_sizes_;
518 std::vector<scoped_refptr<ShareableBlobDataItem>> pending_items_;
519 FileQuotaRequestCallback done_callback_;
520
521 uint64_t allocation_size_;
522 PendingFileQuotaTaskList::iterator my_list_position_;
523
524 base::WeakPtrFactory<FileQuotaAllocationTask> weak_factory_{this};
525 DISALLOW_COPY_AND_ASSIGN(FileQuotaAllocationTask);
526 };
527
BlobMemoryController(const base::FilePath & storage_directory,scoped_refptr<base::TaskRunner> file_runner)528 BlobMemoryController::BlobMemoryController(
529 const base::FilePath& storage_directory,
530 scoped_refptr<base::TaskRunner> file_runner)
531 : file_paging_enabled_(file_runner.get() != nullptr),
532 blob_storage_dir_(storage_directory),
533 file_runner_(std::move(file_runner)),
534 disk_space_function_(&base::SysInfo::AmountOfFreeDiskSpace),
535 populated_memory_items_(
536 base::MRUCache<uint64_t, ShareableBlobDataItem*>::NO_AUTO_EVICT),
537 memory_pressure_listener_(
538 base::BindRepeating(&BlobMemoryController::OnMemoryPressure,
539 base::Unretained(this))) {}
540
541 BlobMemoryController::~BlobMemoryController() = default;
542
DisableFilePaging(base::File::Error reason)543 void BlobMemoryController::DisableFilePaging(base::File::Error reason) {
544 UMA_HISTOGRAM_ENUMERATION("Storage.Blob.PagingDisabled", -reason,
545 -File::FILE_ERROR_MAX);
546 DLOG(ERROR) << "Blob storage paging disabled, reason: " << reason;
547 file_paging_enabled_ = false;
548 in_flight_memory_used_ = 0;
549 items_paging_to_file_.clear();
550 pending_evictions_ = 0;
551 pending_memory_quota_total_size_ = 0;
552 populated_memory_items_.Clear();
553 populated_memory_items_bytes_ = 0;
554 file_runner_ = nullptr;
555
556 PendingMemoryQuotaTaskList old_memory_tasks;
557 PendingFileQuotaTaskList old_file_tasks;
558 std::swap(old_memory_tasks, pending_memory_quota_tasks_);
559 std::swap(old_file_tasks, pending_file_quota_tasks_);
560
561 // Don't call the callbacks until we have a consistent state.
562 for (auto& memory_request : old_memory_tasks) {
563 memory_request->RunDoneCallback(false);
564 }
565 for (auto& file_request : old_file_tasks) {
566 // OnBlobFileDelete is registered when RunDoneCallback is called with
567 // |true|, so manually do disk accounting.
568 disk_used_ -= file_request->allocation_size();
569 file_request->RunDoneCallback(std::vector<FileCreationInfo>(), false);
570 }
571 }
572
DetermineStrategy(size_t preemptive_transported_bytes,uint64_t total_transportation_bytes) const573 BlobMemoryController::Strategy BlobMemoryController::DetermineStrategy(
574 size_t preemptive_transported_bytes,
575 uint64_t total_transportation_bytes) const {
576 if (total_transportation_bytes == 0)
577 return Strategy::NONE_NEEDED;
578 if (!CanReserveQuota(total_transportation_bytes))
579 return Strategy::TOO_LARGE;
580
581 // Handle the case where we have all the bytes preemptively transported, and
582 // we can also fit them.
583 if (preemptive_transported_bytes == total_transportation_bytes &&
584 pending_memory_quota_tasks_.empty() &&
585 preemptive_transported_bytes <= GetAvailableMemoryForBlobs()) {
586 return Strategy::NONE_NEEDED;
587 }
588
589 if (UNLIKELY(limits_.override_file_transport_min_size > 0) &&
590 file_paging_enabled_ &&
591 total_transportation_bytes >= limits_.override_file_transport_min_size) {
592 return Strategy::FILE;
593 }
594
595 if (total_transportation_bytes <= limits_.max_ipc_memory_size)
596 return Strategy::IPC;
597
598 if (file_paging_enabled_ &&
599 total_transportation_bytes <= GetAvailableFileSpaceForBlobs() &&
600 total_transportation_bytes > limits_.memory_limit_before_paging()) {
601 return Strategy::FILE;
602 }
603 return Strategy::SHARED_MEMORY;
604 }
605
CanReserveQuota(uint64_t size) const606 bool BlobMemoryController::CanReserveQuota(uint64_t size) const {
607 // We check each size independently as a blob can't be constructed in both
608 // disk and memory.
609 return size <= GetAvailableMemoryForBlobs() ||
610 size <= GetAvailableFileSpaceForBlobs();
611 }
612
ReserveMemoryQuota(std::vector<scoped_refptr<ShareableBlobDataItem>> unreserved_memory_items,MemoryQuotaRequestCallback done_callback)613 base::WeakPtr<QuotaAllocationTask> BlobMemoryController::ReserveMemoryQuota(
614 std::vector<scoped_refptr<ShareableBlobDataItem>> unreserved_memory_items,
615 MemoryQuotaRequestCallback done_callback) {
616 if (unreserved_memory_items.empty()) {
617 std::move(done_callback).Run(true);
618 return base::WeakPtr<QuotaAllocationTask>();
619 }
620
621 base::CheckedNumeric<uint64_t> unsafe_total_bytes_needed = 0;
622 for (auto& item : unreserved_memory_items) {
623 DCHECK_EQ(ShareableBlobDataItem::QUOTA_NEEDED, item->state());
624 DCHECK(item->item()->type() == BlobDataItem::Type::kBytesDescription ||
625 item->item()->type() == BlobDataItem::Type::kBytes);
626 DCHECK(item->item()->length() > 0);
627 unsafe_total_bytes_needed += item->item()->length();
628 item->set_state(ShareableBlobDataItem::QUOTA_REQUESTED);
629 }
630
631 uint64_t total_bytes_needed = unsafe_total_bytes_needed.ValueOrDie();
632 DCHECK_GT(total_bytes_needed, 0ull);
633
634 // If we're currently waiting for blobs to page already, then we add
635 // ourselves to the end of the queue. Once paging is complete, we'll schedule
636 // more paging for any more pending blobs.
637 if (!pending_memory_quota_tasks_.empty()) {
638 return AppendMemoryTask(total_bytes_needed,
639 std::move(unreserved_memory_items),
640 std::move(done_callback));
641 }
642
643 // Store right away if we can.
644 if (total_bytes_needed <= GetAvailableMemoryForBlobs()) {
645 GrantMemoryAllocations(&unreserved_memory_items,
646 static_cast<size_t>(total_bytes_needed));
647 MaybeScheduleEvictionUntilSystemHealthy(
648 base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE);
649 std::move(done_callback).Run(true);
650 return base::WeakPtr<QuotaAllocationTask>();
651 }
652
653 // Size is larger than available memory.
654 DCHECK(pending_memory_quota_tasks_.empty());
655 DCHECK_EQ(0u, pending_memory_quota_total_size_);
656
657 auto weak_ptr =
658 AppendMemoryTask(total_bytes_needed, std::move(unreserved_memory_items),
659 std::move(done_callback));
660 MaybeScheduleEvictionUntilSystemHealthy(
661 base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE);
662 return weak_ptr;
663 }
664
ReserveFileQuota(std::vector<scoped_refptr<ShareableBlobDataItem>> unreserved_file_items,FileQuotaRequestCallback done_callback)665 base::WeakPtr<QuotaAllocationTask> BlobMemoryController::ReserveFileQuota(
666 std::vector<scoped_refptr<ShareableBlobDataItem>> unreserved_file_items,
667 FileQuotaRequestCallback done_callback) {
668 pending_file_quota_tasks_.push_back(std::make_unique<FileQuotaAllocationTask>(
669 this, disk_space_function_, std::move(unreserved_file_items),
670 std::move(done_callback)));
671 pending_file_quota_tasks_.back()->set_my_list_position(
672 --pending_file_quota_tasks_.end());
673 return pending_file_quota_tasks_.back()->GetWeakPtr();
674 }
675
ShrinkMemoryAllocation(ShareableBlobDataItem * item)676 void BlobMemoryController::ShrinkMemoryAllocation(ShareableBlobDataItem* item) {
677 DCHECK(item->HasGrantedQuota());
678 DCHECK_EQ(item->item()->type(), BlobDataItem::Type::kBytes);
679 DCHECK_GE(item->memory_allocation_->length(), item->item()->length());
680 DCHECK_EQ(item->memory_allocation_->controller_.get(), this);
681
682 // Setting a new MemoryAllocation will delete and free the existing memory
683 // allocation, so here we only have to account for the new allocation.
684 blob_memory_used_ += item->item()->length();
685 item->set_memory_allocation(std::make_unique<MemoryAllocation>(
686 weak_factory_.GetWeakPtr(), item->item_id(),
687 base::checked_cast<size_t>(item->item()->length())));
688 MaybeGrantPendingMemoryRequests();
689 }
690
ShrinkFileAllocation(ShareableFileReference * file_reference,uint64_t old_length,uint64_t new_length)691 void BlobMemoryController::ShrinkFileAllocation(
692 ShareableFileReference* file_reference,
693 uint64_t old_length,
694 uint64_t new_length) {
695 DCHECK_GE(old_length, new_length);
696
697 DCHECK_GE(disk_used_, old_length - new_length);
698 disk_used_ -= old_length - new_length;
699 file_reference->AddFinalReleaseCallback(
700 base::BindOnce(&BlobMemoryController::OnShrunkenBlobFileDelete,
701 weak_factory_.GetWeakPtr(), old_length - new_length));
702 }
703
GrowFileAllocation(ShareableFileReference * file_reference,uint64_t delta)704 void BlobMemoryController::GrowFileAllocation(
705 ShareableFileReference* file_reference,
706 uint64_t delta) {
707 DCHECK_LE(delta, GetAvailableFileSpaceForBlobs());
708 disk_used_ += delta;
709 file_reference->AddFinalReleaseCallback(
710 base::BindOnce(&BlobMemoryController::OnBlobFileDelete,
711 weak_factory_.GetWeakPtr(), delta));
712 }
713
NotifyMemoryItemsUsed(const std::vector<scoped_refptr<ShareableBlobDataItem>> & items)714 void BlobMemoryController::NotifyMemoryItemsUsed(
715 const std::vector<scoped_refptr<ShareableBlobDataItem>>& items) {
716 for (const auto& item : items) {
717 if (item->item()->type() != BlobDataItem::Type::kBytes ||
718 item->state() != ShareableBlobDataItem::POPULATED_WITH_QUOTA) {
719 continue;
720 }
721 // We don't want to re-add the item if we're currently paging it to disk.
722 if (items_paging_to_file_.find(item->item_id()) !=
723 items_paging_to_file_.end()) {
724 return;
725 }
726 auto iterator = populated_memory_items_.Get(item->item_id());
727 if (iterator == populated_memory_items_.end()) {
728 populated_memory_items_bytes_ +=
729 static_cast<size_t>(item->item()->length());
730 populated_memory_items_.Put(item->item_id(), item.get());
731 }
732 }
733 MaybeScheduleEvictionUntilSystemHealthy(
734 base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE);
735 }
736
CallWhenStorageLimitsAreKnown(base::OnceClosure callback)737 void BlobMemoryController::CallWhenStorageLimitsAreKnown(
738 base::OnceClosure callback) {
739 if (did_calculate_storage_limits_) {
740 std::move(callback).Run();
741 return;
742 }
743 on_calculate_limits_callbacks_.push_back(std::move(callback));
744 CalculateBlobStorageLimits();
745 }
746
CalculateBlobStorageLimits()747 void BlobMemoryController::CalculateBlobStorageLimits() {
748 if (did_schedule_limit_calculation_)
749 return;
750 did_schedule_limit_calculation_ = true;
751 if (file_runner_) {
752 PostTaskAndReplyWithResult(
753 file_runner_.get(), FROM_HERE,
754 base::BindOnce(&CalculateBlobStorageLimitsImpl, blob_storage_dir_,
755 true, amount_of_memory_for_testing_),
756 base::BindOnce(&BlobMemoryController::OnStorageLimitsCalculated,
757 weak_factory_.GetWeakPtr()));
758 } else {
759 OnStorageLimitsCalculated(CalculateBlobStorageLimitsImpl(
760 blob_storage_dir_, false, amount_of_memory_for_testing_));
761 }
762 }
763
GetWeakPtr()764 base::WeakPtr<BlobMemoryController> BlobMemoryController::GetWeakPtr() {
765 return weak_factory_.GetWeakPtr();
766 }
767
OnStorageLimitsCalculated(BlobStorageLimits limits)768 void BlobMemoryController::OnStorageLimitsCalculated(BlobStorageLimits limits) {
769 DCHECK(limits.IsValid());
770 if (manual_limits_set_)
771 return;
772 limits_ = limits;
773 did_calculate_storage_limits_ = true;
774 for (auto& callback : on_calculate_limits_callbacks_)
775 std::move(callback).Run();
776 on_calculate_limits_callbacks_.clear();
777 }
778
779 namespace {
780 // Used in UMA metrics, do not change values.
781 enum DiskSpaceAdjustmentType {
782 FREEZE_HIT_MIN_AVAILABLE = 0,
783 LOWERED_NEAR_MIN_AVAILABLE = 1,
784 RAISED_NEAR_MIN_AVAILABLE = 2,
785 RESTORED = 3,
786 MAX_ADJUSTMENT_TYPE
787 };
788
789 enum DiskSpaceAdjustmentStatus { FROZEN, ADJUSTED, NORMAL };
790 } // namespace
791
AdjustDiskUsage(uint64_t avail_disk)792 void BlobMemoryController::AdjustDiskUsage(uint64_t avail_disk) {
793 DCHECK_LE(disk_used_, limits_.desired_max_disk_space +
794 limits_.min_available_external_disk_space());
795
796 DiskSpaceAdjustmentStatus curr_status;
797 if (limits_.effective_max_disk_space == limits_.desired_max_disk_space) {
798 curr_status = NORMAL;
799 } else if (limits_.effective_max_disk_space == disk_used_) {
800 curr_status = FROZEN;
801 } else {
802 curr_status = ADJUSTED;
803 }
804 uint64_t old_effective_max_disk_space = limits_.effective_max_disk_space;
805 uint64_t avail_disk_without_blobs = avail_disk + disk_used_;
806
807 // Note: The UMA metrics here intended to record state change between frozen,
808 // adjusted, and normal states.
809
810 if (avail_disk <= limits_.min_available_external_disk_space()) {
811 limits_.effective_max_disk_space = disk_used_;
812 if (curr_status != FROZEN &&
813 limits_.effective_max_disk_space != old_effective_max_disk_space) {
814 UMA_HISTOGRAM_ENUMERATION("Storage.Blob.MaxDiskSpaceAdjustment",
815 FREEZE_HIT_MIN_AVAILABLE, MAX_ADJUSTMENT_TYPE);
816 }
817 } else if (avail_disk_without_blobs <
818 limits_.min_available_external_disk_space() +
819 limits_.desired_max_disk_space) {
820 // |effective_max_disk_space| is guaranteed to be less than
821 // |desired_max_disk_space| by the if statement.
822 limits_.effective_max_disk_space =
823 avail_disk_without_blobs - limits_.min_available_external_disk_space();
824 if (curr_status != ADJUSTED &&
825 limits_.effective_max_disk_space != old_effective_max_disk_space) {
826 UMA_HISTOGRAM_ENUMERATION("Storage.Blob.MaxDiskSpaceAdjustment",
827 curr_status == NORMAL
828 ? LOWERED_NEAR_MIN_AVAILABLE
829 : RAISED_NEAR_MIN_AVAILABLE,
830 MAX_ADJUSTMENT_TYPE);
831 }
832 } else {
833 limits_.effective_max_disk_space = limits_.desired_max_disk_space;
834 if (curr_status != NORMAL &&
835 limits_.effective_max_disk_space != old_effective_max_disk_space) {
836 UMA_HISTOGRAM_ENUMERATION("Storage.Blob.MaxDiskSpaceAdjustment", RESTORED,
837 MAX_ADJUSTMENT_TYPE);
838 }
839 }
840 }
841
AppendMemoryTask(uint64_t total_bytes_needed,std::vector<scoped_refptr<ShareableBlobDataItem>> unreserved_memory_items,MemoryQuotaRequestCallback done_callback)842 base::WeakPtr<QuotaAllocationTask> BlobMemoryController::AppendMemoryTask(
843 uint64_t total_bytes_needed,
844 std::vector<scoped_refptr<ShareableBlobDataItem>> unreserved_memory_items,
845 MemoryQuotaRequestCallback done_callback) {
846 DCHECK(file_paging_enabled_)
847 << "Caller tried to reserve memory when CanReserveQuota("
848 << total_bytes_needed << ") would have returned false.";
849
850 pending_memory_quota_total_size_ += total_bytes_needed;
851 pending_memory_quota_tasks_.push_back(
852 std::make_unique<MemoryQuotaAllocationTask>(
853 this, total_bytes_needed, std::move(unreserved_memory_items),
854 std::move(done_callback)));
855 pending_memory_quota_tasks_.back()->set_my_list_position(
856 --pending_memory_quota_tasks_.end());
857
858 return pending_memory_quota_tasks_.back()->GetWeakPtr();
859 }
860
MaybeGrantPendingMemoryRequests()861 void BlobMemoryController::MaybeGrantPendingMemoryRequests() {
862 while (!pending_memory_quota_tasks_.empty() &&
863 limits_.max_blob_in_memory_space - blob_memory_used_ >=
864 pending_memory_quota_tasks_.front()->allocation_size()) {
865 std::unique_ptr<MemoryQuotaAllocationTask> memory_task =
866 std::move(pending_memory_quota_tasks_.front());
867 pending_memory_quota_tasks_.pop_front();
868 pending_memory_quota_total_size_ -= memory_task->allocation_size();
869 memory_task->RunDoneCallback(true);
870 }
871 RecordTracingCounters();
872 }
873
CollectItemsForEviction(std::vector<scoped_refptr<ShareableBlobDataItem>> * output,uint64_t min_page_file_size)874 size_t BlobMemoryController::CollectItemsForEviction(
875 std::vector<scoped_refptr<ShareableBlobDataItem>>* output,
876 uint64_t min_page_file_size) {
877 base::CheckedNumeric<size_t> total_items_size = 0;
878 // Process the recent item list and remove items until we have at least a
879 // minimum file size or we're at the end of our items to page to disk.
880 while (total_items_size.ValueOrDie() < min_page_file_size &&
881 !populated_memory_items_.empty()) {
882 auto iterator = --populated_memory_items_.end();
883 ShareableBlobDataItem* item = iterator->second;
884 DCHECK_EQ(item->item()->type(), BlobDataItem::Type::kBytes);
885 populated_memory_items_.Erase(iterator);
886 size_t size = base::checked_cast<size_t>(item->item()->length());
887 populated_memory_items_bytes_ -= size;
888 total_items_size += size;
889 output->push_back(base::WrapRefCounted(item));
890 }
891 return total_items_size.ValueOrDie();
892 }
893
MaybeScheduleEvictionUntilSystemHealthy(base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level)894 void BlobMemoryController::MaybeScheduleEvictionUntilSystemHealthy(
895 base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level) {
896 // Don't do eviction when others are happening, as we don't change our
897 // pending_memory_quota_total_size_ value until after the paging files have
898 // been written.
899 if (pending_evictions_ != 0 || !file_paging_enabled_)
900 return;
901
902 uint64_t total_memory_usage =
903 static_cast<uint64_t>(pending_memory_quota_total_size_) +
904 blob_memory_used_;
905
906 size_t in_memory_limit = limits_.memory_limit_before_paging();
907 uint64_t min_page_file_size = limits_.min_page_file_size;
908 if (memory_pressure_level !=
909 base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE) {
910 in_memory_limit = 0;
911 // Use lower page file size to reduce using more memory for writing under
912 // pressure.
913 min_page_file_size = limits_.max_blob_in_memory_space *
914 limits_.max_blob_in_memory_space_under_pressure_ratio;
915 }
916
917 // We try to page items to disk until our current system size + requested
918 // memory is below our size limit.
919 // Size limit is a lower |memory_limit_before_paging()| if we have disk space.
920 while (disk_used_ < limits_.effective_max_disk_space &&
921 total_memory_usage > in_memory_limit) {
922 const char* reason = nullptr;
923 if (memory_pressure_level !=
924 base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE) {
925 reason = "OnMemoryPressure";
926 } else {
927 reason = "SizeExceededInMemoryLimit";
928 }
929
930 // We only page when we have enough items to fill a whole page file.
931 if (populated_memory_items_bytes_ < min_page_file_size)
932 break;
933 DCHECK_LE(min_page_file_size, static_cast<uint64_t>(blob_memory_used_));
934
935 std::vector<scoped_refptr<ShareableBlobDataItem>> items_to_swap;
936
937 size_t total_items_size =
938 CollectItemsForEviction(&items_to_swap, min_page_file_size);
939 if (total_items_size == 0)
940 break;
941
942 std::vector<base::span<const uint8_t>> data_for_paging;
943 for (auto& shared_blob_item : items_to_swap) {
944 items_paging_to_file_.insert(shared_blob_item->item_id());
945 data_for_paging.push_back(shared_blob_item->item()->bytes());
946 }
947
948 // Update our bookkeeping.
949 pending_evictions_++;
950 disk_used_ += total_items_size;
951 in_flight_memory_used_ += total_items_size;
952
953 // Create our file reference.
954 FilePath page_file_path = GenerateNextPageFileName();
955 scoped_refptr<ShareableFileReference> file_reference =
956 ShareableFileReference::GetOrCreate(
957 page_file_path,
958 ShareableFileReference::DELETE_ON_FINAL_RELEASE,
959 file_runner_.get());
960 // Add the release callback so we decrement our disk usage on file deletion.
961 file_reference->AddFinalReleaseCallback(
962 base::BindOnce(&BlobMemoryController::OnBlobFileDelete,
963 weak_factory_.GetWeakPtr(), total_items_size));
964
965 // Post the file writing task.
966 base::PostTaskAndReplyWithResult(
967 file_runner_.get(), FROM_HERE,
968 base::BindOnce(&CreateFileAndWriteItems, blob_storage_dir_,
969 disk_space_function_, std::move(page_file_path),
970 file_runner_, std::move(data_for_paging),
971 total_items_size),
972 base::BindOnce(&BlobMemoryController::OnEvictionComplete,
973 weak_factory_.GetWeakPtr(), std::move(file_reference),
974 std::move(items_to_swap), total_items_size, reason,
975 total_memory_usage));
976
977 last_eviction_time_ = base::TimeTicks::Now();
978 }
979 RecordTracingCounters();
980 }
981
OnEvictionComplete(scoped_refptr<ShareableFileReference> file_reference,std::vector<scoped_refptr<ShareableBlobDataItem>> items,size_t total_items_size,const char * evict_reason,size_t memory_usage_before_eviction,std::pair<FileCreationInfo,int64_t> result)982 void BlobMemoryController::OnEvictionComplete(
983 scoped_refptr<ShareableFileReference> file_reference,
984 std::vector<scoped_refptr<ShareableBlobDataItem>> items,
985 size_t total_items_size,
986 const char* evict_reason,
987 size_t memory_usage_before_eviction,
988 std::pair<FileCreationInfo, int64_t /* avail_disk */> result) {
989 if (!file_paging_enabled_)
990 return;
991
992 FileCreationInfo& file_info = std::get<0>(result);
993 int64_t avail_disk_space = std::get<1>(result);
994
995 if (file_info.error != File::FILE_OK) {
996 DisableFilePaging(file_info.error);
997 return;
998 }
999
1000 if (avail_disk_space != kUnknownDiskAvailability) {
1001 AdjustDiskUsage(static_cast<uint64_t>(avail_disk_space));
1002 }
1003
1004 DCHECK_LT(0, pending_evictions_);
1005 pending_evictions_--;
1006
1007 // Switch item from memory to the new file.
1008 uint64_t offset = 0;
1009 for (const scoped_refptr<ShareableBlobDataItem>& shareable_item : items) {
1010 scoped_refptr<BlobDataItem> new_item = BlobDataItem::CreateFile(
1011 file_reference->path(), offset, shareable_item->item()->length(),
1012 file_info.last_modified, file_reference);
1013 DCHECK(shareable_item->memory_allocation_);
1014 shareable_item->set_memory_allocation(nullptr);
1015 shareable_item->set_item(new_item);
1016 items_paging_to_file_.erase(shareable_item->item_id());
1017 offset += shareable_item->item()->length();
1018 }
1019 in_flight_memory_used_ -= total_items_size;
1020
1021 // Record change in memory usage at the last eviction reply.
1022 size_t total_usage = blob_memory_used_ + pending_memory_quota_total_size_;
1023 if (!pending_evictions_ && memory_usage_before_eviction >= total_usage) {
1024 std::string full_histogram_name =
1025 std::string("Storage.Blob.SizeEvictedToDiskInKB.") + evict_reason;
1026 base::UmaHistogramCounts100000(
1027 full_histogram_name,
1028 (memory_usage_before_eviction - total_usage) / 1024);
1029 }
1030
1031 // We want callback on blobs up to the amount we've freed.
1032 MaybeGrantPendingMemoryRequests();
1033
1034 // If we still have more blobs waiting and we're not waiting on more paging
1035 // operations, schedule more.
1036 MaybeScheduleEvictionUntilSystemHealthy(
1037 base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE);
1038 }
1039
OnMemoryPressure(base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level)1040 void BlobMemoryController::OnMemoryPressure(
1041 base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level) {
1042 // TODO(sebmarchand): Check if MEMORY_PRESSURE_LEVEL_MODERATE should also be
1043 // ignored.
1044 if (memory_pressure_level ==
1045 base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE) {
1046 return;
1047 }
1048
1049 auto time_from_last_evicion = base::TimeTicks::Now() - last_eviction_time_;
1050 if (last_eviction_time_ != base::TimeTicks() &&
1051 time_from_last_evicion.InSeconds() < kMinSecondsForPressureEvictions) {
1052 return;
1053 }
1054
1055 MaybeScheduleEvictionUntilSystemHealthy(memory_pressure_level);
1056 }
1057
GenerateNextPageFileName()1058 FilePath BlobMemoryController::GenerateNextPageFileName() {
1059 std::string file_name = base::NumberToString(current_file_num_++);
1060 return blob_storage_dir_.Append(FilePath::FromUTF8Unsafe(file_name));
1061 }
1062
RecordTracingCounters() const1063 void BlobMemoryController::RecordTracingCounters() const {
1064 TRACE_COUNTER2("Blob", "MemoryUsage", "TotalStorage", blob_memory_used_,
1065 "InFlightToDisk", in_flight_memory_used_);
1066 TRACE_COUNTER1("Blob", "DiskUsage", disk_used_);
1067 TRACE_COUNTER1("Blob", "TransfersPendingOnDisk",
1068 pending_memory_quota_tasks_.size());
1069 TRACE_COUNTER1("Blob", "TransfersBytesPendingOnDisk",
1070 pending_memory_quota_total_size_);
1071 }
1072
GetAvailableMemoryForBlobs() const1073 size_t BlobMemoryController::GetAvailableMemoryForBlobs() const {
1074 if (limits_.max_blob_in_memory_space < memory_usage())
1075 return 0;
1076 return limits_.max_blob_in_memory_space - memory_usage();
1077 }
1078
GetAvailableFileSpaceForBlobs() const1079 uint64_t BlobMemoryController::GetAvailableFileSpaceForBlobs() const {
1080 if (!file_paging_enabled_)
1081 return 0;
1082 // Sometimes we're only paging part of what we need for the new blob, so add
1083 // the rest of the size we need into our disk usage if this is the case.
1084 uint64_t total_disk_used = disk_used_;
1085 if (in_flight_memory_used_ < pending_memory_quota_total_size_) {
1086 total_disk_used +=
1087 pending_memory_quota_total_size_ - in_flight_memory_used_;
1088 }
1089 if (limits_.effective_max_disk_space < total_disk_used)
1090 return 0;
1091 return limits_.effective_max_disk_space - total_disk_used;
1092 }
1093
GrantMemoryAllocations(std::vector<scoped_refptr<ShareableBlobDataItem>> * items,size_t total_bytes)1094 void BlobMemoryController::GrantMemoryAllocations(
1095 std::vector<scoped_refptr<ShareableBlobDataItem>>* items,
1096 size_t total_bytes) {
1097 // These metrics let us calculate the global distribution of blob storage by
1098 // subtracting the histograms.
1099 UMA_HISTOGRAM_COUNTS_1M("Storage.Blob.StorageSizeBeforeAppend",
1100 blob_memory_used_ / 1024);
1101 blob_memory_used_ += total_bytes;
1102 UMA_HISTOGRAM_COUNTS_1M("Storage.Blob.StorageSizeAfterAppend",
1103 blob_memory_used_ / 1024);
1104
1105 for (auto& item : *items) {
1106 item->set_state(ShareableBlobDataItem::QUOTA_GRANTED);
1107 item->set_memory_allocation(std::make_unique<MemoryAllocation>(
1108 weak_factory_.GetWeakPtr(), item->item_id(),
1109 base::checked_cast<size_t>(item->item()->length())));
1110 }
1111 }
1112
RevokeMemoryAllocation(uint64_t item_id,size_t length)1113 void BlobMemoryController::RevokeMemoryAllocation(uint64_t item_id,
1114 size_t length) {
1115 DCHECK_LE(length, blob_memory_used_);
1116
1117 // These metrics let us calculate the global distribution of blob storage by
1118 // subtracting the histograms.
1119 UMA_HISTOGRAM_COUNTS_1M("Storage.Blob.StorageSizeBeforeAppend",
1120 blob_memory_used_ / 1024);
1121 blob_memory_used_ -= length;
1122 UMA_HISTOGRAM_COUNTS_1M("Storage.Blob.StorageSizeAfterAppend",
1123 blob_memory_used_ / 1024);
1124
1125 auto iterator = populated_memory_items_.Get(item_id);
1126 if (iterator != populated_memory_items_.end()) {
1127 DCHECK_GE(populated_memory_items_bytes_, length);
1128 populated_memory_items_bytes_ -= length;
1129 populated_memory_items_.Erase(iterator);
1130 }
1131 MaybeGrantPendingMemoryRequests();
1132 }
1133
OnBlobFileDelete(uint64_t size,const FilePath & path)1134 void BlobMemoryController::OnBlobFileDelete(uint64_t size,
1135 const FilePath& path) {
1136 DCHECK_LE(size, disk_used_);
1137 disk_used_ -= size;
1138 }
1139
OnShrunkenBlobFileDelete(uint64_t shrink_delta,const FilePath & path)1140 void BlobMemoryController::OnShrunkenBlobFileDelete(uint64_t shrink_delta,
1141 const FilePath& path) {
1142 disk_used_ += shrink_delta;
1143 }
1144
1145 } // namespace storage
1146