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

Side by Side Diff: chrome/browser/memory/tab_manager.cc

Issue 2711093002: Purge once random minutes(between 30min and 60min) after backgrounded. (Closed)
Patch Set: Added a browser test. Created 3 years, 9 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
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 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 "chrome/browser/memory/tab_manager.h" 5 #include "chrome/browser/memory/tab_manager.h"
6 6
7 #include <stddef.h> 7 #include <stddef.h>
8 8
9 #include <algorithm> 9 #include <algorithm>
10 #include <set> 10 #include <set>
11 #include <vector> 11 #include <vector>
12 12
13 #include "base/bind.h" 13 #include "base/bind.h"
14 #include "base/bind_helpers.h" 14 #include "base/bind_helpers.h"
15 #include "base/command_line.h" 15 #include "base/command_line.h"
16 #include "base/feature_list.h" 16 #include "base/feature_list.h"
17 #include "base/macros.h" 17 #include "base/macros.h"
18 #include "base/memory/memory_pressure_monitor.h" 18 #include "base/memory/memory_pressure_monitor.h"
19 #include "base/metrics/field_trial.h" 19 #include "base/metrics/field_trial.h"
20 #include "base/metrics/histogram_macros.h" 20 #include "base/metrics/histogram_macros.h"
21 #include "base/observer_list.h" 21 #include "base/observer_list.h"
22 #include "base/process/process.h" 22 #include "base/process/process.h"
23 #include "base/rand_util.h"
23 #include "base/strings/string16.h" 24 #include "base/strings/string16.h"
24 #include "base/strings/string_number_conversions.h" 25 #include "base/strings/string_number_conversions.h"
25 #include "base/strings/string_util.h" 26 #include "base/strings/string_util.h"
26 #include "base/strings/utf_string_conversions.h" 27 #include "base/strings/utf_string_conversions.h"
27 #include "base/threading/thread.h" 28 #include "base/threading/thread.h"
28 #include "base/threading/thread_task_runner_handle.h" 29 #include "base/threading/thread_task_runner_handle.h"
29 #include "base/time/tick_clock.h" 30 #include "base/time/tick_clock.h"
30 #include "build/build_config.h" 31 #include "build/build_config.h"
31 #include "chrome/browser/browser_process.h" 32 #include "chrome/browser/browser_process.h"
32 #include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h" 33 #include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
73 // value. 74 // value.
74 const int kAdjustmentIntervalSeconds = 10; 75 const int kAdjustmentIntervalSeconds = 10;
75 76
76 #if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_CHROMEOS) 77 #if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_CHROMEOS)
77 // For each period of this length record a statistic to indicate whether or not 78 // For each period of this length record a statistic to indicate whether or not
78 // the user experienced a low memory event. If this interval is changed, 79 // the user experienced a low memory event. If this interval is changed,
79 // Tabs.Discard.DiscardInLastMinute must be replaced with a new statistic. 80 // Tabs.Discard.DiscardInLastMinute must be replaced with a new statistic.
80 const int kRecentTabDiscardIntervalSeconds = 60; 81 const int kRecentTabDiscardIntervalSeconds = 60;
81 #endif 82 #endif
82 83
83 // If there has been no priority adjustment in this interval, assume the 84 // The range between minimum and maximum time-to-purge.
84 // machine was suspended and correct the timing statistics. 85 // The minimum time-to-purge is min_time_to_purge_ and its default value is
85 const int kSuspendThresholdSeconds = kAdjustmentIntervalSeconds * 4; 86 // kDefaultMinTimeToPurge.
86 87 // An inactive background tab's cache will be purged the time-to-purge
87 // A suspended renderer is suspended for this duration. 88 // after the tab is backgrounded.
88 constexpr base::TimeDelta kDurationOfRendererSuspension = 89 const unsigned int kRangeBetweenMinAndMaxTimeToPurgeInMinutes = 30;
89 base::TimeDelta::FromSeconds(1200);
90
91 // A resumed renderer is resumed for this duration.
92 constexpr base::TimeDelta kDurationOfRendererResumption =
93 base::TimeDelta::FromSeconds(10);
94 90
95 // The time during which a tab is protected from discarding after it stops being 91 // The time during which a tab is protected from discarding after it stops being
96 // audible. 92 // audible.
97 const int kAudioProtectionTimeSeconds = 60; 93 const int kAudioProtectionTimeSeconds = 60;
98 94
99 int FindTabStripModelById(int64_t target_web_contents_id, 95 int FindTabStripModelById(int64_t target_web_contents_id,
100 TabStripModel** model) { 96 TabStripModel** model) {
101 DCHECK(model); 97 DCHECK(model);
102 for (auto* browser : *BrowserList::GetInstance()) { 98 for (auto* browser : *BrowserList::GetInstance()) {
103 TabStripModel* local_model = browser->tab_strip_model(); 99 TabStripModel* local_model = browser->tab_strip_model();
(...skipping 28 matching lines...) Expand all
132 base::MemoryPressureListener::MemoryPressureLevel level) { 128 base::MemoryPressureListener::MemoryPressureLevel level) {
133 content::MemoryPressureController::SendPressureNotification( 129 content::MemoryPressureController::SendPressureNotification(
134 render_process_host, level); 130 render_process_host, level);
135 } 131 }
136 132
137 } // namespace 133 } // namespace
138 134
139 //////////////////////////////////////////////////////////////////////////////// 135 ////////////////////////////////////////////////////////////////////////////////
140 // TabManager 136 // TabManager
141 137
142 constexpr base::TimeDelta TabManager::kDefaultTimeToFirstPurge; 138 constexpr base::TimeDelta TabManager::kDefaultMinTimeToPurge;
143 139
144 TabManager::TabManager() 140 TabManager::TabManager()
145 : discard_count_(0), 141 : discard_count_(0),
146 recent_tab_discard_(false), 142 recent_tab_discard_(false),
147 discard_once_(false), 143 discard_once_(false),
148 #if !defined(OS_CHROMEOS) 144 #if !defined(OS_CHROMEOS)
149 minimum_protection_time_(base::TimeDelta::FromMinutes(10)), 145 minimum_protection_time_(base::TimeDelta::FromMinutes(10)),
150 #endif 146 #endif
151 browser_tab_strip_tracker_(this, nullptr, nullptr), 147 browser_tab_strip_tracker_(this, nullptr, nullptr),
152 test_tick_clock_(nullptr), 148 test_tick_clock_(nullptr),
(...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after
221 if (level == base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL) { 217 if (level == base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL) {
222 OnMemoryPressure(level); 218 OnMemoryPressure(level);
223 } 219 }
224 } 220 }
225 #endif 221 #endif
226 // purge-and-suspend param is used for Purge+Suspend finch experiment 222 // purge-and-suspend param is used for Purge+Suspend finch experiment
227 // in the following way: 223 // in the following way:
228 // https://docs.google.com/document/d/1hPHkKtXXBTlsZx9s-9U17XC-ofEIzPo9FYbBEc7 PPbk/edit?usp=sharing 224 // https://docs.google.com/document/d/1hPHkKtXXBTlsZx9s-9U17XC-ofEIzPo9FYbBEc7 PPbk/edit?usp=sharing
229 std::string purge_and_suspend_time = variations::GetVariationParamValue( 225 std::string purge_and_suspend_time = variations::GetVariationParamValue(
230 "PurgeAndSuspend", "purge-and-suspend-time"); 226 "PurgeAndSuspend", "purge-and-suspend-time");
231 unsigned int time_to_first_purge_sec = 0; 227 unsigned int min_time_to_purge_sec = 0;
232 if (purge_and_suspend_time.empty() || 228 if (purge_and_suspend_time.empty() ||
233 !base::StringToUint(purge_and_suspend_time, &time_to_first_purge_sec)) 229 !base::StringToUint(purge_and_suspend_time, &min_time_to_purge_sec))
234 time_to_first_suspension_ = kDefaultTimeToFirstPurge; 230 min_time_to_purge_ = kDefaultMinTimeToPurge;
235 else 231 else
236 time_to_first_suspension_ = 232 min_time_to_purge_ = base::TimeDelta::FromSeconds(min_time_to_purge_sec);
237 base::TimeDelta::FromSeconds(time_to_first_purge_sec);
238 } 233 }
239 234
240 void TabManager::Stop() { 235 void TabManager::Stop() {
241 update_timer_.Stop(); 236 update_timer_.Stop();
242 recent_tab_discard_timer_.Stop(); 237 recent_tab_discard_timer_.Stop();
243 memory_pressure_listener_.reset(); 238 memory_pressure_listener_.reset();
244 } 239 }
245 240
246 TabStatsList TabManager::GetTabStats() const { 241 TabStatsList TabManager::GetTabStats() const {
247 TabStatsList stats_list(GetUnsortedTabStats()); 242 TabStatsList stats_list(GetUnsortedTabStats());
(...skipping 198 matching lines...) Expand 10 before | Expand all | Expand 10 after
446 content::WebContents* TabManager::GetWebContentsById( 441 content::WebContents* TabManager::GetWebContentsById(
447 int64_t tab_contents_id) const { 442 int64_t tab_contents_id) const {
448 TabStripModel* model = nullptr; 443 TabStripModel* model = nullptr;
449 int index = FindTabStripModelById(tab_contents_id, &model); 444 int index = FindTabStripModelById(tab_contents_id, &model);
450 if (index == -1) 445 if (index == -1)
451 return nullptr; 446 return nullptr;
452 return model->GetWebContentsAt(index); 447 return model->GetWebContentsAt(index);
453 } 448 }
454 449
455 bool TabManager::CanSuspendBackgroundedRenderer(int render_process_id) const { 450 bool TabManager::CanSuspendBackgroundedRenderer(int render_process_id) const {
456 // A renderer can be suspended if it's not playing media. 451 // A renderer can be purged if it's not playing media.
457 auto tab_stats = GetUnsortedTabStats(); 452 auto tab_stats = GetUnsortedTabStats();
458 for (auto& tab : tab_stats) { 453 for (auto& tab : tab_stats) {
459 if (tab.child_process_host_id != render_process_id) 454 if (tab.child_process_host_id != render_process_id)
460 continue; 455 continue;
461 WebContents* web_contents = GetWebContentsById(tab.tab_contents_id); 456 WebContents* web_contents = GetWebContentsById(tab.tab_contents_id);
462 if (!web_contents) 457 if (!web_contents)
463 return false; 458 return false;
464 if (IsMediaTab(web_contents)) 459 if (IsMediaTab(web_contents))
465 return false; 460 return false;
466 } 461 }
(...skipping 212 matching lines...) Expand 10 before | Expand all | Expand 10 after
679 stats.title = contents->GetTitle(); 674 stats.title = contents->GetTitle();
680 stats.tab_contents_id = IdFromWebContents(contents); 675 stats.tab_contents_id = IdFromWebContents(contents);
681 stats_list->push_back(stats); 676 stats_list->push_back(stats);
682 } 677 }
683 } 678 }
684 } 679 }
685 680
686 // This function is called when |update_timer_| fires. It will adjust the clock 681 // This function is called when |update_timer_| fires. It will adjust the clock
687 // if needed (if it detects that the machine was asleep) and will fire the stats 682 // if needed (if it detects that the machine was asleep) and will fire the stats
688 // updating on ChromeOS via the delegate. This function also tries to purge 683 // updating on ChromeOS via the delegate. This function also tries to purge
689 // cache memory and suspend tabs which becomes and keeps backgrounded for a 684 // cache memory.
690 // while.
691 void TabManager::UpdateTimerCallback() { 685 void TabManager::UpdateTimerCallback() {
692 // If Chrome is shutting down, do not do anything. 686 // If Chrome is shutting down, do not do anything.
693 if (g_browser_process->IsShuttingDown()) 687 if (g_browser_process->IsShuttingDown())
694 return; 688 return;
695 689
696 if (BrowserList::GetInstance()->empty()) 690 if (BrowserList::GetInstance()->empty())
697 return; 691 return;
698 692
699 // Check for a discontinuity in time caused by the machine being suspended.
700 if (!last_adjust_time_.is_null()) {
701 TimeDelta suspend_time = NowTicks() - last_adjust_time_;
702 if (suspend_time.InSeconds() > kSuspendThresholdSeconds) {
703 // System was probably suspended, move the event timers forward in time so
704 // when they get subtracted out later, "uptime" is being counted.
705 start_time_ += suspend_time;
706 if (!last_discard_time_.is_null())
707 last_discard_time_ += suspend_time;
708 }
709 }
710 last_adjust_time_ = NowTicks(); 693 last_adjust_time_ = NowTicks();
711 694
712 #if defined(OS_CHROMEOS) 695 #if defined(OS_CHROMEOS)
713 TabStatsList stats_list = GetTabStats(); 696 TabStatsList stats_list = GetTabStats();
714 // This starts the CrOS specific OOM adjustments in /proc/<pid>/oom_score_adj. 697 // This starts the CrOS specific OOM adjustments in /proc/<pid>/oom_score_adj.
715 delegate_->AdjustOomPriorities(stats_list); 698 delegate_->AdjustOomPriorities(stats_list);
716 #endif 699 #endif
717 700
718 PurgeAndSuspendBackgroundedTabs(); 701 PurgeBackgroundedTabsIfNeeded();
719 } 702 }
720 703
721 TabManager::PurgeAndSuspendState TabManager::GetNextPurgeAndSuspendState( 704 base::TimeDelta TabManager::GetTimeToPurge(
722 content::WebContents* content, 705 base::TimeDelta min_time_to_purge) const {
723 base::TimeTicks current_time, 706 return min_time_to_purge + base::TimeDelta::FromMinutes(base::RandGenerator(
724 const base::TimeDelta& time_to_first_suspension) const { 707 kRangeBetweenMinAndMaxTimeToPurgeInMinutes));
725 DCHECK(content);
726 PurgeAndSuspendState state =
727 GetWebContentsData(content)->GetPurgeAndSuspendState();
728
729 auto time_passed = current_time -
730 GetWebContentsData(content)->LastPurgeAndSuspendModifiedTime();
731 switch (state) {
732 case RUNNING:
733 if (time_passed > time_to_first_suspension)
734 return SUSPENDED;
735 break;
736 case RESUMED:
737 if (time_passed > kDurationOfRendererResumption)
738 return SUSPENDED;
739 break;
740 case SUSPENDED:
741 if (time_passed > kDurationOfRendererSuspension)
742 return RESUMED;
743 break;
744 }
745 return state;
746 } 708 }
747 709
748 void TabManager::PurgeAndSuspendBackgroundedTabs() { 710 bool TabManager::ShouldPurgeAtTime(content::WebContents* content) const {
749 base::TimeTicks current_time = NowTicks(); 711 if (GetWebContentsData(content)->is_purged())
712 return false;
713
714 base::TimeDelta time_passed =
715 NowTicks() - GetWebContentsData(content)->LastInactiveTime();
716 return time_passed > GetWebContentsData(content)->time_to_purge();
717 }
718
719 void TabManager::PurgeBackgroundedTabsIfNeeded() {
750 auto tab_stats = GetUnsortedTabStats(); 720 auto tab_stats = GetUnsortedTabStats();
751 for (auto& tab : tab_stats) { 721 for (auto& tab : tab_stats) {
752 if (!tab.render_process_host->IsProcessBackgrounded()) 722 if (!tab.render_process_host->IsProcessBackgrounded())
753 continue; 723 continue;
754 if (!CanSuspendBackgroundedRenderer(tab.child_process_host_id)) 724 if (!CanSuspendBackgroundedRenderer(tab.child_process_host_id))
755 continue; 725 continue;
756 726
757 WebContents* content = GetWebContentsById(tab.tab_contents_id); 727 WebContents* content = GetWebContentsById(tab.tab_contents_id);
758 if (!content) 728 if (!content)
759 continue; 729 continue;
760 730
761 PurgeAndSuspendState current_state = 731 bool purge_now = ShouldPurgeAtTime(content);
762 GetWebContentsData(content)->GetPurgeAndSuspendState(); 732 if (!purge_now)
763 // If the tab's purge-and-suspend state is not RUNNING, the tab should be
764 // backgrounded. Since tab.last_hidden is updated everytime the tab is
765 // hidden, we should see tab.last_hidden < last_modified_time.
766 DCHECK(current_state == RUNNING ||
767 tab.last_hidden <
768 GetWebContentsData(content)->LastPurgeAndSuspendModifiedTime());
769 PurgeAndSuspendState next_state = GetNextPurgeAndSuspendState(
770 content, current_time, time_to_first_suspension_);
771 if (current_state == next_state)
772 continue; 733 continue;
773 734
774 // TODO(hajimehoshi): Now calling PurgeAndSuspend is implemented without 735 GetWebContentsData(content)->set_is_purged(true);
775 // timers for simplicity, so PurgeAndSuspend is called even after the 736 // TODO(tasak): rename PurgeAndSuspend with a better name, e.g.
776 // renderer is purged and suspended once. This should be replaced with 737 // RequestPurgeCache, because we don't suspend any renderers.
777 // timers if we want necessary and sufficient signals. 738 tab.render_process_host->PurgeAndSuspend();
778 GetWebContentsData(content)->SetPurgeAndSuspendState(next_state);
779 switch (next_state) {
780 case SUSPENDED:
781 tab.render_process_host->PurgeAndSuspend();
782 break;
783 case RESUMED:
784 tab.render_process_host->Resume();
785 break;
786 case RUNNING:
787 NOTREACHED();
788 }
789 } 739 }
790 } 740 }
791 741
792 WebContents* TabManager::DiscardWebContentsAt(int index, TabStripModel* model) { 742 WebContents* TabManager::DiscardWebContentsAt(int index, TabStripModel* model) {
793 // Can't discard active index. 743 // Can't discard active index.
794 if (model->active_index() == index) 744 if (model->active_index() == index)
795 return nullptr; 745 return nullptr;
796 746
797 WebContents* old_contents = model->GetWebContentsAt(index); 747 WebContents* old_contents = model->GetWebContentsAt(index);
798 748
(...skipping 19 matching lines...) Expand all
818 null_contents->SetLastActiveTime(old_contents->GetLastActiveTime()); 768 null_contents->SetLastActiveTime(old_contents->GetLastActiveTime());
819 // Copy over the discard count. 769 // Copy over the discard count.
820 WebContentsData::CopyState(old_contents, null_contents); 770 WebContentsData::CopyState(old_contents, null_contents);
821 771
822 // Replace the discarded tab with the null version. 772 // Replace the discarded tab with the null version.
823 model->ReplaceWebContentsAt(index, null_contents); 773 model->ReplaceWebContentsAt(index, null_contents);
824 // Mark the tab so it will reload when clicked on. 774 // Mark the tab so it will reload when clicked on.
825 GetWebContentsData(null_contents)->SetDiscardState(true); 775 GetWebContentsData(null_contents)->SetDiscardState(true);
826 GetWebContentsData(null_contents)->IncrementDiscardCount(); 776 GetWebContentsData(null_contents)->IncrementDiscardCount();
827 777
778 // Make the tab PURGED to avoid purging null_contents.
779 GetWebContentsData(null_contents)->set_is_purged(true);
780
828 // Discard the old tab's renderer. 781 // Discard the old tab's renderer.
829 // TODO(jamescook): This breaks script connections with other tabs. 782 // TODO(jamescook): This breaks script connections with other tabs.
830 // Find a different approach that doesn't do that, perhaps based on navigation 783 // Find a different approach that doesn't do that, perhaps based on navigation
831 // to swappedout://. 784 // to swappedout://.
832 delete old_contents; 785 delete old_contents;
833 recent_tab_discard_ = true; 786 recent_tab_discard_ = true;
834 787
835 return null_contents; 788 return null_contents;
836 } 789 }
837 790
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after
879 data->SetRecentlyAudible(current_state); 832 data->SetRecentlyAudible(current_state);
880 data->SetLastAudioChangeTime(NowTicks()); 833 data->SetLastAudioChangeTime(NowTicks());
881 } 834 }
882 } 835 }
883 836
884 void TabManager::ActiveTabChanged(content::WebContents* old_contents, 837 void TabManager::ActiveTabChanged(content::WebContents* old_contents,
885 content::WebContents* new_contents, 838 content::WebContents* new_contents,
886 int index, 839 int index,
887 int reason) { 840 int reason) {
888 GetWebContentsData(new_contents)->SetDiscardState(false); 841 GetWebContentsData(new_contents)->SetDiscardState(false);
889 GetWebContentsData(new_contents)->SetPurgeAndSuspendState(RUNNING); 842 GetWebContentsData(new_contents)->set_is_purged(false);
890 // If |old_contents| is set, that tab has switched from being active to 843 // If |old_contents| is set, that tab has switched from being active to
891 // inactive, so record the time of that transition. 844 // inactive, so record the time of that transition.
892 if (old_contents) 845 if (old_contents) {
893 GetWebContentsData(old_contents)->SetLastInactiveTime(NowTicks()); 846 GetWebContentsData(old_contents)->SetLastInactiveTime(NowTicks());
847 // Re-setting time-to-purge every time a tab becomes inactive.
848 GetWebContentsData(old_contents)
849 ->set_time_to_purge(GetTimeToPurge(min_time_to_purge_));
850 }
894 } 851 }
895 852
896 void TabManager::TabInsertedAt(TabStripModel* tab_strip_model, 853 void TabManager::TabInsertedAt(TabStripModel* tab_strip_model,
897 content::WebContents* contents, 854 content::WebContents* contents,
898 int index, 855 int index,
899 bool foreground) { 856 bool foreground) {
900 // Only interested in background tabs, as foreground tabs get taken care of by 857 // Only interested in background tabs, as foreground tabs get taken care of by
901 // ActiveTabChanged. 858 // ActiveTabChanged.
902 if (foreground) 859 if (foreground)
903 return; 860 return;
904 861
905 // A new background tab is similar to having a tab switch from being active to 862 // A new background tab is similar to having a tab switch from being active to
906 // inactive. 863 // inactive.
907 GetWebContentsData(contents)->SetLastInactiveTime(NowTicks()); 864 GetWebContentsData(contents)->SetLastInactiveTime(NowTicks());
865 // Re-setting time-to-purge every time a tab becomes inactive.
866 GetWebContentsData(contents)->set_time_to_purge(
867 GetTimeToPurge(min_time_to_purge_));
908 } 868 }
909 869
910 bool TabManager::IsMediaTab(WebContents* contents) const { 870 bool TabManager::IsMediaTab(WebContents* contents) const {
911 if (contents->WasRecentlyAudible()) 871 if (contents->WasRecentlyAudible())
912 return true; 872 return true;
913 873
914 scoped_refptr<MediaStreamCaptureIndicator> media_indicator = 874 scoped_refptr<MediaStreamCaptureIndicator> media_indicator =
915 MediaCaptureDevicesDispatcher::GetInstance() 875 MediaCaptureDevicesDispatcher::GetInstance()
916 ->GetMediaStreamCaptureIndicator(); 876 ->GetMediaStreamCaptureIndicator();
917 if (media_indicator->IsCapturingUserMedia(contents) || 877 if (media_indicator->IsCapturingUserMedia(contents) ||
(...skipping 113 matching lines...) Expand 10 before | Expand all | Expand 10 after
1031 // platform. 991 // platform.
1032 std::string allow_multiple_discards = variations::GetVariationParamValue( 992 std::string allow_multiple_discards = variations::GetVariationParamValue(
1033 features::kAutomaticTabDiscarding.name, "AllowMultipleDiscards"); 993 features::kAutomaticTabDiscarding.name, "AllowMultipleDiscards");
1034 return (allow_multiple_discards != "true"); 994 return (allow_multiple_discards != "true");
1035 #else 995 #else
1036 return false; 996 return false;
1037 #endif 997 #endif
1038 } 998 }
1039 999
1040 } // namespace memory 1000 } // namespace memory
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698