Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "media/base/android/media_service_throttler.h" | |
| 6 | |
| 7 #include "base/bind.h" | |
| 8 #include "base/memory/ptr_util.h" | |
| 9 #include "base/threading/thread_task_runner_handle.h" | |
| 10 #include "base/time/default_tick_clock.h" | |
| 11 #include "media/base/android/media_server_crash_listener.h" | |
| 12 | |
| 13 namespace media { | |
| 14 | |
| 15 namespace { | |
| 16 | |
| 17 static base::LazyInstance<MediaServiceThrottler>::Leaky | |
| 18 g_media_service_throttler = LAZY_INSTANCE_INITIALIZER; | |
| 19 | |
| 20 // Period of inactivity after which we stop listening for MediaServer crashes. | |
| 21 // NOTE: Server crashes don't count as acticity. Only calls to | |
| 22 // GetDelayForClientCreation() do. | |
| 23 constexpr base::TimeDelta kReleaseInactivityDelay = | |
| 24 base::TimeDelta::FromMinutes(1); | |
| 25 | |
| 26 // Elapsed time between crashes needed to completely reset the media server | |
| 27 // crash count. | |
| 28 constexpr base::TimeDelta kTimeUntilCrashReset = | |
| 29 base::TimeDelta::FromMinutes(1); | |
| 30 | |
| 31 // Elapsed time between schedule calls needed to completely reset the | |
| 32 // scheduling clock. | |
| 33 constexpr base::TimeDelta kTimeUntilScheduleReset = | |
| 34 base::TimeDelta::FromMinutes(1); | |
| 35 | |
| 36 // Decay rate of server crashes, corresponding to a tolerable 'normal' crash | |
| 37 // rate. This means that we will decrement our crash rate by ~1 crash/minute. | |
| 38 const uint32_t kCrashDecayPeriodInMs = 60000; | |
| 39 | |
| 40 // Rate at which client creations will be exponentially throttled based on the | |
| 41 // number of media server crashes. | |
| 42 // NOTE: Since our exponential delay formula is 2^(server crashes), 0 server | |
| 43 // crashes still result in this delay being added once. | |
| 44 constexpr base::TimeDelta kBaseExponentialDelay = | |
| 45 base::TimeDelta::FromMilliseconds(120); | |
| 46 | |
| 47 // Base rate at which we schedule client creations. | |
| 48 // The minimal delay is |kLinearThrottlingDelay| + |kBaseExponentialDelay|. | |
| 49 // This corresponds to 0.2s. | |
| 50 constexpr base::TimeDelta kLinearThrottlingDelay = | |
| 51 base::TimeDelta::FromMilliseconds(80); | |
| 52 | |
| 53 // Max exponential throttling rate from media server crashes. | |
| 54 // The max delay will still be |kLinearThrottlingDelay| + | |
| 55 // |kMaxExponentialDelay|. This corresponds to 3s. | |
| 56 constexpr base::TimeDelta kMaxExponentialDelay = | |
| 57 base::TimeDelta::FromMilliseconds(2920); | |
| 58 | |
| 59 // Max number of clients to schedule immediately (e.g when loading a new page). | |
| 60 const uint32_t kMaxBurstClients = 10; | |
| 61 | |
| 62 // Sliding window of time during which we allow clients to be scheduled | |
| 63 // immediately, to accomodate for a "bursts" of requests when loading new pages. | |
| 64 const base::TimeDelta kMinDelayWindow = | |
| 65 (kLinearThrottlingDelay + kBaseExponentialDelay) * kMaxBurstClients; | |
| 66 | |
| 67 // The throttling progression based on number of crashes looks as follows: | |
| 68 // | |
| 69 // | # crashes | period | clients/sec | clients/mins | # burst clients | |
| 70 // | 0 | 200 ms | 5.0 | 300 | 10 | |
| 71 // | 1 | 320 ms | 3.1 | 188 | 6 | |
| 72 // | 2 | 560 ms | 1.8 | 107 | 4 | |
| 73 // | 3 | 1040 ms | 1.0 | 58 | 2 | |
| 74 // | 4 | 2000 ms | 0.5 | 30 | 1 | |
| 75 // | 5 | 3000 ms | 0.3 | 20 | 1 | |
| 76 // | 6 | 3000 ms | 0.3 | 20 | 1 | |
| 77 // | |
| 78 // NOTE: Since we use the floor function and a decay rate of 1 crash/minute when | |
| 79 // calculating the effective # of crashes, a single crash per minute will result | |
| 80 // in 0 effective crashes (since floor(1.0 - 'tiny decay') is 0). If we | |
| 81 // experience slightly more than 1 crash per 60 seconds, the effective number of | |
| 82 // crashes will go up as expected. | |
| 83 } | |
| 84 | |
| 85 // static | |
| 86 MediaServiceThrottler* MediaServiceThrottler::GetInstance() { | |
| 87 return g_media_service_throttler.Pointer(); | |
| 88 } | |
| 89 | |
| 90 MediaServiceThrottler::~MediaServiceThrottler() {} | |
| 91 | |
| 92 MediaServiceThrottler::MediaServiceThrottler() | |
| 93 : clock_(new base::DefaultTickClock()), | |
| 94 current_crashes_(0), | |
| 95 crash_listener_task_runner_(base::ThreadTaskRunnerHandle::Get()) { | |
| 96 // base::Unretained is safe because the MediaServiceThrottler is supposed to | |
| 97 // live until the process dies. | |
| 98 release_crash_listener_cb_ = base::Bind( | |
| 99 &MediaServiceThrottler::ReleaseCrashListener, base::Unretained(this)); | |
| 100 EnsureCrashListenerStarted(); | |
| 101 } | |
| 102 | |
| 103 void MediaServiceThrottler::SetTickClockForTesting(base::TickClock* clock) { | |
| 104 clock_.reset(clock); | |
| 105 } | |
| 106 | |
| 107 base::TimeDelta MediaServiceThrottler::GetBaseThrottlingRateForTesting() { | |
| 108 return kBaseExponentialDelay + kLinearThrottlingDelay; | |
| 109 } | |
| 110 | |
| 111 void MediaServiceThrottler::ResetInternalStateForTesting() { | |
| 112 last_server_crash_ = base::TimeTicks(); | |
| 113 last_schedule_call_ = base::TimeTicks(); | |
| 114 next_schedulable_slot_ = clock_->NowTicks(); | |
| 115 last_current_crash_update_time_ = clock_->NowTicks(); | |
| 116 current_crashes_ = 0.0; | |
| 117 } | |
| 118 | |
| 119 base::TimeDelta MediaServiceThrottler::GetDelayForClientCreation() { | |
| 120 // Make sure the listener is started and the crashes decayed. | |
| 121 EnsureCrashListenerStarted(); | |
| 122 UpdateServerCrashes(); | |
| 123 | |
| 124 base::TimeTicks now = clock_->NowTicks(); | |
| 125 | |
| 126 // If we are passed the next time slot or if it has been 1 minute since the | |
| 127 // last call to GetDelayForClientCreation(), reset the next time to now. | |
| 128 if (now > next_schedulable_slot_ || | |
| 129 (now - last_schedule_call_) > kTimeUntilScheduleReset) { | |
| 130 next_schedulable_slot_ = now; | |
| 131 } | |
| 132 | |
| 133 last_schedule_call_ = now; | |
| 134 | |
| 135 // Increment the next scheduled time between 0.2s and 3s, which allows the | |
| 136 // creation of between 50 and 3 clients per 10s. | |
| 137 next_schedulable_slot_ += | |
| 138 kLinearThrottlingDelay + GetThrottlingDelayFromServerCrashes(); | |
| 139 | |
| 140 // Calculate how long to delay the creation so it isn't scheduled before | |
| 141 // |next_schedulable_slot_|. | |
| 142 base::TimeDelta delay = next_schedulable_slot_ - now; | |
| 143 | |
| 144 // If the scheduling delay is low enough, schedule it immediately instead. | |
| 145 // This allows up to kMaxBurstClients clients to be scheduled immediately. | |
| 146 if (delay <= kMinDelayWindow) | |
| 147 return base::TimeDelta(); | |
| 148 | |
| 149 return delay; | |
| 150 } | |
| 151 | |
| 152 base::TimeDelta MediaServiceThrottler::GetThrottlingDelayFromServerCrashes() { | |
| 153 // The combination of rounding down the number of crashes down and decaying | |
| 154 // at the rate of 1 crash / min means that a single crash will very quickly be | |
| 155 // rounded down to 0. Effectively, this means that we only start exponentially | |
| 156 // backing off if we have more than 1 crash in a 60 second window. | |
| 157 uint32_t num_crashes = std::floor(current_crashes_); | |
|
DaleCurtis
2016/11/15 00:49:32
Just casting to uint32_t should be sufficient.
tguilbert
2016/11/15 19:29:07
Done.
| |
| 158 DCHECK_GE(num_crashes, 0u); | |
| 159 | |
| 160 // Prevents overflow/undefined behavior. We already reach kMaxExponentialDelay | |
| 161 // at 5 crashes in any case. | |
| 162 num_crashes = std::min(num_crashes, 10u); | |
| 163 | |
| 164 return std::min(kBaseExponentialDelay * (1 << num_crashes), | |
| 165 kMaxExponentialDelay); | |
| 166 } | |
| 167 | |
| 168 void MediaServiceThrottler::OnMediaServerCrash() { | |
| 169 UpdateServerCrashes(); | |
| 170 | |
| 171 last_server_crash_ = clock_->NowTicks(); | |
| 172 current_crashes_ += 1.0; | |
| 173 } | |
| 174 | |
| 175 void MediaServiceThrottler::UpdateServerCrashes() { | |
| 176 base::TimeTicks now = clock_->NowTicks(); | |
| 177 base::TimeDelta time_since_last_crash = now - last_server_crash_; | |
| 178 | |
| 179 if (time_since_last_crash > kTimeUntilCrashReset) { | |
| 180 // Reset the number of crashes if we haven't had a crash in the past minute. | |
| 181 current_crashes_ = 0.0; | |
| 182 } else { | |
| 183 // Decay at the rate of 1 crash/minute otherwise. | |
| 184 double decay = (now - last_current_crash_update_time_).InMillisecondsF() / | |
| 185 kCrashDecayPeriodInMs; | |
| 186 current_crashes_ = std::max(0.0, current_crashes_ - decay); | |
| 187 } | |
| 188 | |
| 189 last_current_crash_update_time_ = now; | |
| 190 } | |
| 191 | |
| 192 void MediaServiceThrottler::ReleaseCrashListener() { | |
| 193 crash_listener_.reset(nullptr); | |
| 194 } | |
| 195 | |
| 196 void MediaServiceThrottler::EnsureCrashListenerStarted() { | |
| 197 if (!crash_listener_) { | |
| 198 // base::Unretained is safe here because both the MediaServiceThrottler and | |
| 199 // the MediaServerCrashListener live until the process is terminated. | |
| 200 crash_listener_ = base::MakeUnique<MediaServerCrashListener>( | |
| 201 base::Bind(&MediaServiceThrottler::OnMediaServerCrash, | |
| 202 base::Unretained(this)), | |
| 203 crash_listener_task_runner_); | |
| 204 } else { | |
| 205 crash_listener_->EnsureListening(); | |
| 206 } | |
| 207 | |
| 208 // Cancels outstanding/pending versions of the callback. | |
| 209 cancelable_release_crash_listener_cb_.Reset(release_crash_listener_cb_); | |
| 210 | |
| 211 // Schedule the release of |crash_listener_| a minute from now. This will be | |
| 212 // updated anytime GetDelayForClientCreation() is called. | |
| 213 crash_listener_task_runner_->PostDelayedTask( | |
| 214 FROM_HERE, cancelable_release_crash_listener_cb_.callback(), | |
| 215 kReleaseInactivityDelay); | |
| 216 } | |
| 217 | |
| 218 bool MediaServiceThrottler::IsCrashListenerAliveForTesting() { | |
| 219 return !!crash_listener_; | |
| 220 } | |
| 221 | |
| 222 void MediaServiceThrottler::SetCrashListenerTaskRunnerForTesting( | |
| 223 scoped_refptr<base::SingleThreadTaskRunner> crash_listener_task_runner) { | |
| 224 // Set the task runner so |crash_listener_| be deleted on the right thread. | |
| 225 crash_listener_task_runner_ = crash_listener_task_runner; | |
| 226 | |
| 227 // Re-create the crash listener. | |
| 228 crash_listener_ = base::MakeUnique<MediaServerCrashListener>( | |
| 229 base::Closure(), crash_listener_task_runner_); | |
| 230 } | |
| 231 | |
| 232 } // namespace media | |
| OLD | NEW |