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

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

Issue 2055053003: [BlobAsync] Disk support for blob storage (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: comments, simplifications Created 4 years, 5 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/files/file_util.h"
12 #include "base/location.h"
13 #include "base/memory/ptr_util.h"
14 #include "base/metrics/histogram_macros.h"
15 #include "base/numerics/safe_conversions.h"
16 #include "base/numerics/safe_math.h"
17 #include "base/single_thread_task_runner.h"
18 #include "base/single_thread_task_runner.h"
19 #include "base/strings/string_number_conversions.h"
20 #include "base/task_runner.h"
21 #include "base/task_runner_util.h"
22 #include "base/threading/thread_task_runner_handle.h"
23 #include "base/time/time.h"
24 #include "base/trace_event/trace_event.h"
25 #include "base/tuple.h"
26 #include "storage/browser/blob/blob_data_item.h"
27 #include "storage/browser/blob/shareable_blob_data_item.h"
28 #include "storage/browser/blob/shareable_file_reference.h"
29
30 using base::File;
31 using base::FilePath;
32 using FileCreationInfo = storage::BlobMemoryController::FileCreationInfo;
33
34 namespace storage {
35 namespace {
36
37 bool CalculateBlobMemorySize(const std::vector<DataElement>& elements,
38 size_t* shortcut_bytes,
39 uint64_t* total_bytes) {
40 DCHECK(shortcut_bytes);
41 DCHECK(total_bytes);
42
43 base::CheckedNumeric<uint64_t> total_size_checked = 0;
44 base::CheckedNumeric<size_t> shortcut_size_checked = 0;
45 for (const auto& e : elements) {
46 if (e.type() == DataElement::TYPE_BYTES) {
47 total_size_checked += e.length();
48 shortcut_size_checked += e.length();
49 } else if (e.type() == DataElement::TYPE_BYTES_DESCRIPTION) {
50 total_size_checked += e.length();
51 } else {
52 continue;
53 }
54 if (!total_size_checked.IsValid() || !shortcut_size_checked.IsValid()) {
55 return false;
56 }
57 }
58 *shortcut_bytes = shortcut_size_checked.ValueOrDie();
59 *total_bytes = total_size_checked.ValueOrDie();
60 return true;
61 }
62
63 // Creates a file in the given directory w/ the given filename and size.
64 BlobMemoryController::FileCreationInfo CreateFile(
65 scoped_refptr<ShareableFileReference> file_reference,
66 size_t size_bytes) {
67 LOG(ERROR) << "creating file for renderer";
68 DCHECK_NE(0u, size_bytes);
69 BlobMemoryController::FileCreationInfo creation_info;
70
71 // Try to open our file.
72 File file(file_reference->path(),
73 File::FLAG_CREATE_ALWAYS | File::FLAG_WRITE);
74 creation_info.file_reference = std::move(file_reference);
75 creation_info.error = file.error_details();
76 UMA_HISTOGRAM_ENUMERATION("Storage.Blob.TransportFileCreate",
77 -creation_info.error, -File::FILE_ERROR_MAX);
78 if (creation_info.error != File::FILE_OK)
79 return creation_info;
80
81 // Grab the file info to get the "last modified" time, and create the
82 // ShareableFileReference which will manage the lifetime of the file.
83 File::Info file_info;
84 bool success = file.GetInfo(&file_info);
85 UMA_HISTOGRAM_BOOLEAN("Storage.Blob.TransportFileInfoSuccess", success);
86 creation_info.error = success ? File::FILE_OK : File::FILE_ERROR_FAILED;
87 creation_info.last_modified = file_info.last_modified;
88 if (creation_info.error == File::FILE_OK) {
89 creation_info.file = std::move(file);
90 }
91 return creation_info;
92 }
93
94 BlobMemoryController::FileCreationInfo WriteItemsToFile(
95 std::vector<scoped_refptr<ShareableBlobDataItem>>* items,
96 size_t total_size_bytes,
97 scoped_refptr<ShareableFileReference> file_reference) {
98 DCHECK_NE(0u, total_size_bytes);
99 LOG(ERROR) << "writing to file!";
100 UMA_HISTOGRAM_MEMORY_KB("Storage.Blob.PageFileSize", total_size_bytes / 1024);
101
102 // Create our file.
103 BlobMemoryController::FileCreationInfo creation_info;
104 File file(file_reference->path(),
105 File::FLAG_CREATE_ALWAYS | File::FLAG_WRITE);
106 creation_info.file_reference = std::move(file_reference);
107 creation_info.error = file.error_details();
108 UMA_HISTOGRAM_ENUMERATION("Storage.Blob.PageFileCreate", -creation_info.error,
109 -File::FILE_ERROR_MAX);
110 if (creation_info.error != File::FILE_OK)
111 return creation_info;
112
113 // Write data.
114 file.SetLength(total_size_bytes);
115 int bytes_written = 0;
116 for (const auto& refptr : *items) {
117 const DataElement& element = refptr->item()->data_element();
118 DCHECK_EQ(DataElement::TYPE_BYTES, element.type());
119 int length = base::checked_cast<int, uint64_t>(element.length());
120 bytes_written = file.WriteAtCurrentPos(element.bytes(), length);
121 DCHECK_EQ(length, bytes_written);
122 if (bytes_written < 0) {
123 break;
124 }
125 }
126 UMA_HISTOGRAM_BOOLEAN("Storage.Blob.PageFileWriteSuccess", bytes_written > 0);
127
128 // Grab our modification time and create our SharedFileReference to manage the
129 // lifetime of the file.
130 File::Info info;
131 bool success = file.GetInfo(&info);
132 UMA_HISTOGRAM_BOOLEAN("Storage.Blob.PageFileInfoSuccess", success);
133 creation_info.error =
134 bytes_written < 0 || !success ? File::FILE_ERROR_FAILED : File::FILE_OK;
135 creation_info.last_modified = info.last_modified;
136 return creation_info;
137 }
138
139 } // namespace
140
141 BlobMemoryController::FileCreationInfo::FileCreationInfo() {}
142
143 BlobMemoryController::FileCreationInfo::~FileCreationInfo() {}
144
145 FileCreationInfo::FileCreationInfo(FileCreationInfo&&) = default;
146 FileCreationInfo& FileCreationInfo::operator=(FileCreationInfo&&) = default;
147
148 BlobMemoryController::BlobMemoryController()
149 : recent_item_cache_(RecentItemsCache::NO_AUTO_EVICT) {}
150
151 BlobMemoryController::~BlobMemoryController() {}
152
153 void BlobMemoryController::EnableDisk(
154 const base::FilePath storage_directory,
155 scoped_refptr<base::SingleThreadTaskRunner> file_runner) {
156 LOG(ERROR) << "enbling disk";
157 file_runner_ = std::move(file_runner);
158 blob_storage_dir_ = storage_directory;
159 enable_disk_ = true;
160 }
161
162 bool BlobMemoryController::DecideBlobTransportationMemoryStrategy(
163 const std::vector<DataElement>& descriptions,
164 uint64_t* total_bytes,
165 BlobMemoryController::MemoryStrategyResult* result) const {
166 DCHECK(total_bytes);
167 DCHECK(result);
168
169 // Step 1: Get the sizes.
170 size_t shortcut_memory_size_bytes;
171 uint64_t total_memory_size_bytes;
172 if (!CalculateBlobMemorySize(descriptions, &shortcut_memory_size_bytes,
173 &total_memory_size_bytes)) {
174 return false;
175 }
176 *total_bytes = total_memory_size_bytes;
177
178 // Step 2: Handle case where we have no memory to transport.
179 if (total_memory_size_bytes == 0) {
180 *result = MemoryStrategyResult::NONE_NEEDED;
181 return true;
182 }
183
184 // Step 3: Check if we have enough memory to store the blob.
185 if (total_memory_size_bytes > GetAvailableMemoryForBlobs() &&
186 total_memory_size_bytes > GetAvailableDiskSpaceForBlobs()) {
187 *result = MemoryStrategyResult::TOO_LARGE;
188 return true;
189 }
190
191 // From here on, we know we can fit the blob in memory or on disk.
192 // Step 4: Decide if we're using the shortcut method.
193 if (shortcut_memory_size_bytes == total_memory_size_bytes &&
194 blobs_waiting_for_paging_.empty() &&
195 shortcut_memory_size_bytes < GetAvailableMemoryForBlobs()) {
196 *result = MemoryStrategyResult::SHORTCUT;
197 return true;
198 }
199
200 // Step 5: Decide if we're going straight to disk.
201 if (enable_disk_ && (total_memory_size_bytes > max_blob_in_memory_size_)) {
202 *result = MemoryStrategyResult::FILE;
203 return true;
204 }
205 // From here on, we know the blob's size is less than:
206 // * max_blob_in_memory_size_ if enable_disk_ is true
207 // * max_blob_memory_space_ if enable_disk_ is false
208 // So we know we're < max(size_t).
209 // Step 6: Decide if we're using shared memory.
210 if (total_memory_size_bytes > max_ipc_memory_size_) {
211 *result = MemoryStrategyResult::SHARED_MEMORY;
212 return true;
213 }
214 // Step 7: We can fit in IPC.
215 *result = MemoryStrategyResult::IPC;
216 return true;
217 }
218
219 void BlobMemoryController::CreateTemporaryFileForRenderer(
220 uint64_t size_bytes,
221 const base::Callback<void(FileCreationInfo)>& done) {
michaeln 2016/07/07 20:05:22 the name of this callback reads like a boolean val
dmurph 2016/07/11 23:33:27 Done.
222 if (!enable_disk_) {
223 BlobMemoryController::FileCreationInfo creation_info;
224 done.Run(std::move(creation_info));
225 return;
226 }
227
228 disk_used_ += size_bytes;
229 std::string file_name = base::Uint64ToString(current_file_num_++);
230 scoped_refptr<ShareableFileReference> file_ref =
231 ShareableFileReference::GetOrCreate(
232 blob_storage_dir_.Append(file_name),
233 ShareableFileReference::DELETE_ON_FINAL_RELEASE, file_runner_.get());
234 base::PostTaskAndReplyWithResult(
235 file_runner_.get(), FROM_HERE,
236 base::Bind(&CreateFile, std::move(file_ref), size_bytes),
237 base::Bind(&BlobMemoryController::OnCreateFile, this->AsWeakPtr(),
238 size_bytes, done));
239 }
240
241 void BlobMemoryController::FreeMemory(size_t memory_size_bytes) {
242 DCHECK_GE(blob_memory_used_, memory_size_bytes);
243 blob_memory_used_ -= memory_size_bytes;
244 MaybeScheduleWaitingBlobs();
245 }
246
247 base::Optional<BlobMemoryController::PendingContructionEntry>
248 BlobMemoryController::NotifyWhenMemoryCanPopulated(
249 size_t memory_size,
250 const base::Callback<void(bool)>& can_request) {
michaeln 2016/07/07 20:05:22 the name of this callback reads like a boolean val
dmurph 2016/07/11 23:33:27 Done.
251 DCHECK(memory_size <=
252 GetAvailableMemoryForBlobs() + GetAvailableDiskSpaceForBlobs());
253
254 if (!enable_disk_) {
255 LOG(ERROR) << "Yes, " << memory_size << " can fit in memory now.";
256 blob_memory_used_ += memory_size;
257 return base::nullopt;
258 }
259
260 // If we're currently waiting for blobs to page already, then we add
261 // ourselves to the end of the queue. Once paging is complete, we'll schedule
262 // more paging for any more pending blobs.
263 if (!blobs_waiting_for_paging_.empty()) {
264 LOG(ERROR) << "putting memory request " << memory_size << " in queue.";
265 blobs_waiting_for_paging_.push_back(
266 std::make_pair(memory_size, can_request));
267 blobs_waiting_for_paging_size_ += memory_size;
268 return base::make_optional(--blobs_waiting_for_paging_.end());
269 }
270
271 // Store right away if we can.
272 if (memory_size <= GetAvailableMemoryForBlobs()) {
273 // If we're past our blob memory limit, then schedule our paging.
274 if (blob_memory_used_ + memory_size > max_blob_in_memory_size_) {
275 ScheduleBlobPaging();
276 }
277 LOG(ERROR) << "Yes, " << memory_size << " can fit in memory now.";
278 blob_memory_used_ += memory_size;
279 return base::nullopt;
280 }
281
282 // This means we're too big for memory.
283 LOG(ERROR) << "waiting until " << memory_size << " can fit.";
284 DCHECK(blobs_waiting_for_paging_.empty());
285 DCHECK_EQ(0u, blobs_waiting_for_paging_size_);
286 blobs_waiting_for_paging_.push_back(std::make_pair(memory_size, can_request));
287 blobs_waiting_for_paging_size_ = memory_size;
288 SchedulePagingUntilWeCanFit(memory_size);
289 return base::make_optional(--blobs_waiting_for_paging_.end());
290 }
291
292 void BlobMemoryController::RemovePendingConstructionEntry(
293 const BlobMemoryController::PendingContructionEntry& entry) {
294 const std::pair<size_t, base::Callback<void(bool)>> pair = *entry;
295 blobs_waiting_for_paging_size_ -= pair.first;
296 }
297
298 void BlobMemoryController::UpdateBlobItemInRecents(
299 ShareableBlobDataItem* item) {
300 auto iterator = recent_item_cache_.Get(item->item_id());
301 if (iterator != recent_item_cache_.end())
302 recent_item_cache_.Erase(iterator);
303 recent_item_cache_.Put(item->item_id(), item);
michaeln 2016/07/07 20:05:22 I think MRUCache::Put() has the erase-old, insert-
dmurph 2016/07/11 23:33:27 Done.
304 }
305
306 void BlobMemoryController::RemoveBlobItemInRecents(uint64_t id) {
307 auto iterator = recent_item_cache_.Get(id);
308 if (iterator != recent_item_cache_.end())
309 recent_item_cache_.Erase(iterator);
310 }
311
312 void BlobMemoryController::OnCreateFile(
313 uint64_t file_size,
314 const base::Callback<void(FileCreationInfo)>& done,
315 FileCreationInfo result) {
316 if (result.error == File::FILE_OK) {
317 result.file_reference->AddFinalReleaseCallback(base::Bind(
318 &BlobMemoryController::OnBlobFileDelete, this->AsWeakPtr(), file_size));
319 } else {
320 disk_used_ -= file_size;
321 }
322 LOG(ERROR) << "Created file!";
323 done.Run(std::move(result));
324 }
325
326 void BlobMemoryController::MaybeScheduleWaitingBlobs() {
327 size_t space_available = max_blob_in_memory_size_ - blob_memory_used_;
328 while (!blobs_waiting_for_paging_.empty() &&
329 space_available >= blobs_waiting_for_paging_.front().first) {
330 auto size_callback_pair = blobs_waiting_for_paging_.front();
331 blobs_waiting_for_paging_.pop_front();
332 space_available -= size_callback_pair.first;
333 blobs_waiting_for_paging_size_ -= size_callback_pair.first;
334 blob_memory_used_ += size_callback_pair.first;
335 size_callback_pair.second.Run(true);
336 }
337 }
338
339 size_t BlobMemoryController::ScheduleBlobPaging() {
340 DCHECK(enable_disk_);
341 DCHECK_LT(min_page_file_size_, static_cast<uint64_t>(blob_memory_used_));
342 LOG(ERROR) << "scheduling paging";
343
344 size_t total_items_size = 0;
345 std::unique_ptr<std::vector<scoped_refptr<ShareableBlobDataItem>>>
346 items_for_disk(new std::vector<scoped_refptr<ShareableBlobDataItem>>());
347 // Collect our items.
348 while (total_items_size < min_page_file_size_ &&
349 !recent_item_cache_.empty()) {
350 auto iterator = --recent_item_cache_.end();
351 ShareableBlobDataItem* item = iterator->second;
352 DCHECK(item);
353 recent_item_cache_.Erase(iterator);
354 size_t size = base::checked_cast<size_t, uint64_t>(item->item()->length());
355 total_items_size += size;
356 items_for_disk->push_back(make_scoped_refptr(item));
357 }
358
359 // Update our bookkeeping.
360 disk_used_ += total_items_size;
361 DCHECK_GE(blob_memory_used_, total_items_size);
362 blob_memory_used_ -= total_items_size;
363 in_flight_memory_used_ += total_items_size;
364 std::string file_name = base::Uint64ToString(current_file_num_++);
365 scoped_refptr<ShareableFileReference> file_ref =
366 ShareableFileReference::GetOrCreate(
367 blob_storage_dir_.Append(file_name),
368 ShareableFileReference::DELETE_ON_FINAL_RELEASE, file_runner_.get());
369 base::PostTaskAndReplyWithResult(
370 file_runner_.get(), FROM_HERE,
371 base::Bind(&WriteItemsToFile, items_for_disk.get(), total_items_size,
372 std::move(file_ref)),
373 base::Bind(&BlobMemoryController::OnPagingComplete, this->AsWeakPtr(),
374 base::Passed(&items_for_disk), total_items_size));
375 return total_items_size;
376 }
377
378 void BlobMemoryController::SchedulePagingUntilWeCanFit(
379 size_t total_memory_needed) {
380 DCHECK_LT(total_memory_needed, max_blob_in_memory_size_);
381 while (total_memory_needed + blob_memory_used_ > max_blob_in_memory_size_) {
382 // schedule another page.
383 size_t memory_freeing = ScheduleBlobPaging();
384 DCHECK_GE(blob_memory_used_, memory_freeing);
385 }
386 }
387
388 void BlobMemoryController::OnPagingComplete(
389 std::unique_ptr<std::vector<scoped_refptr<ShareableBlobDataItem>>> items,
390 size_t total_items_size,
391 FileCreationInfo result) {
392 if (!enable_disk_) {
393 return;
394 }
395 if (result.error != File::FILE_OK) {
396 UMA_HISTOGRAM_ENUMERATION("Storage.Blob.PagingError", -result.error,
397 -File::FILE_ERROR_MAX);
398 disk_used_ -= total_items_size;
399 DisableDisk();
400 return;
401 }
402 // Switch from the data backing item to a new file backing item.
403 uint64_t offset = 0;
404 for (const scoped_refptr<ShareableBlobDataItem>& shareable_item :
405 *items.get()) {
406 scoped_refptr<BlobDataItem> new_item(new BlobDataItem(
407 base::WrapUnique(new DataElement()), result.file_reference));
408 new_item->data_element_ptr()->SetToFilePathRange(
409 result.file_reference->path(), offset, shareable_item->item()->length(),
410 result.last_modified);
411 shareable_item->item_.swap(new_item);
michaeln 2016/07/07 20:05:23 shareable_item->item_ = new_item might be more obv
dmurph 2016/07/11 23:33:26 Done.
412 offset += shareable_item->item()->length();
413 }
414 in_flight_memory_used_ -= total_items_size;
415
416 // We want callback on blobs up to the amount we've freed.
417 MaybeScheduleWaitingBlobs();
418
419 // If we still have more blobs waiting, schedule more paging.
420 if (!blobs_waiting_for_paging_.empty()) {
421 SchedulePagingUntilWeCanFit(blobs_waiting_for_paging_size_);
422 }
423 }
424
425 void BlobMemoryController::DisableDisk() {
426 enable_disk_ = false;
427 blob_memory_used_ += in_flight_memory_used_;
428 in_flight_memory_used_ = 0;
429 for (const auto& size_callback_pair : blobs_waiting_for_paging_) {
430 size_callback_pair.second.Run(false);
431 }
432 blobs_waiting_for_paging_.clear();
433 blobs_waiting_for_paging_size_ = 0;
434 recent_item_cache_.Clear();
435 RecordTracingCounters();
436 }
437
438 void BlobMemoryController::RecordTracingCounters() {
439 TRACE_COUNTER2("Blob", "MemoryUsage", "RegularStorage", blob_memory_used_,
440 "InFlightToDisk", in_flight_memory_used_);
441 TRACE_COUNTER1("Blob", "TranfersPendingOnDisk",
442 blobs_waiting_for_paging_.size());
443 TRACE_COUNTER1("Blob", "TranfersBytesPendingOnDisk",
444 blobs_waiting_for_paging_size_);
445 }
446
447 bool BlobMemoryController::CanFitInSystem(uint64_t size) const {
448 return size < GetAvailableMemoryForBlobs() + GetAvailableDiskSpaceForBlobs();
449 }
450
451 size_t BlobMemoryController::GetAvailableMemoryForBlobs() const {
452 if (max_blob_in_memory_size_ < blob_memory_used_)
453 return 0;
454 if (enable_disk_)
455 return std::min<size_t>(0u, in_flight_space_ - in_flight_memory_used_) +
456 (max_blob_in_memory_size_ - blob_memory_used_);
457 return max_blob_memory_space_ - blob_memory_used_;
458 }
459
460 uint64_t BlobMemoryController::GetAvailableDiskSpaceForBlobs() const {
461 return enable_disk_ ? kBlobStorageMaxDiskSpace - disk_used_ : 0;
462 }
463
464 void BlobMemoryController::OnBlobFileDelete(uint64_t size,
465 const base::FilePath& path) {
466 DCHECK_LE(size, disk_used_);
467 LOG(ERROR) << "deleting file " << path.value();
468 disk_used_ -= size;
469 }
470
471 } // namespace storage
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698