blob: 117bbcecedb43a7f6959c445f1d88a41416a745f [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CONTENT_BROWSER_WEBTRANSPORT_WEB_TRANSPORT_THROTTLE_CONTEXT_H_
#define CONTENT_BROWSER_WEBTRANSPORT_WEB_TRANSPORT_THROTTLE_CONTEXT_H_
#include <stddef.h>
#include <functional>
#include <memory>
#include <queue>
#include <vector>
#include "base/containers/queue.h"
#include "base/functional/callback_forward.h"
#include "base/memory/weak_ptr.h"
#include "base/supports_user_data.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "content/common/content_export.h"
#include "net/base/ip_endpoint.h"
namespace content {
// Tracks a single "bucket" of pending handshakes. For frames and dedicated
// workers there is one object per Page. For shared and service workers there is
// one per profile.
class CONTENT_EXPORT WebTransportThrottleContext final
: public base::SupportsUserData::Data {
public:
// Tracks an ongoing handshake. Passed to the caller of PerformThrottle(),
// which should call one of the methods.
class CONTENT_EXPORT Tracker final {
public:
// Only constructed by WebTransportThrottleContext.
explicit Tracker(
base::WeakPtr<WebTransportThrottleContext> throttle_context);
Tracker(const Tracker&) = delete;
Tracker& operator=(const Tracker&) = delete;
// Destruction of the object without calling one of the methods is treated
// like handshake failure.
~Tracker();
// Collects information about a WebTransport handshake that is about to
// start.
void OnBeforeConnect(const net::IPEndPoint& server_address);
// Records the successful end of a WebTransport handshake.
void OnHandshakeEstablished();
// Records a WebTransport handshake failure.
void OnHandshakeFailed();
private:
base::WeakPtr<WebTransportThrottleContext> throttle_context_;
net::IPEndPoint server_address_;
};
using ThrottleDoneCallback =
base::OnceCallback<void(std::unique_ptr<Tracker>)>;
static constexpr int kMaxPendingSessions = 64;
enum class ThrottleResult {
kOk,
kTooManyPendingSessions,
};
WebTransportThrottleContext();
WebTransportThrottleContext(const WebTransportThrottleContext&) = delete;
WebTransportThrottleContext& operator=(const WebTransportThrottleContext&) =
delete;
~WebTransportThrottleContext() override;
// Attempts to start a new handshake. Returns kTooManyPendingSessions if there
// are already too many pending handshakes. Otherwise, runs the
// `on_throttle_done` closure, possibly synchronously, and returns kOk.
// Immediately before the `on_throttle_done` closure is called the handshake
// is considered started.
ThrottleResult PerformThrottle(ThrottleDoneCallback on_throttle_done);
// Called when a handshake fails. Adds handshake delays unless it is
// explicitly suppressed.
void MaybeQueueHandshakeFailurePenalty(
const std::optional<net::IPEndPoint>& server_address);
void OnPendingQueueReady();
base::WeakPtr<WebTransportThrottleContext> GetWeakPtr();
private:
class PenaltyManager final {
public:
using FailedHandshakesMap = std::map<net::IPAddress, base::TimeTicks>;
explicit PenaltyManager(WebTransportThrottleContext*);
PenaltyManager(const PenaltyManager&) = delete;
PenaltyManager& operator=(const PenaltyManager&) = delete;
~PenaltyManager();
base::TimeDelta ComputeHandshakePenalty(
const std::optional<net::IPEndPoint>& server_address);
// Queues a pending handshake to be considered complete after `after`.
void QueuePending(base::TimeDelta after);
// If there are handshakes in `pending_queue_` that can now be considered
// finished, remove them and decrement `pending_handshakes_`. Recalculates
// the delay for the head of `throttled_connections_` and may trigger it to
// start as a side-effect.
void MaybeDecrementPending();
// Start the timer based on the `pending_queue` top.
void ProcessPendingQueue();
void StopPendingQueueTimer();
int PendingHandshakes() const { return pending_handshakes_; }
void AddPendingHandshakes() { ++pending_handshakes_; }
void RemovePendingHandshakes() { --pending_handshakes_; }
bool PendingQueueTimerIsRunning() {
return pending_queue_timer_.IsRunning();
}
bool PendingQueueIsEmpty() { return pending_queue_.empty(); }
private:
// Start the timer for removing items from `pending_queue_timer_`, to fire
// after `after` has passed.
void StartPendingQueueTimer(base::TimeDelta after);
// Removes any obsolete item in the failed_handshakes_ map.
void CleanupFailedHandshakes();
// Checks if there is a previous, non-obsolete, item in the
// failed_handshakes_ map for the given ip address and updates
// the map with the current time.
bool FailedHandshakeNeedsPenalty(const net::IPAddress ip_address);
const raw_ptr<WebTransportThrottleContext> throttle_context_;
int pending_handshakes_ = 0;
// Sessions for which the handshake has completed but we are still counting
// as "pending" for the purposes of throttling. An items is added to this
// queue when the handshake completes, and removed when the timer expires.
// The "top" of the queue is the timer that will expire first.
std::priority_queue<base::TimeTicks,
std::vector<base::TimeTicks>,
std::greater<>>
pending_queue_;
// A timer that will fire the next time an entry should be removed from
// `pending_queue_`. The timer doesn't run when `throttled_connections_` is
// empty.
base::OneShotTimer pending_queue_timer_;
FailedHandshakesMap failed_handshakes_;
// A timer to cleanup the obsolete failed_handshakes.
base::RepeatingTimer failed_handshakes_timer_;
};
// Starts a connection immediately if there are none pending, or sets a timer
// to start one later.
void ScheduleThrottledConnection();
// Calls the throttle done callback on the connection at the head of
// `throttled_connections_`.
void DoOnThrottleDone();
// Starts a connection if there is one waiting, and schedules the timer for
// the next connection.
void StartOneConnection();
// False when the `--webtransport-developer-mode` flag is specified, true
// otherwise.
const bool should_queue_handshake_failure_penalty_;
PenaltyManager penalty_mgr_{this};
base::queue<ThrottleDoneCallback> throttled_connections_;
// The time that `throttled_connections_[0]` reached the front of the queue.
// This is needed if it gets recheduled by ScheduleThrottledConnection().
base::TimeTicks queue_head_time_;
// A timer that will fire the next time a throttled connection should be
// allowed to proceed. This is a reset when pending_handshakes_ is
// decremented.
base::OneShotTimer throttled_connections_timer_;
base::WeakPtrFactory<WebTransportThrottleContext> weak_factory_{this};
};
} // namespace content
#endif // CONTENT_BROWSER_WEBTRANSPORT_WEB_TRANSPORT_THROTTLE_CONTEXT_H_