File: blob_memory_controller.h

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (326 lines) | stat: -rw-r--r-- 12,526 bytes parent folder | download | duplicates (9)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef STORAGE_BROWSER_BLOB_BLOB_MEMORY_CONTROLLER_H_
#define STORAGE_BROWSER_BLOB_BLOB_MEMORY_CONTROLLER_H_

#include <stdint.h>

#include <list>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <unordered_set>
#include <utility>
#include <vector>

#include "base/component_export.h"
#include "base/containers/lru_cache.h"
#include "base/feature_list.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/functional/callback_forward.h"
#include "base/functional/callback_helpers.h"
#include "base/gtest_prod_util.h"
#include "base/memory/memory_pressure_listener.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "storage/browser/blob/blob_storage_constants.h"

namespace base {
class TaskRunner;
}

namespace content {
class ChromeBlobStorageContext;
}

namespace storage {
class ShareableBlobDataItem;
class ShareableFileReference;

// This class's main responsibility is deciding how blob data gets stored.
// This encompasses:
// * Keeping track of memory & file quota,
// * How to transport the blob data from the renderer (DetermineStrategy),
// * Allocating memory & file quota (ReserveMemoryQuota, ReserveFileQuota)
// * Paging memory quota to disk when we're nearing our memory limit, and
// * Maintaining an LRU of memory items to choose candidates to page to disk
//   (NotifyMemoryItemsUsed).
// This class can only be interacted with on the IO thread.
class COMPONENT_EXPORT(STORAGE_BROWSER) BlobMemoryController {
 public:
  enum class Strategy {
    // We don't have enough memory for this blob.
    TOO_LARGE,
    // There isn't any memory that needs transporting.
    NONE_NEEDED,
    // Transportation strategies.
    IPC,
    SHARED_MEMORY,
    FILE
  };

  struct COMPONENT_EXPORT(STORAGE_BROWSER) FileCreationInfo {
    FileCreationInfo();
    ~FileCreationInfo();
    FileCreationInfo(FileCreationInfo&& other);
    FileCreationInfo& operator=(FileCreationInfo&&);

    base::File::Error error = base::File::FILE_ERROR_FAILED;
    base::File file;
    scoped_refptr<base::TaskRunner> file_deletion_runner;
    base::FilePath path;
    scoped_refptr<ShareableFileReference> file_reference;
    base::Time last_modified;
  };

  struct MemoryAllocation {
    MemoryAllocation(base::WeakPtr<BlobMemoryController> controller,
                     uint64_t item_id,
                     size_t length);

    MemoryAllocation(const MemoryAllocation&) = delete;
    MemoryAllocation& operator=(const MemoryAllocation&) = delete;

    ~MemoryAllocation();

    size_t length() const { return length_; }

   private:
    friend class BlobMemoryController;

    base::WeakPtr<BlobMemoryController> controller_;
    uint64_t item_id_;
    size_t length_;
  };

  class QuotaAllocationTask {
   public:
    // Operation is cancelled and the callback will NOT be called. This object
    // will be destroyed by this call.
    virtual void Cancel() = 0;

   protected:
    virtual ~QuotaAllocationTask();
  };

  // The bool argument is true if we successfully received memory quota.
  using MemoryQuotaRequestCallback = base::OnceCallback<void(bool)>;
  // The bool argument is true if we successfully received file quota, and the
  // vector argument provides the file info.
  using FileQuotaRequestCallback =
      base::OnceCallback<void(std::vector<FileCreationInfo>, bool)>;

  // We enable file paging if |file_runner| isn't a nullptr.
  BlobMemoryController(const base::FilePath& storage_directory,
                       scoped_refptr<base::TaskRunner> file_runner);

  BlobMemoryController(const BlobMemoryController&) = delete;
  BlobMemoryController& operator=(const BlobMemoryController&) = delete;

  ~BlobMemoryController();

  // Disables file paging. This cancels all pending file creations and paging
  // operations. Reason is recorded in UMA.
  void DisableFilePaging(base::File::Error reason);

  bool file_paging_enabled() const { return file_paging_enabled_; }

  // Returns the strategy the transportation layer should use to transport the
  // given memory. |preemptive_transported_bytes| are the number of transport
  // bytes that are already populated for us, so we don't haved to request them
  // from the renderer.
  Strategy DetermineStrategy(size_t preemptive_transported_bytes,
                             uint64_t total_transportation_bytes) const;

  // Checks to see if we can reserve quota (disk or memory) for the given size.
  bool CanReserveQuota(uint64_t size) const;

  // Reserves quota for the given |unreserved_memory_items|. The items must be
  // bytes items in QUOTA_NEEDED state which we change to QUOTA_REQUESTED.
  // After we reserve memory quota we change their state to QUOTA_GRANTED, set
  // the 'memory_allocation' on the item, and  call |done_callback|. This can
  // happen synchronously.
  // Returns a task handle if the request is asynchronous for cancellation.
  // NOTE: We don't inspect quota limits and assume the user checked
  //       CanReserveQuota before calling this.
  base::WeakPtr<QuotaAllocationTask> ReserveMemoryQuota(
      std::vector<scoped_refptr<ShareableBlobDataItem>> unreserved_memory_items,
      MemoryQuotaRequestCallback done_callback);

  // Reserves quota for the given |unreserved_file_items|. The items must be
  // temporary file items (BlobDataBuilder::IsTemporaryFileItem returns true) in
  // QUOTA_NEEDED state, which we change to QUOTA_REQUESTED. After we reserve
  // file quota we change their state to QUOTA_GRANTED and call
  // |done_callback|.
  // Returns a task handle for cancellation.
  // NOTE: We don't inspect quota limits and assume the user checked
  //       CanReserveQuota before calling this.
  base::WeakPtr<QuotaAllocationTask> ReserveFileQuota(
      std::vector<scoped_refptr<ShareableBlobDataItem>> unreserved_file_items,
      FileQuotaRequestCallback done_callback);

  // Called when initially populated or upon later access.
  void NotifyMemoryItemsUsed(
      const std::vector<scoped_refptr<ShareableBlobDataItem>>& items);

  size_t memory_usage() const { return blob_memory_used_; }
  uint64_t disk_usage() const { return disk_used_; }

  base::WeakPtr<BlobMemoryController> GetWeakPtr();

  const BlobStorageLimits& limits() const { return limits_; }
  void set_limits_for_testing(const BlobStorageLimits& limits) {
    OnStorageLimitsCalculated(limits);
    manual_limits_set_ = true;
  }

  void ShrinkMemoryAllocation(ShareableBlobDataItem* item);
  void ShrinkFileAllocation(ShareableFileReference* file_reference,
                            uint64_t old_length,
                            uint64_t new_length);
  void GrowFileAllocation(ShareableFileReference* file_reference,
                          uint64_t delta);

  using DiskSpaceFuncPtr = int64_t (*)(const base::FilePath&);

  void set_testing_disk_space(DiskSpaceFuncPtr disk_space_function) {
    disk_space_function_ = disk_space_function;
  }

  size_t GetAvailableMemoryForBlobs() const;
  uint64_t GetAvailableFileSpaceForBlobs() const;

  // The given callback will be called when we've finished calculating blob
  // storage limits. Usually limits are calculated at some point after startup,
  // but calling this method may cause them to be calculated sooner.
  // If limits have already been calculated |callback| will be called
  // synchronously.
  void CallWhenStorageLimitsAreKnown(base::OnceClosure callback);

  void set_amount_of_physical_memory_for_testing(uint64_t amount_of_memory) {
    amount_of_memory_for_testing_ = amount_of_memory;
  }

 private:
  class FileQuotaAllocationTask;
  class MemoryQuotaAllocationTask;

  FRIEND_TEST_ALL_PREFIXES(BlobMemoryControllerTest, OnMemoryPressure);
  // So this (and only this) class can call CalculateBlobStorageLimits().
  friend class content::ChromeBlobStorageContext;

  // Schedules a task on the file runner to calculate blob storage quota limits.
  // This should only be called once per storage partition initialization as we
  // emit UMA stats with that expectation.
  void CalculateBlobStorageLimits();

  using PendingMemoryQuotaTaskList =
      std::list<std::unique_ptr<MemoryQuotaAllocationTask>>;
  using PendingFileQuotaTaskList =
      std::list<std::unique_ptr<FileQuotaAllocationTask>>;

  void OnStorageLimitsCalculated(BlobStorageLimits limits);

  // Adjusts the effective disk usage based on the available space. We try to
  // keep at least BlobSorageLimits::min_available_disk_space() free.
  void AdjustDiskUsage(uint64_t avail_disk_space);

  base::WeakPtr<QuotaAllocationTask> AppendMemoryTask(
      uint64_t total_bytes_needed,
      std::vector<scoped_refptr<ShareableBlobDataItem>> unreserved_memory_items,
      MemoryQuotaRequestCallback done_callback);

  void MaybeGrantPendingMemoryRequests();

  size_t CollectItemsForEviction(
      std::vector<scoped_refptr<ShareableBlobDataItem>>* output,
      uint64_t min_page_file_size);

  // Schedule paging until our memory usage is below our memory limit.
  void MaybeScheduleEvictionUntilSystemHealthy(
      base::MemoryPressureListener::MemoryPressureLevel level);

  // Called when we've completed evicting a list of items to disk. This is where
  // we swap the bytes items for file items, and update our bookkeeping.
  void OnEvictionComplete(
      scoped_refptr<ShareableFileReference> file_reference,
      std::vector<scoped_refptr<ShareableBlobDataItem>> items,
      size_t total_items_size,
      std::pair<FileCreationInfo, int64_t /* avail_disk */> result);

  void OnMemoryPressure(
      base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level);

  void GrantMemoryAllocations(
      std::vector<scoped_refptr<ShareableBlobDataItem>>* items,
      size_t total_bytes);
  void RevokeMemoryAllocation(uint64_t item_id, size_t length);

  // This is registered as a callback for file deletions on the file reference
  // of our paging files. We decrement the disk space used.
  void OnBlobFileDelete(uint64_t size, const base::FilePath& path);
  void OnShrunkenBlobFileDelete(uint64_t shrink_delta,
                                const base::FilePath& path);

  base::FilePath GenerateNextPageFileName();

  // This records diagnostic counters of our memory quotas. Called when usage
  // changes.
  void RecordTracingCounters() const;

  // Store that we set manual limits so we don't accidentally override them with
  // our configuration task.
  bool manual_limits_set_ = false;
  BlobStorageLimits limits_;
  bool did_schedule_limit_calculation_ = false;
  bool did_calculate_storage_limits_ = false;
  std::vector<base::OnceClosure> on_calculate_limits_callbacks_;

  std::optional<uint64_t> amount_of_memory_for_testing_;

  // Memory bookkeeping. These numbers are all disjoint.
  // This is the amount of memory we're using for blobs in RAM, including the
  // in_flight_memory_used_.
  size_t blob_memory_used_ = 0;
  // This is memory we're temporarily using while we try to write blob items to
  // disk.
  size_t in_flight_memory_used_ = 0;
  // This is the amount of memory we're using on disk.
  uint64_t disk_used_ = 0;

  // State for GenerateNextPageFileName.
  uint64_t current_file_num_ = 0;

  size_t pending_memory_quota_total_size_ = 0;
  PendingMemoryQuotaTaskList pending_memory_quota_tasks_;
  PendingFileQuotaTaskList pending_file_quota_tasks_;

  int pending_evictions_ = 0;

  bool file_paging_enabled_ = false;
  base::FilePath blob_storage_dir_;
  scoped_refptr<base::TaskRunner> file_runner_;
  // This defaults to calling base::SysInfo::AmountOfFreeDiskSpace.
  DiskSpaceFuncPtr disk_space_function_;
  base::TimeTicks last_eviction_time_;

  // Lifetime of the ShareableBlobDataItem objects is handled externally in the
  // BlobStorageContext class.
  base::LRUCache<uint64_t, ShareableBlobDataItem*> populated_memory_items_;
  size_t populated_memory_items_bytes_ = 0;
  // We need to keep track of items currently being paged to disk so that if
  // another blob successfully grabs a ref, we can prevent it from adding the
  // item to the recent_item_cache_ above.
  std::unordered_set<uint64_t> items_paging_to_file_;

  base::MemoryPressureListener memory_pressure_listener_;

  base::WeakPtrFactory<BlobMemoryController> weak_factory_{this};
};
}  // namespace storage
#endif  // STORAGE_BROWSER_BLOB_BLOB_MEMORY_CONTROLLER_H_