summaryrefslogtreecommitdiff
path: root/chromium/media/base/android/media_service_throttler.cc
blob: 603e5543365e7e600a9ba7c2413bf983587dac95 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/base/android/media_service_throttler.h"

#include <memory>

#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/default_tick_clock.h"
#include "media/base/android/media_server_crash_listener.h"

namespace media {

namespace {

// Period of inactivity after which we stop listening for MediaServer crashes.
// NOTE: Server crashes don't count as activity. Only calls to
// GetDelayForClientCreation() do.
constexpr auto kReleaseInactivityDelay = base::Minutes(1);

// Elapsed time between crashes needed to completely reset the media server
// crash count.
constexpr auto kTimeUntilCrashReset = base::Minutes(1);

// Elapsed time between schedule calls needed to completely reset the
// scheduling clock.
constexpr auto kTimeUntilScheduleReset = base::Minutes(1);

// Rate at which client creations will be exponentially throttled based on the
// number of media server crashes.
// NOTE: Since our exponential delay formula is 2^(server crashes), 0 server
// crashes still result in this delay being added once.
constexpr auto kBaseExponentialDelay = base::Milliseconds(120);

// Base rate at which we schedule client creations.
// The minimal delay is |kLinearThrottlingDelay| + |kBaseExponentialDelay|.
constexpr auto kLinearThrottlingDelay =
    base::Seconds(0.2) - kBaseExponentialDelay;

// Max exponential throttling rate from media server crashes.
// The max delay will still be |kLinearThrottlingDelay| +
// |kMaxExponentialDelay|.
constexpr auto kMaxExponentialDelay = base::Seconds(3) - kLinearThrottlingDelay;

// Max number of clients to schedule immediately (e.g when loading a new page).
const uint32_t kMaxBurstClients = 10;

// The throttling progression based on number of crashes looks as follows:
//
// | # crashes | period  | clients/sec | clients/mins | # burst clients
// | 0         | 200  ms | 5.0         | 300          | 10
// | 1         | 320  ms | 3.1         | 188          | 6
// | 2         | 560  ms | 1.8         | 107          | 4
// | 3         | 1040 ms | 1.0         | 58           | 2
// | 4         | 2000 ms | 0.5         | 30           | 1
// | 5         | 3000 ms | 0.3         | 20           | 1
// | 6         | 3000 ms | 0.3         | 20           | 1
//
// NOTE: Since we use the floor function and a decay rate of 1 crash/minute when
// calculating the effective # of crashes, a single crash per minute will result
// in 0 effective crashes (since floor(1.0 - 'tiny decay') is 0). If we
// experience slightly more than 1 crash per 60 seconds, the effective number of
// crashes will go up as expected.
}

// static
MediaServiceThrottler* MediaServiceThrottler::GetInstance() {
  static MediaServiceThrottler* instance = new MediaServiceThrottler();
  return instance;
}

MediaServiceThrottler::~MediaServiceThrottler() {}

MediaServiceThrottler::MediaServiceThrottler()
    : clock_(base::DefaultTickClock::GetInstance()),
      current_crashes_(0),
      crash_listener_task_runner_(base::ThreadTaskRunnerHandle::Get()) {
  // base::Unretained is safe because the MediaServiceThrottler is supposed to
  // live until the process dies.
  release_crash_listener_cb_ = base::BindRepeating(
      &MediaServiceThrottler::ReleaseCrashListener, base::Unretained(this));
  EnsureCrashListenerStarted();
}

void MediaServiceThrottler::SetTickClockForTesting(
    const base::TickClock* clock) {
  clock_ = clock;
}

base::TimeDelta MediaServiceThrottler::GetBaseThrottlingRateForTesting() {
  return kBaseExponentialDelay + kLinearThrottlingDelay;
}

void MediaServiceThrottler::ResetInternalStateForTesting() {
  last_server_crash_ = base::TimeTicks();
  last_schedule_call_ = base::TimeTicks();
  next_schedulable_slot_ = clock_->NowTicks();
  last_current_crash_update_time_ = clock_->NowTicks();
  current_crashes_ = 0.0;
}

base::TimeDelta MediaServiceThrottler::GetDelayForClientCreation() {
  // Make sure the listener is started and the crashes decayed.
  EnsureCrashListenerStarted();
  UpdateServerCrashes();

  base::TimeTicks now = clock_->NowTicks();

  // If we are passed the next time slot or if it has been 1 minute since the
  // last call to GetDelayForClientCreation(), reset the next time to now.
  if (now > next_schedulable_slot_ ||
      (now - last_schedule_call_) > kTimeUntilScheduleReset) {
    next_schedulable_slot_ = now;
  }

  last_schedule_call_ = now;

  // Increment the next scheduled time between 0.2s and 3s, which allows the
  // creation of between 50 and 3 clients per 10s.
  next_schedulable_slot_ +=
      kLinearThrottlingDelay + GetThrottlingDelayFromServerCrashes();

  // Calculate how long to delay the creation so it isn't scheduled before
  // |next_schedulable_slot_|.
  base::TimeDelta delay = next_schedulable_slot_ - now;

  // If the scheduling delay is low enough, schedule it immediately instead.
  // This allows up to kMaxBurstClients clients to be scheduled immediately.
  if (delay <=
      (kLinearThrottlingDelay + kBaseExponentialDelay) * kMaxBurstClients)
    return base::TimeDelta();

  return delay;
}

base::TimeDelta MediaServiceThrottler::GetThrottlingDelayFromServerCrashes() {
  // The combination of rounding down the number of crashes down and decaying
  // at the rate of 1 crash / min means that a single crash will very quickly be
  // rounded down to 0. Effectively, this means that we only start exponentially
  // backing off if we have more than 1 crash in a 60 second window.
  uint32_t num_crashes = static_cast<uint32_t>(current_crashes_);
  DCHECK_GE(num_crashes, 0u);

  // Prevents overflow/undefined behavior. We already reach kMaxExponentialDelay
  // at 5 crashes in any case.
  num_crashes = std::min(num_crashes, 10u);

  return std::min(kBaseExponentialDelay * (1 << num_crashes),
                  kMaxExponentialDelay);
}

void MediaServiceThrottler::OnMediaServerCrash(bool watchdog_needs_release) {
  if (watchdog_needs_release && crash_listener_)
    crash_listener_->ReleaseWatchdog();

  UpdateServerCrashes();

  last_server_crash_ = clock_->NowTicks();
  current_crashes_ += 1.0;
}

void MediaServiceThrottler::UpdateServerCrashes() {
  base::TimeTicks now = clock_->NowTicks();
  base::TimeDelta time_since_last_crash = now - last_server_crash_;

  if (time_since_last_crash > kTimeUntilCrashReset) {
    // Reset the number of crashes if we haven't had a crash in the past minute.
    current_crashes_ = 0.0;
  } else {
    // Decay at the rate of 1 crash/minute otherwise.
    const double decay =
        (now - last_current_crash_update_time_) / base::Minutes(1);
    current_crashes_ = std::max(0.0, current_crashes_ - decay);
  }

  last_current_crash_update_time_ = now;
}

void MediaServiceThrottler::ReleaseCrashListener() {
  crash_listener_.reset(nullptr);
}

void MediaServiceThrottler::EnsureCrashListenerStarted() {
  if (!crash_listener_) {
    // base::Unretained is safe here because the MediaServiceThrottler will live
    // until the process is terminated.
    crash_listener_ = std::make_unique<MediaServerCrashListener>(
        base::BindRepeating(&MediaServiceThrottler::OnMediaServerCrash,
                            base::Unretained(this)),
        crash_listener_task_runner_);
  } else {
    crash_listener_->EnsureListening();
  }

  // Cancels outstanding/pending versions of the callback.
  cancelable_release_crash_listener_cb_.Reset(release_crash_listener_cb_);

  // Schedule the release of |crash_listener_| a minute from now. This will be
  // updated anytime GetDelayForClientCreation() is called.
  crash_listener_task_runner_->PostDelayedTask(
      FROM_HERE, cancelable_release_crash_listener_cb_.callback(),
      kReleaseInactivityDelay);
}

bool MediaServiceThrottler::IsCrashListenerAliveForTesting() {
  return !!crash_listener_;
}

void MediaServiceThrottler::SetCrashListenerTaskRunnerForTesting(
    scoped_refptr<base::SingleThreadTaskRunner> crash_listener_task_runner) {
  // Set the task runner so |crash_listener_| be deleted on the right thread.
  crash_listener_task_runner_ = crash_listener_task_runner;

  // Re-create the crash listener.
  crash_listener_ = std::make_unique<MediaServerCrashListener>(
      base::NullCallback(), crash_listener_task_runner_);
}

}  // namespace media