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

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: Fixed. 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 213 matching lines...) Expand 10 before | Expand all | Expand 10 after
680 stats.title = contents->GetTitle(); 675 stats.title = contents->GetTitle();
681 stats.tab_contents_id = IdFromWebContents(contents); 676 stats.tab_contents_id = IdFromWebContents(contents);
682 stats_list->push_back(stats); 677 stats_list->push_back(stats);
683 } 678 }
684 } 679 }
685 } 680 }
686 681
687 // This function is called when |update_timer_| fires. It will adjust the clock 682 // This function is called when |update_timer_| fires. It will adjust the clock
688 // if needed (if it detects that the machine was asleep) and will fire the stats 683 // if needed (if it detects that the machine was asleep) and will fire the stats
689 // updating on ChromeOS via the delegate. This function also tries to purge 684 // updating on ChromeOS via the delegate. This function also tries to purge
690 // cache memory and suspend tabs which becomes and keeps backgrounded for a 685 // cache memory.
691 // while.
692 void TabManager::UpdateTimerCallback() { 686 void TabManager::UpdateTimerCallback() {
693 // If Chrome is shutting down, do not do anything. 687 // If Chrome is shutting down, do not do anything.
694 if (g_browser_process->IsShuttingDown()) 688 if (g_browser_process->IsShuttingDown())
695 return; 689 return;
696 690
697 if (BrowserList::GetInstance()->empty()) 691 if (BrowserList::GetInstance()->empty())
698 return; 692 return;
699 693
700 // Check for a discontinuity in time caused by the machine being suspended.
701 if (!last_adjust_time_.is_null()) {
702 TimeDelta suspend_time = NowTicks() - last_adjust_time_;
703 if (suspend_time.InSeconds() > kSuspendThresholdSeconds) {
704 // System was probably suspended, move the event timers forward in time so
705 // when they get subtracted out later, "uptime" is being counted.
706 start_time_ += suspend_time;
707 if (!last_discard_time_.is_null())
708 last_discard_time_ += suspend_time;
709 }
710 }
711 last_adjust_time_ = NowTicks(); 694 last_adjust_time_ = NowTicks();
712 695
713 #if defined(OS_CHROMEOS) 696 #if defined(OS_CHROMEOS)
714 TabStatsList stats_list = GetTabStats(); 697 TabStatsList stats_list = GetTabStats();
715 // This starts the CrOS specific OOM adjustments in /proc/<pid>/oom_score_adj. 698 // This starts the CrOS specific OOM adjustments in /proc/<pid>/oom_score_adj.
716 delegate_->AdjustOomPriorities(stats_list); 699 delegate_->AdjustOomPriorities(stats_list);
717 #endif 700 #endif
718 701
719 PurgeAndSuspendBackgroundedTabs(); 702 PurgeBackgroundedTabsIfNeeded();
720 } 703 }
721 704
722 TabManager::PurgeAndSuspendState TabManager::GetNextPurgeAndSuspendState( 705 base::TimeDelta TabManager::GetTimeToPurge(
723 content::WebContents* content, 706 base::TimeDelta min_time_to_purge) const {
724 base::TimeTicks current_time, 707 return min_time_to_purge + base::TimeDelta::FromMinutes(base::RandGenerator(
725 const base::TimeDelta& time_to_first_suspension) const { 708 kRangeBetweenMinAndMaxTimeToPurgeInMinutes));
726 DCHECK(content);
727 PurgeAndSuspendState state =
728 GetWebContentsData(content)->GetPurgeAndSuspendState();
729
730 auto time_passed = current_time -
731 GetWebContentsData(content)->LastPurgeAndSuspendModifiedTime();
732 switch (state) {
733 case RUNNING:
734 if (time_passed > time_to_first_suspension)
735 return SUSPENDED;
736 break;
737 case RESUMED:
738 if (time_passed > kDurationOfRendererResumption)
739 return SUSPENDED;
740 break;
741 case SUSPENDED:
742 if (time_passed > kDurationOfRendererSuspension)
743 return RESUMED;
744 break;
745 }
746 return state;
747 } 709 }
748 710
749 void TabManager::PurgeAndSuspendBackgroundedTabs() { 711 bool TabManager::ShouldPurgeAtTime(content::WebContents* content,
712 base::TimeTicks current_time) const {
713 if (GetWebContentsData(content)->IsPurged())
714 return false;
715
716 base::TimeDelta time_passed =
717 current_time - GetWebContentsData(content)->LastInactiveTime();
718 return time_passed > GetWebContentsData(content)->TimeToPurge();
719 }
720
721 void TabManager::PurgeBackgroundedTabsIfNeeded() {
750 base::TimeTicks current_time = NowTicks(); 722 base::TimeTicks current_time = NowTicks();
751 auto tab_stats = GetUnsortedTabStats(); 723 auto tab_stats = GetUnsortedTabStats();
752 for (auto& tab : tab_stats) { 724 for (auto& tab : tab_stats) {
753 if (!tab.render_process_host->IsProcessBackgrounded()) 725 if (!tab.render_process_host->IsProcessBackgrounded())
754 continue; 726 continue;
755 if (!CanSuspendBackgroundedRenderer(tab.child_process_host_id)) 727 if (!CanSuspendBackgroundedRenderer(tab.child_process_host_id))
756 continue; 728 continue;
757 729
758 WebContents* content = GetWebContentsById(tab.tab_contents_id); 730 WebContents* content = GetWebContentsById(tab.tab_contents_id);
759 if (!content) 731 if (!content)
760 continue; 732 continue;
761 733
762 PurgeAndSuspendState current_state = 734 bool purge_now = ShouldPurgeAtTime(content, current_time);
763 GetWebContentsData(content)->GetPurgeAndSuspendState(); 735 if (!purge_now)
764 // If the tab's purge-and-suspend state is not RUNNING, the tab should be
765 // backgrounded. Since tab.last_hidden is updated everytime the tab is
766 // hidden, we should see tab.last_hidden < last_modified_time.
767 DCHECK(current_state == RUNNING ||
768 tab.last_hidden <
769 GetWebContentsData(content)->LastPurgeAndSuspendModifiedTime());
770 PurgeAndSuspendState next_state = GetNextPurgeAndSuspendState(
771 content, current_time, time_to_first_suspension_);
772 if (current_state == next_state)
773 continue; 736 continue;
774 737
775 // TODO(hajimehoshi): Now calling PurgeAndSuspend is implemented without 738 GetWebContentsData(content)->SetPurgeState(true);
776 // timers for simplicity, so PurgeAndSuspend is called even after the 739 // TODO(tasak): rename PurgeAndSuspend with a better name, e.g.
777 // renderer is purged and suspended once. This should be replaced with 740 // RequestPurgeCache, because we don't suspend any renderers.
778 // timers if we want necessary and sufficient signals. 741 tab.render_process_host->PurgeAndSuspend();
779 GetWebContentsData(content)->SetPurgeAndSuspendState(next_state);
780 switch (next_state) {
781 case SUSPENDED:
782 tab.render_process_host->PurgeAndSuspend();
783 break;
784 case RESUMED:
785 tab.render_process_host->Resume();
786 break;
787 case RUNNING:
788 NOTREACHED();
789 }
790 } 742 }
791 } 743 }
792 744
793 WebContents* TabManager::DiscardWebContentsAt(int index, TabStripModel* model) { 745 WebContents* TabManager::DiscardWebContentsAt(int index, TabStripModel* model) {
794 // Can't discard active index. 746 // Can't discard active index.
795 if (model->active_index() == index) 747 if (model->active_index() == index)
796 return nullptr; 748 return nullptr;
797 749
798 WebContents* old_contents = model->GetWebContentsAt(index); 750 WebContents* old_contents = model->GetWebContentsAt(index);
799 751
(...skipping 19 matching lines...) Expand all
819 null_contents->SetLastActiveTime(old_contents->GetLastActiveTime()); 771 null_contents->SetLastActiveTime(old_contents->GetLastActiveTime());
820 // Copy over the discard count. 772 // Copy over the discard count.
821 WebContentsData::CopyState(old_contents, null_contents); 773 WebContentsData::CopyState(old_contents, null_contents);
822 774
823 // Replace the discarded tab with the null version. 775 // Replace the discarded tab with the null version.
824 model->ReplaceWebContentsAt(index, null_contents); 776 model->ReplaceWebContentsAt(index, null_contents);
825 // Mark the tab so it will reload when clicked on. 777 // Mark the tab so it will reload when clicked on.
826 GetWebContentsData(null_contents)->SetDiscardState(true); 778 GetWebContentsData(null_contents)->SetDiscardState(true);
827 GetWebContentsData(null_contents)->IncrementDiscardCount(); 779 GetWebContentsData(null_contents)->IncrementDiscardCount();
828 780
781 // Make the tab PURGED to avoid purging null_contents.
782 GetWebContentsData(null_contents)->SetPurgeState(true);
783
829 // Discard the old tab's renderer. 784 // Discard the old tab's renderer.
830 // TODO(jamescook): This breaks script connections with other tabs. 785 // TODO(jamescook): This breaks script connections with other tabs.
831 // Find a different approach that doesn't do that, perhaps based on navigation 786 // Find a different approach that doesn't do that, perhaps based on navigation
832 // to swappedout://. 787 // to swappedout://.
833 delete old_contents; 788 delete old_contents;
834 recent_tab_discard_ = true; 789 recent_tab_discard_ = true;
835 790
836 return null_contents; 791 return null_contents;
837 } 792 }
838 793
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after
880 data->SetRecentlyAudible(current_state); 835 data->SetRecentlyAudible(current_state);
881 data->SetLastAudioChangeTime(NowTicks()); 836 data->SetLastAudioChangeTime(NowTicks());
882 } 837 }
883 } 838 }
884 839
885 void TabManager::ActiveTabChanged(content::WebContents* old_contents, 840 void TabManager::ActiveTabChanged(content::WebContents* old_contents,
886 content::WebContents* new_contents, 841 content::WebContents* new_contents,
887 int index, 842 int index,
888 int reason) { 843 int reason) {
889 GetWebContentsData(new_contents)->SetDiscardState(false); 844 GetWebContentsData(new_contents)->SetDiscardState(false);
890 GetWebContentsData(new_contents)->SetPurgeAndSuspendState(RUNNING); 845 GetWebContentsData(new_contents)->SetPurgeState(false);
891 // If |old_contents| is set, that tab has switched from being active to 846 // If |old_contents| is set, that tab has switched from being active to
892 // inactive, so record the time of that transition. 847 // inactive, so record the time of that transition.
893 if (old_contents) 848 if (old_contents) {
894 GetWebContentsData(old_contents)->SetLastInactiveTime(NowTicks()); 849 GetWebContentsData(old_contents)->SetLastInactiveTime(NowTicks());
850 // Re-setting time-to-purge every time a tab becomes inactive.
851 GetWebContentsData(old_contents)
852 ->SetTimeToPurge(GetTimeToPurge(min_time_to_purge_));
853 }
895 } 854 }
896 855
897 void TabManager::TabInsertedAt(TabStripModel* tab_strip_model, 856 void TabManager::TabInsertedAt(TabStripModel* tab_strip_model,
898 content::WebContents* contents, 857 content::WebContents* contents,
899 int index, 858 int index,
900 bool foreground) { 859 bool foreground) {
901 // Only interested in background tabs, as foreground tabs get taken care of by 860 // Only interested in background tabs, as foreground tabs get taken care of by
902 // ActiveTabChanged. 861 // ActiveTabChanged.
903 if (foreground) 862 if (foreground)
904 return; 863 return;
905 864
906 // A new background tab is similar to having a tab switch from being active to 865 // A new background tab is similar to having a tab switch from being active to
907 // inactive. 866 // inactive.
908 GetWebContentsData(contents)->SetLastInactiveTime(NowTicks()); 867 GetWebContentsData(contents)->SetLastInactiveTime(NowTicks());
868 // Re-setting time-to-purge every time a tab becomes inactive.
869 GetWebContentsData(contents)->SetTimeToPurge(
870 GetTimeToPurge(min_time_to_purge_));
909 } 871 }
910 872
911 bool TabManager::IsMediaTab(WebContents* contents) const { 873 bool TabManager::IsMediaTab(WebContents* contents) const {
912 if (contents->WasRecentlyAudible()) 874 if (contents->WasRecentlyAudible())
913 return true; 875 return true;
914 876
915 scoped_refptr<MediaStreamCaptureIndicator> media_indicator = 877 scoped_refptr<MediaStreamCaptureIndicator> media_indicator =
916 MediaCaptureDevicesDispatcher::GetInstance() 878 MediaCaptureDevicesDispatcher::GetInstance()
917 ->GetMediaStreamCaptureIndicator(); 879 ->GetMediaStreamCaptureIndicator();
918 if (media_indicator->IsCapturingUserMedia(contents) || 880 if (media_indicator->IsCapturingUserMedia(contents) ||
(...skipping 113 matching lines...) Expand 10 before | Expand all | Expand 10 after
1032 // platform. 994 // platform.
1033 std::string allow_multiple_discards = variations::GetVariationParamValue( 995 std::string allow_multiple_discards = variations::GetVariationParamValue(
1034 features::kAutomaticTabDiscarding.name, "AllowMultipleDiscards"); 996 features::kAutomaticTabDiscarding.name, "AllowMultipleDiscards");
1035 return (allow_multiple_discards != "true"); 997 return (allow_multiple_discards != "true");
1036 #else 998 #else
1037 return false; 999 return false;
1038 #endif 1000 #endif
1039 } 1001 }
1040 1002
1041 } // namespace memory 1003 } // namespace memory
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698