| OLD | NEW | 
|---|
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be | 
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. | 
| 4 | 4 | 
| 5 #include <stddef.h> | 5 #include <stddef.h> | 
| 6 #include <stdint.h> | 6 #include <stdint.h> | 
| 7 | 7 | 
| 8 #include <algorithm> | 8 #include <algorithm> | 
| 9 | 9 | 
| 10 #include "base/numerics/safe_math.h" | 10 #include "base/numerics/safe_math.h" | 
| 11 #include "storage/browser/blob/blob_async_transport_strategy.h" | 11 #include "storage/browser/blob/blob_async_transport_request_builder.h" | 
| 12 #include "storage/common/blob_storage/blob_storage_constants.h" | 12 #include "storage/common/blob_storage/blob_storage_constants.h" | 
| 13 | 13 | 
| 14 namespace storage { | 14 namespace storage { | 
| 15 namespace { | 15 namespace { | 
| 16 bool IsBytes(DataElement::Type type) { | 16 bool IsBytes(DataElement::Type type) { | 
| 17   return type == DataElement::TYPE_BYTES || | 17   return type == DataElement::TYPE_BYTES || | 
| 18          type == DataElement::TYPE_BYTES_DESCRIPTION; | 18          type == DataElement::TYPE_BYTES_DESCRIPTION; | 
| 19 } | 19 } | 
| 20 | 20 | 
| 21 // This is the general template that each strategy below implements. See the | 21 // This is the general template that each strategy below implements. See the | 
| 22 // ForEachWithSegment method for a description of how these are called. | 22 // ForEachWithSegment method for a description of how these are called. | 
| 23 // class BlobSegmentVisitor { | 23 // class BlobSegmentVisitor { | 
| 24 //  public: | 24 //  public: | 
| 25 //   typedef ___ SizeType; | 25 //   typedef ___ SizeType; | 
| 26 //   void VisitBytesSegment(size_t element_index, uint64_t element_offset, | 26 //   void VisitBytesSegment(size_t element_index, uint64_t element_offset, | 
| 27 //                          size_t segment_index, uint64_t segment_offset, | 27 //                          size_t segment_index, uint64_t segment_offset, | 
| 28 //                          uint64_t size); | 28 //                          uint64_t size); | 
| 29 //   void VisitNonBytesSegment(const DataElement& element, size_t element_idx); | 29 //   void VisitNonBytesSegment(const DataElement& element, size_t element_idx); | 
| 30 //   void Done(); | 30 //   void Done(); | 
| 31 // }; | 31 // }; | 
| 32 | 32 | 
| 33 // This class handles the logic of how transported memory is going to be | 33 // This class handles the logic of how transported memory is going to be | 
| 34 // represented as storage in the browser.  The main idea is that all the memory | 34 // represented as storage in the browser.  The main idea is that all the memory | 
| 35 // is now packed into file chunks, and the browser items will just reference | 35 // is now packed into file chunks, and the browser items will just reference | 
| 36 // the file with offsets and sizes. | 36 // the file with offsets and sizes. | 
| 37 class FileStorageStrategy { | 37 class FileStorageStrategy { | 
| 38  public: | 38  public: | 
| 39   FileStorageStrategy( | 39   FileStorageStrategy( | 
| 40       std::vector<BlobAsyncTransportStrategy::RendererMemoryItemRequest>* | 40       std::vector<BlobAsyncTransportRequestBuilder::RendererMemoryItemRequest>* | 
| 41           requests, | 41           requests, | 
| 42       BlobDataBuilder* builder) | 42       BlobDataBuilder* builder) | 
| 43       : requests(requests), builder(builder), current_item_index(0) {} | 43       : requests(requests), builder(builder), current_item_index(0) {} | 
| 44 | 44 | 
| 45   ~FileStorageStrategy() {} | 45   ~FileStorageStrategy() {} | 
| 46 | 46 | 
| 47   void VisitBytesSegment(size_t element_index, | 47   void VisitBytesSegment(size_t element_index, | 
| 48                          uint64_t element_offset, | 48                          uint64_t element_offset, | 
| 49                          size_t segment_index, | 49                          size_t segment_index, | 
| 50                          uint64_t segment_offset, | 50                          uint64_t segment_offset, | 
| 51                          uint64_t size) { | 51                          uint64_t size) { | 
| 52     BlobAsyncTransportStrategy::RendererMemoryItemRequest request; | 52     BlobAsyncTransportRequestBuilder::RendererMemoryItemRequest request; | 
| 53     request.browser_item_index = current_item_index; | 53     request.browser_item_index = current_item_index; | 
| 54     request.browser_item_offset = 0; | 54     request.browser_item_offset = 0; | 
| 55     request.message.request_number = requests->size(); | 55     request.message.request_number = requests->size(); | 
| 56     request.message.transport_strategy = IPCBlobItemRequestStrategy::FILE; | 56     request.message.transport_strategy = IPCBlobItemRequestStrategy::FILE; | 
| 57     request.message.renderer_item_index = element_index; | 57     request.message.renderer_item_index = element_index; | 
| 58     request.message.renderer_item_offset = element_offset; | 58     request.message.renderer_item_offset = element_offset; | 
| 59     request.message.size = size; | 59     request.message.size = size; | 
| 60     request.message.handle_index = segment_index; | 60     request.message.handle_index = segment_index; | 
| 61     request.message.handle_offset = segment_offset; | 61     request.message.handle_offset = segment_offset; | 
| 62 | 62 | 
| 63     requests->push_back(request); | 63     requests->push_back(request); | 
| 64     builder->AppendFutureFile(segment_offset, size); | 64     builder->AppendFutureFile(segment_offset, size); | 
| 65     current_item_index++; | 65     current_item_index++; | 
| 66   } | 66   } | 
| 67 | 67 | 
| 68   void VisitNonBytesSegment(const DataElement& element, size_t element_index) { | 68   void VisitNonBytesSegment(const DataElement& element, size_t element_index) { | 
| 69     builder->AppendIPCDataElement(element); | 69     builder->AppendIPCDataElement(element); | 
| 70     current_item_index++; | 70     current_item_index++; | 
| 71   } | 71   } | 
| 72 | 72 | 
| 73   void Done() {} | 73   void Done() {} | 
| 74 | 74 | 
| 75   std::vector<BlobAsyncTransportStrategy::RendererMemoryItemRequest>* requests; | 75   std::vector<BlobAsyncTransportRequestBuilder::RendererMemoryItemRequest>* | 
|  | 76       requests; | 
| 76   BlobDataBuilder* builder; | 77   BlobDataBuilder* builder; | 
| 77 | 78 | 
| 78   size_t current_item_index; | 79   size_t current_item_index; | 
| 79 }; | 80 }; | 
| 80 | 81 | 
| 81 // This class handles the logic of storing memory that is transported as | 82 // This class handles the logic of storing memory that is transported as | 
| 82 // consolidated shared memory. | 83 // consolidated shared memory. | 
| 83 class SharedMemoryStorageStrategy { | 84 class SharedMemoryStorageStrategy { | 
| 84  public: | 85  public: | 
| 85   SharedMemoryStorageStrategy( | 86   SharedMemoryStorageStrategy( | 
| 86       size_t max_segment_size, | 87       size_t max_segment_size, | 
| 87       std::vector<BlobAsyncTransportStrategy::RendererMemoryItemRequest>* | 88       std::vector<BlobAsyncTransportRequestBuilder::RendererMemoryItemRequest>* | 
| 88           requests, | 89           requests, | 
| 89       BlobDataBuilder* builder) | 90       BlobDataBuilder* builder) | 
| 90       : requests(requests), | 91       : requests(requests), | 
| 91         max_segment_size(max_segment_size), | 92         max_segment_size(max_segment_size), | 
| 92         builder(builder), | 93         builder(builder), | 
| 93         current_item_size(0), | 94         current_item_size(0), | 
| 94         current_item_index(0) {} | 95         current_item_index(0) {} | 
| 95   ~SharedMemoryStorageStrategy() {} | 96   ~SharedMemoryStorageStrategy() {} | 
| 96 | 97 | 
| 97   void VisitBytesSegment(size_t element_index, | 98   void VisitBytesSegment(size_t element_index, | 
| 98                          uint64_t element_offset, | 99                          uint64_t element_offset, | 
| 99                          size_t segment_index, | 100                          size_t segment_index, | 
| 100                          uint64_t segment_offset, | 101                          uint64_t segment_offset, | 
| 101                          uint64_t size) { | 102                          uint64_t size) { | 
| 102     if (current_item_size + size > max_segment_size) { | 103     if (current_item_size + size > max_segment_size) { | 
| 103       builder->AppendFutureData(current_item_size); | 104       builder->AppendFutureData(current_item_size); | 
| 104       current_item_index++; | 105       current_item_index++; | 
| 105       current_item_size = 0; | 106       current_item_size = 0; | 
| 106     } | 107     } | 
| 107     BlobAsyncTransportStrategy::RendererMemoryItemRequest request; | 108     BlobAsyncTransportRequestBuilder::RendererMemoryItemRequest request; | 
| 108     request.browser_item_index = current_item_index; | 109     request.browser_item_index = current_item_index; | 
| 109     request.browser_item_offset = current_item_size; | 110     request.browser_item_offset = current_item_size; | 
| 110     request.message.request_number = requests->size(); | 111     request.message.request_number = requests->size(); | 
| 111     request.message.transport_strategy = | 112     request.message.transport_strategy = | 
| 112         IPCBlobItemRequestStrategy::SHARED_MEMORY; | 113         IPCBlobItemRequestStrategy::SHARED_MEMORY; | 
| 113     request.message.renderer_item_index = element_index; | 114     request.message.renderer_item_index = element_index; | 
| 114     request.message.renderer_item_offset = element_offset; | 115     request.message.renderer_item_offset = element_offset; | 
| 115     request.message.size = size; | 116     request.message.size = size; | 
| 116     request.message.handle_index = segment_index; | 117     request.message.handle_index = segment_index; | 
| 117     request.message.handle_offset = segment_offset; | 118     request.message.handle_offset = segment_offset; | 
| (...skipping 11 matching lines...) Expand all  Loading... | 
| 129     current_item_index++; | 130     current_item_index++; | 
| 130     current_item_size = 0; | 131     current_item_size = 0; | 
| 131   } | 132   } | 
| 132 | 133 | 
| 133   void Done() { | 134   void Done() { | 
| 134     if (current_item_size != 0) { | 135     if (current_item_size != 0) { | 
| 135       builder->AppendFutureData(current_item_size); | 136       builder->AppendFutureData(current_item_size); | 
| 136     } | 137     } | 
| 137   } | 138   } | 
| 138 | 139 | 
| 139   std::vector<BlobAsyncTransportStrategy::RendererMemoryItemRequest>* requests; | 140   std::vector<BlobAsyncTransportRequestBuilder::RendererMemoryItemRequest>* | 
|  | 141       requests; | 
| 140 | 142 | 
| 141   size_t max_segment_size; | 143   size_t max_segment_size; | 
| 142   BlobDataBuilder* builder; | 144   BlobDataBuilder* builder; | 
| 143   size_t current_item_size; | 145   size_t current_item_size; | 
| 144   uint64_t current_item_index; | 146   uint64_t current_item_index; | 
| 145 }; | 147 }; | 
| 146 | 148 | 
| 147 // This iterates of the data elements and segments the 'bytes' data into | 149 // This iterates of the data elements and segments the 'bytes' data into | 
| 148 // the smallest number of segments given the max_segment_size. | 150 // the smallest number of segments given the max_segment_size. | 
| 149 // The callback describes either: | 151 // The callback describes either: | 
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
| 184                                  segment_offset, memory_writing); | 186                                  segment_offset, memory_writing); | 
| 185       element_memory_left -= memory_writing; | 187       element_memory_left -= memory_writing; | 
| 186       segment_offset += memory_writing; | 188       segment_offset += memory_writing; | 
| 187       element_offset += memory_writing; | 189       element_offset += memory_writing; | 
| 188     } | 190     } | 
| 189   } | 191   } | 
| 190   visitor->Done(); | 192   visitor->Done(); | 
| 191 } | 193 } | 
| 192 }  // namespace | 194 }  // namespace | 
| 193 | 195 | 
| 194 BlobAsyncTransportStrategy::RendererMemoryItemRequest:: | 196 BlobAsyncTransportRequestBuilder::RendererMemoryItemRequest:: | 
| 195     RendererMemoryItemRequest() | 197     RendererMemoryItemRequest() | 
| 196     : browser_item_index(0), browser_item_offset(0), received(false) {} | 198     : browser_item_index(0), browser_item_offset(0) {} | 
| 197 | 199 | 
| 198 BlobAsyncTransportStrategy::BlobAsyncTransportStrategy() | 200 BlobAsyncTransportRequestBuilder::BlobAsyncTransportRequestBuilder() | 
| 199     : error_(BlobAsyncTransportStrategy::ERROR_NONE), total_bytes_size_(0) {} | 201     : total_bytes_size_(0) {} | 
| 200 | 202 | 
| 201 BlobAsyncTransportStrategy::~BlobAsyncTransportStrategy() {} | 203 BlobAsyncTransportRequestBuilder::~BlobAsyncTransportRequestBuilder() {} | 
| 202 | 204 | 
| 203 // if total_blob_size > |memory_available| (say 400MB) | 205 // Initializes the transport strategy for file requests. | 
| 204 //   Request all data in files | 206 void BlobAsyncTransportRequestBuilder::InitializeForFileRequests( | 
| 205 //   (Segment all of the existing data into | 207     size_t max_file_size, | 
| 206 //   file blocks, of <= |max_file_size|) | 208     uint64_t blob_total_size, | 
| 207 // else if total_blob_size > |max_ipc_memory_size| (say 150KB) | 209     const std::vector<DataElement>& elements, | 
| 208 //   Request all data in shared memory | 210     BlobDataBuilder* builder) { | 
| 209 //   (Segment all of the existing data into | 211   DCHECK(requests_.empty()); | 
| 210 //   shared memory blocks, of <= |max_shared_memory_size|) | 212   total_bytes_size_ = blob_total_size; | 
| 211 // else | 213   ComputeHandleSizes(total_bytes_size_, max_file_size, &file_sizes_); | 
| 212 //   Request all data to be sent over IPC | 214   FileStorageStrategy strategy(&requests_, builder); | 
| 213 void BlobAsyncTransportStrategy::Initialize( | 215   ForEachWithSegment(elements, static_cast<uint64_t>(max_file_size), &strategy); | 
|  | 216 } | 
|  | 217 | 
|  | 218 void BlobAsyncTransportRequestBuilder::InitializeForSharedMemoryRequests( | 
|  | 219     size_t max_shared_memory_size, | 
|  | 220     uint64_t blob_total_size, | 
|  | 221     const std::vector<DataElement>& elements, | 
|  | 222     BlobDataBuilder* builder) { | 
|  | 223   DCHECK(requests_.empty()); | 
|  | 224   DCHECK(blob_total_size <= std::numeric_limits<size_t>::max()); | 
|  | 225   total_bytes_size_ = blob_total_size; | 
|  | 226   ComputeHandleSizes(total_bytes_size_, max_shared_memory_size, | 
|  | 227                      &shared_memory_sizes_); | 
|  | 228   SharedMemoryStorageStrategy strategy(max_shared_memory_size, &requests_, | 
|  | 229                                        builder); | 
|  | 230   ForEachWithSegment(elements, static_cast<uint64_t>(max_shared_memory_size), | 
|  | 231                      &strategy); | 
|  | 232 } | 
|  | 233 | 
|  | 234 void BlobAsyncTransportRequestBuilder::InitializeForIPCRequests( | 
| 214     size_t max_ipc_memory_size, | 235     size_t max_ipc_memory_size, | 
| 215     size_t max_shared_memory_size, | 236     uint64_t blob_total_size, | 
| 216     size_t max_file_size, | 237     const std::vector<DataElement>& elements, | 
| 217     uint64_t disk_space_left, | 238     BlobDataBuilder* builder) { | 
| 218     size_t memory_available, |  | 
| 219     const std::string& uuid, |  | 
| 220     const std::vector<DataElement>& blob_item_infos) { |  | 
| 221   DCHECK(handle_sizes_.empty()); |  | 
| 222   DCHECK(requests_.empty()); | 239   DCHECK(requests_.empty()); | 
| 223   DCHECK(!builder_.get()); | 240   // We don't segment anything, and just request the memory items directly | 
| 224   builder_.reset(new BlobDataBuilder(uuid)); | 241   // in IPC. | 
| 225   error_ = BlobAsyncTransportStrategy::ERROR_NONE; | 242   size_t items_length = elements.size(); | 
| 226 | 243   total_bytes_size_ = blob_total_size; | 
| 227   size_t memory_items = 0; | 244   for (size_t i = 0; i < items_length; i++) { | 
| 228   base::CheckedNumeric<uint64_t> total_size_checked = 0; | 245     const auto& info = elements.at(i); | 
| 229   for (const auto& info : blob_item_infos) { |  | 
| 230     if (!IsBytes(info.type())) { | 246     if (!IsBytes(info.type())) { | 
|  | 247       builder->AppendIPCDataElement(info); | 
| 231       continue; | 248       continue; | 
| 232     } | 249     } | 
| 233     total_size_checked += info.length(); | 250     BlobAsyncTransportRequestBuilder::RendererMemoryItemRequest request; | 
| 234     ++memory_items; |  | 
| 235   } |  | 
| 236 |  | 
| 237   if (!total_size_checked.IsValid()) { |  | 
| 238     DVLOG(1) << "Impossible total size of all memory elements."; |  | 
| 239     error_ = BlobAsyncTransportStrategy::ERROR_INVALID_PARAMS; |  | 
| 240     return; |  | 
| 241   } |  | 
| 242 |  | 
| 243   total_bytes_size_ = total_size_checked.ValueOrDie(); |  | 
| 244 |  | 
| 245   // See if we have enough memory. |  | 
| 246   if (total_bytes_size_ > |  | 
| 247       disk_space_left + static_cast<uint64_t>(memory_available)) { |  | 
| 248     error_ = BlobAsyncTransportStrategy::ERROR_TOO_LARGE; |  | 
| 249     return; |  | 
| 250   } |  | 
| 251 |  | 
| 252   // If we're more than the available memory, then we're going straight to disk. |  | 
| 253   if (total_bytes_size_ > memory_available) { |  | 
| 254     if (total_bytes_size_ > disk_space_left) { |  | 
| 255       error_ = BlobAsyncTransportStrategy::ERROR_TOO_LARGE; |  | 
| 256       return; |  | 
| 257     } |  | 
| 258     ComputeHandleSizes(total_bytes_size_, max_file_size, &handle_sizes_); |  | 
| 259     FileStorageStrategy strategy(&requests_, builder_.get()); |  | 
| 260     ForEachWithSegment(blob_item_infos, static_cast<uint64_t>(max_file_size), |  | 
| 261                        &strategy); |  | 
| 262     return; |  | 
| 263   } |  | 
| 264 |  | 
| 265   if (total_bytes_size_ > max_ipc_memory_size) { |  | 
| 266     // Note: The size must be <= std::numeric_limits<size_t>::max(). Otherwise |  | 
| 267     // we are guarenteed to be caught by the if statement above, |  | 
| 268     // |total_bytes_size_ > memory_available|. |  | 
| 269     ComputeHandleSizes(total_bytes_size_, max_shared_memory_size, |  | 
| 270                        &handle_sizes_); |  | 
| 271     SharedMemoryStorageStrategy strategy(max_shared_memory_size, &requests_, |  | 
| 272                                          builder_.get()); |  | 
| 273     ForEachWithSegment(blob_item_infos, |  | 
| 274                        static_cast<uint64_t>(max_shared_memory_size), |  | 
| 275                        &strategy); |  | 
| 276     return; |  | 
| 277   } |  | 
| 278 |  | 
| 279   // Since they can all fit in IPC memory, we don't need to segment anything, |  | 
| 280   // and just request them straight in IPC. |  | 
| 281   size_t items_length = blob_item_infos.size(); |  | 
| 282   for (size_t i = 0; i < items_length; i++) { |  | 
| 283     const auto& info = blob_item_infos.at(i); |  | 
| 284     if (!IsBytes(info.type())) { |  | 
| 285       builder_->AppendIPCDataElement(info); |  | 
| 286       continue; |  | 
| 287     } |  | 
| 288     BlobAsyncTransportStrategy::RendererMemoryItemRequest request; |  | 
| 289     request.browser_item_index = i; | 251     request.browser_item_index = i; | 
| 290     request.browser_item_offset = 0; | 252     request.browser_item_offset = 0; | 
| 291     request.message.request_number = requests_.size(); | 253     request.message.request_number = requests_.size(); | 
| 292     request.message.transport_strategy = IPCBlobItemRequestStrategy::IPC; | 254     request.message.transport_strategy = IPCBlobItemRequestStrategy::IPC; | 
| 293     request.message.renderer_item_index = i; | 255     request.message.renderer_item_index = i; | 
| 294     request.message.renderer_item_offset = 0; | 256     request.message.renderer_item_offset = 0; | 
| 295     request.message.size = info.length(); | 257     request.message.size = info.length(); | 
| 296     requests_.push_back(request); | 258     requests_.push_back(request); | 
| 297     builder_->AppendFutureData(info.length()); | 259     builder->AppendFutureData(info.length()); | 
| 298   } | 260   } | 
| 299 } | 261 } | 
| 300 | 262 | 
| 301 /* static */ | 263 /* static */ | 
| 302 bool BlobAsyncTransportStrategy::ShouldBeShortcut( | 264 bool BlobAsyncTransportRequestBuilder::ShouldBeShortcut( | 
| 303     const std::vector<DataElement>& elements, | 265     const std::vector<DataElement>& elements, | 
| 304     size_t memory_available) { | 266     size_t memory_available) { | 
| 305   base::CheckedNumeric<size_t> shortcut_bytes = 0; | 267   base::CheckedNumeric<size_t> shortcut_bytes = 0; | 
| 306   for (const auto& element : elements) { | 268   for (const auto& element : elements) { | 
| 307     DataElement::Type type = element.type(); | 269     DataElement::Type type = element.type(); | 
| 308     if (type == DataElement::TYPE_BYTES_DESCRIPTION) { | 270     if (type == DataElement::TYPE_BYTES_DESCRIPTION) { | 
| 309       return false; | 271       return false; | 
| 310     } | 272     } | 
| 311     if (type == DataElement::TYPE_BYTES) { | 273     if (type == DataElement::TYPE_BYTES) { | 
| 312       shortcut_bytes += element.length(); | 274       shortcut_bytes += element.length(); | 
| 313       if (!shortcut_bytes.IsValid()) { | 275       if (!shortcut_bytes.IsValid()) { | 
| 314         return false; | 276         return false; | 
| 315       } | 277       } | 
| 316     } | 278     } | 
| 317   } | 279   } | 
| 318   return shortcut_bytes.ValueOrDie() <= memory_available; | 280   return shortcut_bytes.ValueOrDie() <= memory_available; | 
| 319 } | 281 } | 
| 320 | 282 | 
| 321 /* static */ | 283 /* static */ | 
| 322 void BlobAsyncTransportStrategy::ComputeHandleSizes( | 284 void BlobAsyncTransportRequestBuilder::ComputeHandleSizes( | 
| 323     uint64_t total_memory_size, | 285     uint64_t total_memory_size, | 
| 324     size_t max_segment_size, | 286     size_t max_segment_size, | 
| 325     std::vector<size_t>* segment_sizes) { | 287     std::vector<size_t>* segment_sizes) { | 
| 326   size_t total_max_segments = | 288   size_t total_max_segments = | 
| 327       static_cast<size_t>(total_memory_size / max_segment_size); | 289       static_cast<size_t>(total_memory_size / max_segment_size); | 
| 328   bool has_extra_segment = (total_memory_size % max_segment_size) > 0; | 290   bool has_extra_segment = (total_memory_size % max_segment_size) > 0; | 
| 329   segment_sizes->reserve(total_max_segments + (has_extra_segment ? 1 : 0)); | 291   segment_sizes->reserve(total_max_segments + (has_extra_segment ? 1 : 0)); | 
| 330   segment_sizes->insert(segment_sizes->begin(), total_max_segments, | 292   segment_sizes->insert(segment_sizes->begin(), total_max_segments, | 
| 331                         max_segment_size); | 293                         max_segment_size); | 
| 332   if (has_extra_segment) { | 294   if (has_extra_segment) { | 
| 333     segment_sizes->push_back(total_memory_size % max_segment_size); | 295     segment_sizes->push_back(total_memory_size % max_segment_size); | 
| 334   } | 296   } | 
| 335 } | 297 } | 
| 336 | 298 | 
| 337 }  // namespace storage | 299 }  // namespace storage | 
| OLD | NEW | 
|---|