blob: 8cf294b891bd4e97cccfc2903368c166552acbe1 [file] [log] [blame]
// Copyright 2024 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.
#ifndef QUICHE_QUIC_CORE_WEB_TRANSPORT_WRITE_BLOCKED_LIST_H_
#define QUICHE_QUIC_CORE_WEB_TRANSPORT_WRITE_BLOCKED_LIST_H_
#include <cstddef>
#include <limits>
#include <ostream>
#include <string>
#include "absl/container/flat_hash_map.h"
#include "quiche/quic/core/quic_stream_priority.h"
#include "quiche/quic/core/quic_types.h"
#include "quiche/quic/core/quic_write_blocked_list.h"
#include "quiche/common/btree_scheduler.h"
#include "quiche/common/platform/api/quiche_export.h"
#include "quiche/web_transport/web_transport.h"
namespace quic {
// Scheduler that is capable of handling both regular HTTP/3 priorities and
// WebTransport priorities for multiple sessions at the same time.
//
// Here is a brief overview of the scheme:
// - At the top, there are HTTP/3 streams that are ordered by urgency as
// defined in RFC 9218.
// - The HTTP/3 connection can be a host to multiple WebTransport sessions.
// Those are identified by the ID of the HTTP/3 control stream that created
// the session; they also inherit the priority from that stream.
// - The sessions consist of send groups that all have equal priority.
// - The send groups have individual WebTransport data streams; each data
// stream has a send order, which is a strict priority expressed as int64.
//
// To simplify the implementation of an already excessively complex scheme, this
// class makes a couple of affordances:
// - Instead of first scheduling an individual session, then scheduling a
// group within it, it schedules session-group pairs at the top level. This
// is technically allowed by the spec, but it does mean that sessions with
// more groups may get more bandwidth.
// - Incremental priorities are not currently supported.
class QUICHE_EXPORT WebTransportWriteBlockedList
: public QuicWriteBlockedListInterface {
public:
// Handle static streams by treating them as streams of priority MAX + 1.
static constexpr int kStaticUrgency = HttpStreamPriority::kMaximumUrgency + 1;
// QuicWriteBlockedListInterface implementation.
bool HasWriteBlockedDataStreams() const override;
size_t NumBlockedSpecialStreams() const override;
size_t NumBlockedStreams() const override;
void RegisterStream(QuicStreamId stream_id, bool is_static_stream,
const QuicStreamPriority& raw_priority) override;
void UnregisterStream(QuicStreamId stream_id) override;
void UpdateStreamPriority(QuicStreamId stream_id,
const QuicStreamPriority& new_priority) override;
bool ShouldYield(QuicStreamId id) const override;
QuicStreamPriority GetPriorityOfStream(QuicStreamId id) const override;
QuicStreamId PopFront() override;
void UpdateBytesForStream(QuicStreamId /*stream_id*/,
size_t /*bytes*/) override {}
void AddStream(QuicStreamId stream_id) override;
bool IsStreamBlocked(QuicStreamId stream_id) const override;
size_t NumRegisteredGroups() const {
return web_transport_session_schedulers_.size();
}
size_t NumRegisteredHttpStreams() const {
return main_schedule_.NumRegistered() - NumRegisteredGroups();
}
private:
// ScheduleKey represents anything that can be put into the main scheduler,
// which is either:
// - an HTTP/3 stream, or
// - an individual WebTransport session-send group pair.
class QUICHE_EXPORT ScheduleKey {
public:
static ScheduleKey HttpStream(QuicStreamId id) {
return ScheduleKey(id, kNoSendGroup);
}
static ScheduleKey WebTransportSession(QuicStreamId session_id,
webtransport::SendGroupId group_id) {
return ScheduleKey(session_id, group_id);
}
static ScheduleKey WebTransportSession(const QuicStreamPriority& priority) {
return ScheduleKey(priority.web_transport().session_id,
priority.web_transport().send_group_number);
}
bool operator==(const ScheduleKey& other) const {
return stream_ == other.stream_ && group_ == other.group_;
}
bool operator!=(const ScheduleKey& other) const {
return !(*this == other);
}
template <typename H>
friend H AbslHashValue(H h, const ScheduleKey& key) {
return H::combine(std::move(h), key.stream_, key.group_);
}
bool has_group() const { return group_ != kNoSendGroup; }
quic::QuicStreamId stream() const { return stream_; }
std::string DebugString() const;
friend inline std::ostream& operator<<(std::ostream& os,
const ScheduleKey& key) {
os << key.DebugString();
return os;
}
private:
static constexpr webtransport::SendGroupId kNoSendGroup =
std::numeric_limits<webtransport::SendGroupId>::max();
explicit ScheduleKey(quic::QuicStreamId stream,
webtransport::SendGroupId group)
: stream_(stream), group_(group) {}
quic::QuicStreamId stream_;
webtransport::SendGroupId group_;
};
// WebTransport requires individual sessions to have the same urgency as their
// control streams; in a naive implementation, that would mean that both would
// get the same urgency N, but we also want for the control streams to have
// higher priority than WebTransport user data. In order to achieve that, we
// enter control streams at urgency 2 * N + 1, and data streams at urgency
// 2 * N.
static constexpr int RemapUrgency(int urgency, bool is_http) {
return urgency * 2 + (is_http ? 1 : 0);
}
// Scheduler for individual WebTransport send groups.
using Subscheduler =
quiche::BTreeScheduler<QuicStreamId, webtransport::SendOrder>;
// Top-level scheduler used to multiplex WebTransport sessions and individual
// HTTP/3 streams.
quiche::BTreeScheduler<ScheduleKey, int> main_schedule_;
// Records of priority for every stream; used when looking up WebTransport
// session associated with an individual stream.
absl::flat_hash_map<QuicStreamId, QuicStreamPriority> priorities_;
// Schedulers for individual WebTransport send groups.
absl::flat_hash_map<ScheduleKey, Subscheduler>
web_transport_session_schedulers_;
};
} // namespace quic
#endif // QUICHE_QUIC_CORE_WEB_TRANSPORT_WRITE_BLOCKED_LIST_H_