mirror of
https://github.com/mollyim/webrtc.git
synced 2025-05-17 23:57:59 +01:00

By allowing the max timer backoff duration to be limited, a socket can fast recover in case of intermittent network issues. Before this CL, the exponential backoff algorithm could result in very long retry durations (in the order of minutes), when connection has been lost or been flaky for a long while. Note that limiting the maximum backoff duration might require compensating the maximum retransmission limit to avoid closing the socket prematurely due to reaching the maximum retransmission limit much faster than previously. Bug: webrtc:13129 Change-Id: Ib94030d666433e3fa1a2c8ef69750a1afab8ef94 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/230702 Reviewed-by: Florent Castelli <orphis@webrtc.org> Commit-Queue: Victor Boivie <boivie@webrtc.org> Cr-Commit-Position: refs/heads/main@{#34913}
195 lines
7.4 KiB
C++
195 lines
7.4 KiB
C++
/*
|
|
* Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by a BSD-style license
|
|
* that can be found in the LICENSE file in the root of the source
|
|
* tree. An additional intellectual property rights grant can be found
|
|
* in the file PATENTS. All contributing project authors may
|
|
* be found in the AUTHORS file in the root of the source tree.
|
|
*/
|
|
#ifndef NET_DCSCTP_TIMER_TIMER_H_
|
|
#define NET_DCSCTP_TIMER_TIMER_H_
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <algorithm>
|
|
#include <functional>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <utility>
|
|
|
|
#include "absl/strings/string_view.h"
|
|
#include "absl/types/optional.h"
|
|
#include "net/dcsctp/public/strong_alias.h"
|
|
#include "net/dcsctp/public/timeout.h"
|
|
|
|
namespace dcsctp {
|
|
|
|
using TimerID = StrongAlias<class TimerIDTag, uint32_t>;
|
|
using TimerGeneration = StrongAlias<class TimerGenerationTag, uint32_t>;
|
|
|
|
enum class TimerBackoffAlgorithm {
|
|
// The base duration will be used for any restart.
|
|
kFixed,
|
|
// An exponential backoff is used for restarts, with a 2x multiplier, meaning
|
|
// that every restart will use a duration that is twice as long as the
|
|
// previous.
|
|
kExponential,
|
|
};
|
|
|
|
struct TimerOptions {
|
|
explicit TimerOptions(DurationMs duration)
|
|
: TimerOptions(duration, TimerBackoffAlgorithm::kExponential) {}
|
|
TimerOptions(DurationMs duration, TimerBackoffAlgorithm backoff_algorithm)
|
|
: TimerOptions(duration, backoff_algorithm, absl::nullopt) {}
|
|
TimerOptions(DurationMs duration,
|
|
TimerBackoffAlgorithm backoff_algorithm,
|
|
absl::optional<int> max_restarts)
|
|
: TimerOptions(duration, backoff_algorithm, max_restarts, absl::nullopt) {
|
|
}
|
|
TimerOptions(DurationMs duration,
|
|
TimerBackoffAlgorithm backoff_algorithm,
|
|
absl::optional<int> max_restarts,
|
|
absl::optional<DurationMs> max_backoff_duration)
|
|
: duration(duration),
|
|
backoff_algorithm(backoff_algorithm),
|
|
max_restarts(max_restarts),
|
|
max_backoff_duration(max_backoff_duration) {}
|
|
|
|
// The initial timer duration. Can be overridden with `set_duration`.
|
|
const DurationMs duration;
|
|
// If the duration should be increased (using exponential backoff) when it is
|
|
// restarted. If not set, the same duration will be used.
|
|
const TimerBackoffAlgorithm backoff_algorithm;
|
|
// The maximum number of times that the timer will be automatically restarted,
|
|
// or absl::nullopt if there is no limit.
|
|
const absl::optional<int> max_restarts;
|
|
// The maximum timeout value for exponential backoff.
|
|
const absl::optional<DurationMs> max_backoff_duration;
|
|
};
|
|
|
|
// A high-level timer (in contrast to the low-level `Timeout` class).
|
|
//
|
|
// Timers are started and can be stopped or restarted. When a timer expires,
|
|
// the provided `on_expired` callback will be triggered. A timer is
|
|
// automatically restarted, as long as the number of restarts is below the
|
|
// configurable `max_restarts` parameter. The `is_running` property can be
|
|
// queried to know if it's still running after having expired.
|
|
//
|
|
// When a timer is restarted, it will use a configurable `backoff_algorithm` to
|
|
// possibly adjust the duration of the next expiry. It is also possible to
|
|
// return a new base duration (which is the duration before it's adjusted by the
|
|
// backoff algorithm).
|
|
class Timer {
|
|
public:
|
|
// The maximum timer duration - one day.
|
|
static constexpr DurationMs kMaxTimerDuration = DurationMs(24 * 3600 * 1000);
|
|
|
|
// When expired, the timer handler can optionally return a new duration which
|
|
// will be set as `duration` and used as base duration when the timer is
|
|
// restarted and as input to the backoff algorithm.
|
|
using OnExpired = std::function<absl::optional<DurationMs>()>;
|
|
|
|
// TimerManager will have pointers to these instances, so they must not move.
|
|
Timer(const Timer&) = delete;
|
|
Timer& operator=(const Timer&) = delete;
|
|
|
|
~Timer();
|
|
|
|
// Starts the timer if it's stopped or restarts the timer if it's already
|
|
// running. The `expiration_count` will be reset.
|
|
void Start();
|
|
|
|
// Stops the timer. This can also be called when the timer is already stopped.
|
|
// The `expiration_count` will be reset.
|
|
void Stop();
|
|
|
|
// Sets the base duration. The actual timer duration may be larger depending
|
|
// on the backoff algorithm.
|
|
void set_duration(DurationMs duration) {
|
|
duration_ = std::min(duration, kMaxTimerDuration);
|
|
}
|
|
|
|
// Retrieves the base duration. The actual timer duration may be larger
|
|
// depending on the backoff algorithm.
|
|
DurationMs duration() const { return duration_; }
|
|
|
|
// Returns the number of times the timer has expired.
|
|
int expiration_count() const { return expiration_count_; }
|
|
|
|
// Returns the timer's options.
|
|
const TimerOptions& options() const { return options_; }
|
|
|
|
// Returns the name of the timer.
|
|
absl::string_view name() const { return name_; }
|
|
|
|
// Indicates if this timer is currently running.
|
|
bool is_running() const { return is_running_; }
|
|
|
|
private:
|
|
friend class TimerManager;
|
|
using UnregisterHandler = std::function<void()>;
|
|
Timer(TimerID id,
|
|
absl::string_view name,
|
|
OnExpired on_expired,
|
|
UnregisterHandler unregister,
|
|
std::unique_ptr<Timeout> timeout,
|
|
const TimerOptions& options);
|
|
|
|
// Called by TimerManager. Will trigger the callback and increment
|
|
// `expiration_count`. The timer will automatically be restarted at the
|
|
// duration as decided by the backoff algorithm, unless the
|
|
// `TimerOptions::max_restarts` has been reached and then it will be stopped
|
|
// and `is_running()` will return false.
|
|
void Trigger(TimerGeneration generation);
|
|
|
|
const TimerID id_;
|
|
const std::string name_;
|
|
const TimerOptions options_;
|
|
const OnExpired on_expired_;
|
|
const UnregisterHandler unregister_handler_;
|
|
const std::unique_ptr<Timeout> timeout_;
|
|
|
|
DurationMs duration_;
|
|
|
|
// Increased on each start, and is matched on Trigger, to avoid races. And by
|
|
// race, meaning that a timeout - which may be evaluated/expired on a
|
|
// different thread while this thread has stopped that timer already. Note
|
|
// that the entire socket is not thread-safe, so `TimerManager::HandleTimeout`
|
|
// is never executed concurrently with any timer starting/stopping.
|
|
//
|
|
// This will wrap around after 4 billion timer restarts, and if it wraps
|
|
// around, it would just trigger _this_ timer in advance (but it's hard to
|
|
// restart it 4 billion times within its duration).
|
|
TimerGeneration generation_ = TimerGeneration(0);
|
|
bool is_running_ = false;
|
|
// Incremented each time time has expired and reset when stopped or restarted.
|
|
int expiration_count_ = 0;
|
|
};
|
|
|
|
// Creates and manages timers.
|
|
class TimerManager {
|
|
public:
|
|
explicit TimerManager(
|
|
std::function<std::unique_ptr<Timeout>()> create_timeout)
|
|
: create_timeout_(std::move(create_timeout)) {}
|
|
|
|
// Creates a timer with name `name` that will expire (when started) after
|
|
// `options.duration` and call `on_expired`. There are more `options` that
|
|
// affects the behavior. Note that timers are created initially stopped.
|
|
std::unique_ptr<Timer> CreateTimer(absl::string_view name,
|
|
Timer::OnExpired on_expired,
|
|
const TimerOptions& options);
|
|
|
|
void HandleTimeout(TimeoutID timeout_id);
|
|
|
|
private:
|
|
const std::function<std::unique_ptr<Timeout>()> create_timeout_;
|
|
std::map<TimerID, Timer*> timers_;
|
|
TimerID next_id_ = TimerID(0);
|
|
};
|
|
|
|
} // namespace dcsctp
|
|
|
|
#endif // NET_DCSCTP_TIMER_TIMER_H_
|