OLD | NEW |
---|---|
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 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 "components/discardable_memory/service/discardable_shared_memory_manage r.h" | 5 #include "components/discardable_memory/service/discardable_shared_memory_manage r.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
8 #include <utility> | 8 #include <utility> |
9 | 9 |
10 #include "base/atomic_sequence_num.h" | 10 #include "base/atomic_sequence_num.h" |
(...skipping 11 matching lines...) Expand all Loading... | |
22 #include "base/strings/string_number_conversions.h" | 22 #include "base/strings/string_number_conversions.h" |
23 #include "base/strings/stringprintf.h" | 23 #include "base/strings/stringprintf.h" |
24 #include "base/sys_info.h" | 24 #include "base/sys_info.h" |
25 #include "base/threading/thread_task_runner_handle.h" | 25 #include "base/threading/thread_task_runner_handle.h" |
26 #include "base/trace_event/memory_allocator_dump.h" | 26 #include "base/trace_event/memory_allocator_dump.h" |
27 #include "base/trace_event/memory_dump_manager.h" | 27 #include "base/trace_event/memory_dump_manager.h" |
28 #include "base/trace_event/process_memory_dump.h" | 28 #include "base/trace_event/process_memory_dump.h" |
29 #include "base/trace_event/trace_event.h" | 29 #include "base/trace_event/trace_event.h" |
30 #include "build/build_config.h" | 30 #include "build/build_config.h" |
31 #include "components/discardable_memory/common/discardable_shared_memory_heap.h" | 31 #include "components/discardable_memory/common/discardable_shared_memory_heap.h" |
32 #include "mojo/public/cpp/bindings/strong_binding.h" | |
33 #include "mojo/public/cpp/system/platform_handle.h" | |
32 | 34 |
33 #if defined(OS_LINUX) | 35 #if defined(OS_LINUX) |
34 #include "base/files/file_path.h" | 36 #include "base/files/file_path.h" |
35 #include "base/files/file_util.h" | 37 #include "base/files/file_util.h" |
36 #include "base/metrics/histogram_macros.h" | 38 #include "base/metrics/histogram_macros.h" |
37 #endif | 39 #endif |
38 | 40 |
39 namespace discardable_memory { | 41 namespace discardable_memory { |
40 namespace { | 42 namespace { |
41 | 43 |
42 const char kSingleProcess[] = "single-process"; | 44 const char kSingleProcess[] = "single-process"; |
43 | 45 |
44 const int kInvalidUniqueClientID = -1; | 46 const int kInvalidUniqueClientID = -1; |
45 | 47 |
46 const uint64_t kBrowserTracingProcessId = std::numeric_limits<uint64_t>::max(); | 48 const uint64_t kBrowserTracingProcessId = std::numeric_limits<uint64_t>::max(); |
47 | 49 |
48 uint64_t ClientProcessUniqueIdToTracingProcessId(int client_id) { | 50 uint64_t ClientProcessUniqueIdToTracingProcessId(int client_id) { |
49 // TODO(penghuang): Move this function to right place. | 51 // TODO(penghuang): Move this function to right place. |
50 // https://crbug.com/661257 | 52 // https://crbug.com/661257 |
51 if (base::CommandLine::ForCurrentProcess()->HasSwitch(kSingleProcess)) | 53 if (base::CommandLine::ForCurrentProcess()->HasSwitch(kSingleProcess)) |
52 return kBrowserTracingProcessId; | 54 return kBrowserTracingProcessId; |
53 // The hash value is incremented so that the tracing id is never equal to | 55 // The hash value is incremented so that the tracing id is never equal to |
54 // MemoryDumpManager::kInvalidTracingProcessId. | 56 // MemoryDumpManager::kInvalidTracingProcessId. |
55 return static_cast<uint64_t>(base::Hash( | 57 return static_cast<uint64_t>(base::Hash( |
56 reinterpret_cast<const char*>(&client_id), sizeof(client_id))) + | 58 reinterpret_cast<const char*>(&client_id), sizeof(client_id))) + |
57 1; | 59 1; |
58 } | 60 } |
59 | 61 |
62 class DiscardableSharedMemoryManagerProxy | |
reveman
2016/11/23 17:07:44
Please add a comment above this class describing w
Peng
2016/11/24 15:04:37
How about MojoDiscardableSharedMemoryManagerImpl?
| |
63 : public mojom::DiscardableSharedMemoryManager { | |
64 public: | |
65 DiscardableSharedMemoryManagerProxy( | |
66 int32_t client_id, | |
67 ::discardable_memory::DiscardableSharedMemoryManager* manager) | |
68 : client_id_(client_id), manager_(manager) {} | |
69 | |
70 ~DiscardableSharedMemoryManagerProxy() override { | |
71 manager_->ClientRemoved(client_id_); | |
72 } | |
73 | |
74 // mojom::DiscardableSharedMemoryManager overrides: | |
75 void AllocateLockedDiscardableSharedMemory( | |
76 uint32_t size, | |
77 int32_t id, | |
78 const AllocateLockedDiscardableSharedMemoryCallback& callback) override { | |
79 base::SharedMemoryHandle handle; | |
80 manager_->AllocateLockedDiscardableSharedMemoryForClient(client_id_, size, | |
81 id, &handle); | |
82 mojo::ScopedSharedBufferHandle memory; | |
83 if (handle != base::SharedMemory::NULLHandle()) { | |
dcheng
2016/11/25 00:07:08
Is it necessary to check this condition? I would e
Peng
2016/11/25 16:41:53
Done.
| |
84 memory = | |
85 mojo::WrapSharedMemoryHandle(handle, size, false /* ready_only */); | |
86 } | |
87 return callback.Run(std::move(memory)); | |
88 } | |
89 | |
90 void DeletedDiscardableSharedMemory(int32_t id) override { | |
91 manager_->ClientDeletedDiscardableSharedMemory(id, client_id_); | |
92 } | |
93 | |
94 private: | |
95 const int32_t client_id_; | |
96 ::discardable_memory::DiscardableSharedMemoryManager* const manager_; | |
97 | |
98 DISALLOW_COPY_AND_ASSIGN(DiscardableSharedMemoryManagerProxy); | |
99 }; | |
100 | |
60 class DiscardableMemoryImpl : public base::DiscardableMemory { | 101 class DiscardableMemoryImpl : public base::DiscardableMemory { |
61 public: | 102 public: |
62 DiscardableMemoryImpl( | 103 DiscardableMemoryImpl( |
63 std::unique_ptr<base::DiscardableSharedMemory> shared_memory, | 104 std::unique_ptr<base::DiscardableSharedMemory> shared_memory, |
64 const base::Closure& deleted_callback) | 105 const base::Closure& deleted_callback) |
65 : shared_memory_(std::move(shared_memory)), | 106 : shared_memory_(std::move(shared_memory)), |
66 deleted_callback_(deleted_callback), | 107 deleted_callback_(deleted_callback), |
67 is_locked_(true) {} | 108 is_locked_(true) {} |
68 | 109 |
69 ~DiscardableMemoryImpl() override { | 110 ~DiscardableMemoryImpl() override { |
(...skipping 103 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
173 | 214 |
174 } // namespace | 215 } // namespace |
175 | 216 |
176 DiscardableSharedMemoryManager::MemorySegment::MemorySegment( | 217 DiscardableSharedMemoryManager::MemorySegment::MemorySegment( |
177 std::unique_ptr<base::DiscardableSharedMemory> memory) | 218 std::unique_ptr<base::DiscardableSharedMemory> memory) |
178 : memory_(std::move(memory)) {} | 219 : memory_(std::move(memory)) {} |
179 | 220 |
180 DiscardableSharedMemoryManager::MemorySegment::~MemorySegment() {} | 221 DiscardableSharedMemoryManager::MemorySegment::~MemorySegment() {} |
181 | 222 |
182 DiscardableSharedMemoryManager::DiscardableSharedMemoryManager() | 223 DiscardableSharedMemoryManager::DiscardableSharedMemoryManager() |
183 : default_memory_limit_(GetDefaultMemoryLimit()), | 224 : last_client_id_(0), |
225 default_memory_limit_(GetDefaultMemoryLimit()), | |
184 memory_limit_(default_memory_limit_), | 226 memory_limit_(default_memory_limit_), |
185 bytes_allocated_(0), | 227 bytes_allocated_(0), |
186 memory_pressure_listener_(new base::MemoryPressureListener( | 228 memory_pressure_listener_(new base::MemoryPressureListener( |
187 base::Bind(&DiscardableSharedMemoryManager::OnMemoryPressure, | 229 base::Bind(&DiscardableSharedMemoryManager::OnMemoryPressure, |
188 base::Unretained(this)))), | 230 base::Unretained(this)))), |
189 // Current thread might not have a task runner in tests. | 231 // Current thread might not have a task runner in tests. |
190 enforce_memory_policy_task_runner_(base::ThreadTaskRunnerHandle::Get()), | 232 enforce_memory_policy_task_runner_(base::ThreadTaskRunnerHandle::Get()), |
191 enforce_memory_policy_pending_(false), | 233 enforce_memory_policy_pending_(false), |
192 weak_ptr_factory_(this) { | 234 weak_ptr_factory_(this) { |
193 DCHECK_NE(memory_limit_, 0u); | 235 DCHECK_NE(memory_limit_, 0u); |
194 enforce_memory_policy_callback_ = | 236 enforce_memory_policy_callback_ = |
195 base::Bind(&DiscardableSharedMemoryManager::EnforceMemoryPolicy, | 237 base::Bind(&DiscardableSharedMemoryManager::EnforceMemoryPolicy, |
196 weak_ptr_factory_.GetWeakPtr()); | 238 weak_ptr_factory_.GetWeakPtr()); |
197 base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider( | 239 base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider( |
198 this, "DiscardableSharedMemoryManager", | 240 this, "DiscardableSharedMemoryManager", |
199 base::ThreadTaskRunnerHandle::Get()); | 241 base::ThreadTaskRunnerHandle::Get()); |
200 base::MemoryCoordinatorClientRegistry::GetInstance()->Register(this); | 242 base::MemoryCoordinatorClientRegistry::GetInstance()->Register(this); |
201 } | 243 } |
202 | 244 |
203 DiscardableSharedMemoryManager::~DiscardableSharedMemoryManager() { | 245 DiscardableSharedMemoryManager::~DiscardableSharedMemoryManager() { |
204 base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider( | 246 base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider( |
205 this); | 247 this); |
206 } | 248 } |
207 | 249 |
250 // static | |
251 DiscardableSharedMemoryManager* | |
252 DiscardableSharedMemoryManager::CreateInstance() { | |
253 DCHECK(g_discardable_shared_memory_manager == nullptr); | |
reveman
2016/11/23 17:07:44
nit: DCHECK(!g_discardable_shared_memory_manager)
Peng
2016/11/24 15:04:37
It doesn't work. Because base::LazyInstance only o
| |
254 return g_discardable_shared_memory_manager.Pointer(); | |
255 } | |
256 | |
257 // static | |
208 DiscardableSharedMemoryManager* DiscardableSharedMemoryManager::current() { | 258 DiscardableSharedMemoryManager* DiscardableSharedMemoryManager::current() { |
259 DCHECK(!(g_discardable_shared_memory_manager == nullptr)); | |
reveman
2016/11/23 17:07:44
nit: DCHECK(g_discardable_shared_memory_manager)
Peng
2016/11/24 15:04:37
It doesn't work. Because base::LazyInstance only o
| |
209 return g_discardable_shared_memory_manager.Pointer(); | 260 return g_discardable_shared_memory_manager.Pointer(); |
210 } | 261 } |
211 | 262 |
263 // static | |
264 void DiscardableSharedMemoryManager::Bind( | |
265 mojom::DiscardableSharedMemoryManagerRequest request) { | |
266 auto* manager = current(); | |
267 mojo::MakeStrongBinding(base::MakeUnique<DiscardableSharedMemoryManagerProxy>( | |
268 ++manager->last_client_id_, manager), | |
269 std::move(request)); | |
270 } | |
271 | |
212 std::unique_ptr<base::DiscardableMemory> | 272 std::unique_ptr<base::DiscardableMemory> |
213 DiscardableSharedMemoryManager::AllocateLockedDiscardableMemory(size_t size) { | 273 DiscardableSharedMemoryManager::AllocateLockedDiscardableMemory(size_t size) { |
214 DCHECK_NE(size, 0u); | 274 DCHECK_NE(size, 0u); |
215 | 275 |
216 DiscardableSharedMemoryId new_id = | 276 int32_t new_id = g_next_discardable_shared_memory_id.GetNext(); |
217 g_next_discardable_shared_memory_id.GetNext(); | |
218 base::ProcessHandle current_process_handle = base::GetCurrentProcessHandle(); | |
219 | 277 |
220 // Note: Use DiscardableSharedMemoryHeap for in-process allocation | 278 // Note: Use DiscardableSharedMemoryHeap for in-process allocation |
221 // of discardable memory if the cost of each allocation is too high. | 279 // of discardable memory if the cost of each allocation is too high. |
222 base::SharedMemoryHandle handle; | 280 base::SharedMemoryHandle handle; |
223 AllocateLockedDiscardableSharedMemory( | 281 AllocateLockedDiscardableSharedMemory(kInvalidUniqueClientID, size, new_id, |
224 current_process_handle, kInvalidUniqueClientID, size, new_id, &handle); | 282 &handle); |
225 std::unique_ptr<base::DiscardableSharedMemory> memory( | 283 std::unique_ptr<base::DiscardableSharedMemory> memory( |
226 new base::DiscardableSharedMemory(handle)); | 284 new base::DiscardableSharedMemory(handle)); |
227 if (!memory->Map(size)) | 285 if (!memory->Map(size)) |
228 base::TerminateBecauseOutOfMemory(size); | 286 base::TerminateBecauseOutOfMemory(size); |
229 // Close file descriptor to avoid running out. | 287 // Close file descriptor to avoid running out. |
230 memory->Close(); | 288 memory->Close(); |
231 return base::MakeUnique<DiscardableMemoryImpl>( | 289 return base::MakeUnique<DiscardableMemoryImpl>( |
232 std::move(memory), | 290 std::move(memory), |
233 base::Bind( | 291 base::Bind( |
234 &DiscardableSharedMemoryManager::DeletedDiscardableSharedMemory, | 292 &DiscardableSharedMemoryManager::DeletedDiscardableSharedMemory, |
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
301 static_cast<uint64_t>(resident_size)); | 359 static_cast<uint64_t>(resident_size)); |
302 } | 360 } |
303 #endif // defined(COUNT_RESIDENT_BYTES_SUPPORTED) | 361 #endif // defined(COUNT_RESIDENT_BYTES_SUPPORTED) |
304 } | 362 } |
305 } | 363 } |
306 return true; | 364 return true; |
307 } | 365 } |
308 | 366 |
309 void DiscardableSharedMemoryManager:: | 367 void DiscardableSharedMemoryManager:: |
310 AllocateLockedDiscardableSharedMemoryForClient( | 368 AllocateLockedDiscardableSharedMemoryForClient( |
311 base::ProcessHandle process_handle, | |
312 int client_id, | 369 int client_id, |
313 size_t size, | 370 size_t size, |
314 DiscardableSharedMemoryId id, | 371 int32_t id, |
315 base::SharedMemoryHandle* shared_memory_handle) { | 372 base::SharedMemoryHandle* shared_memory_handle) { |
316 AllocateLockedDiscardableSharedMemory(process_handle, client_id, size, id, | 373 AllocateLockedDiscardableSharedMemory(client_id, size, id, |
317 shared_memory_handle); | 374 shared_memory_handle); |
318 } | 375 } |
319 | 376 |
320 void DiscardableSharedMemoryManager::ClientDeletedDiscardableSharedMemory( | 377 void DiscardableSharedMemoryManager::ClientDeletedDiscardableSharedMemory( |
321 DiscardableSharedMemoryId id, | 378 int32_t id, |
322 int client_id) { | 379 int client_id) { |
323 DeletedDiscardableSharedMemory(id, client_id); | 380 DeletedDiscardableSharedMemory(id, client_id); |
324 } | 381 } |
325 | 382 |
326 void DiscardableSharedMemoryManager::ClientRemoved(int client_id) { | 383 void DiscardableSharedMemoryManager::ClientRemoved(int client_id) { |
327 base::AutoLock lock(lock_); | 384 base::AutoLock lock(lock_); |
328 | 385 |
329 auto it = clients_.find(client_id); | 386 auto it = clients_.find(client_id); |
330 if (it == clients_.end()) | 387 if (it == clients_.end()) |
331 return; | 388 return; |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
373 case base::MemoryState::SUSPENDED: | 430 case base::MemoryState::SUSPENDED: |
374 // Note that SUSPENDED never occurs in the main browser process so far. | 431 // Note that SUSPENDED never occurs in the main browser process so far. |
375 // Fall through. | 432 // Fall through. |
376 case base::MemoryState::UNKNOWN: | 433 case base::MemoryState::UNKNOWN: |
377 NOTREACHED(); | 434 NOTREACHED(); |
378 break; | 435 break; |
379 } | 436 } |
380 } | 437 } |
381 | 438 |
382 void DiscardableSharedMemoryManager::AllocateLockedDiscardableSharedMemory( | 439 void DiscardableSharedMemoryManager::AllocateLockedDiscardableSharedMemory( |
383 base::ProcessHandle process_handle, | |
384 int client_id, | 440 int client_id, |
385 size_t size, | 441 size_t size, |
386 DiscardableSharedMemoryId id, | 442 int32_t id, |
387 base::SharedMemoryHandle* shared_memory_handle) { | 443 base::SharedMemoryHandle* shared_memory_handle) { |
388 base::AutoLock lock(lock_); | 444 base::AutoLock lock(lock_); |
389 | 445 |
390 // Make sure |id| is not already in use. | 446 // Make sure |id| is not already in use. |
391 MemorySegmentMap& client_segments = clients_[client_id]; | 447 MemorySegmentMap& client_segments = clients_[client_id]; |
392 if (client_segments.find(id) != client_segments.end()) { | 448 if (client_segments.find(id) != client_segments.end()) { |
393 LOG(ERROR) << "Invalid discardable shared memory ID"; | 449 LOG(ERROR) << "Invalid discardable shared memory ID"; |
394 *shared_memory_handle = base::SharedMemory::NULLHandle(); | 450 *shared_memory_handle = base::SharedMemory::NULLHandle(); |
395 return; | 451 return; |
396 } | 452 } |
(...skipping 12 matching lines...) Expand all Loading... | |
409 if (bytes_allocated_ > limit) | 465 if (bytes_allocated_ > limit) |
410 ReduceMemoryUsageUntilWithinLimit(limit); | 466 ReduceMemoryUsageUntilWithinLimit(limit); |
411 | 467 |
412 std::unique_ptr<base::DiscardableSharedMemory> memory( | 468 std::unique_ptr<base::DiscardableSharedMemory> memory( |
413 new base::DiscardableSharedMemory); | 469 new base::DiscardableSharedMemory); |
414 if (!memory->CreateAndMap(size)) { | 470 if (!memory->CreateAndMap(size)) { |
415 *shared_memory_handle = base::SharedMemory::NULLHandle(); | 471 *shared_memory_handle = base::SharedMemory::NULLHandle(); |
416 return; | 472 return; |
417 } | 473 } |
418 | 474 |
419 if (!memory->ShareToProcess(process_handle, shared_memory_handle)) { | 475 *shared_memory_handle = base::SharedMemory::DuplicateHandle(memory->handle()); |
dcheng
2016/11/25 00:07:08
Nit: I would just DuplicateHandle up in the Proxy
Peng
2016/11/25 16:41:53
Because the handle inside of memory will be closed
dcheng
2016/11/28 18:26:54
Hmm... I was hoping we could wrap this in sort of
Peng
2016/11/29 15:20:13
Done.
| |
420 LOG(ERROR) << "Cannot share discardable memory segment"; | |
421 *shared_memory_handle = base::SharedMemory::NULLHandle(); | |
422 return; | |
423 } | |
424 | 476 |
425 // Close file descriptor to avoid running out. | 477 // Close file descriptor to avoid running out. |
426 memory->Close(); | 478 memory->Close(); |
427 | 479 |
428 base::CheckedNumeric<size_t> checked_bytes_allocated = bytes_allocated_; | 480 base::CheckedNumeric<size_t> checked_bytes_allocated = bytes_allocated_; |
429 checked_bytes_allocated += memory->mapped_size(); | 481 checked_bytes_allocated += memory->mapped_size(); |
430 if (!checked_bytes_allocated.IsValid()) { | 482 if (!checked_bytes_allocated.IsValid()) { |
483 base::SharedMemory::CloseHandle(*shared_memory_handle); | |
431 *shared_memory_handle = base::SharedMemory::NULLHandle(); | 484 *shared_memory_handle = base::SharedMemory::NULLHandle(); |
432 return; | 485 return; |
433 } | 486 } |
434 | 487 |
435 bytes_allocated_ = checked_bytes_allocated.ValueOrDie(); | 488 bytes_allocated_ = checked_bytes_allocated.ValueOrDie(); |
436 BytesAllocatedChanged(bytes_allocated_); | 489 BytesAllocatedChanged(bytes_allocated_); |
437 | 490 |
438 scoped_refptr<MemorySegment> segment(new MemorySegment(std::move(memory))); | 491 scoped_refptr<MemorySegment> segment(new MemorySegment(std::move(memory))); |
439 client_segments[id] = segment.get(); | 492 client_segments[id] = segment.get(); |
440 segments_.push_back(segment.get()); | 493 segments_.push_back(segment.get()); |
441 std::push_heap(segments_.begin(), segments_.end(), CompareMemoryUsageTime); | 494 std::push_heap(segments_.begin(), segments_.end(), CompareMemoryUsageTime); |
442 | 495 |
443 if (bytes_allocated_ > memory_limit_) | 496 if (bytes_allocated_ > memory_limit_) |
444 ScheduleEnforceMemoryPolicy(); | 497 ScheduleEnforceMemoryPolicy(); |
445 } | 498 } |
446 | 499 |
447 void DiscardableSharedMemoryManager::DeletedDiscardableSharedMemory( | 500 void DiscardableSharedMemoryManager::DeletedDiscardableSharedMemory( |
448 DiscardableSharedMemoryId id, | 501 int32_t id, |
449 int client_id) { | 502 int client_id) { |
450 base::AutoLock lock(lock_); | 503 base::AutoLock lock(lock_); |
451 | 504 |
452 MemorySegmentMap& client_segments = clients_[client_id]; | 505 MemorySegmentMap& client_segments = clients_[client_id]; |
453 | 506 |
454 MemorySegmentMap::iterator segment_it = client_segments.find(id); | 507 MemorySegmentMap::iterator segment_it = client_segments.find(id); |
455 if (segment_it == client_segments.end()) { | 508 if (segment_it == client_segments.end()) { |
456 LOG(ERROR) << "Invalid discardable shared memory ID"; | 509 LOG(ERROR) << "Invalid discardable shared memory ID"; |
457 return; | 510 return; |
458 } | 511 } |
(...skipping 122 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
581 return; | 634 return; |
582 | 635 |
583 enforce_memory_policy_pending_ = true; | 636 enforce_memory_policy_pending_ = true; |
584 DCHECK(enforce_memory_policy_task_runner_); | 637 DCHECK(enforce_memory_policy_task_runner_); |
585 enforce_memory_policy_task_runner_->PostDelayedTask( | 638 enforce_memory_policy_task_runner_->PostDelayedTask( |
586 FROM_HERE, enforce_memory_policy_callback_, | 639 FROM_HERE, enforce_memory_policy_callback_, |
587 base::TimeDelta::FromMilliseconds(kEnforceMemoryPolicyDelayMs)); | 640 base::TimeDelta::FromMilliseconds(kEnforceMemoryPolicyDelayMs)); |
588 } | 641 } |
589 | 642 |
590 } // namespace discardable_memory | 643 } // namespace discardable_memory |
OLD | NEW |