blob: 15939e09102bbffe51668b119337b0d75e0f5afb [file] [log] [blame]
QUICHE team82dee2f2019-01-18 12:35:12 -05001// Copyright (c) 2015 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#ifndef QUICHE_SPDY_CORE_PRIORITY_WRITE_SCHEDULER_H_
6#define QUICHE_SPDY_CORE_PRIORITY_WRITE_SCHEDULER_H_
7
8#include <algorithm>
9#include <cstddef>
10#include <cstdint>
bnc44712912019-08-15 18:58:14 -070011#include <string>
QUICHE team82dee2f2019-01-18 12:35:12 -050012#include <tuple>
13#include <unordered_map>
14#include <utility>
15#include <vector>
16
QUICHE team82dee2f2019-01-18 12:35:12 -050017#include "net/third_party/quiche/src/http2/platform/api/http2_containers.h"
18#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
19#include "net/third_party/quiche/src/spdy/core/write_scheduler.h"
20#include "net/third_party/quiche/src/spdy/platform/api/spdy_bug_tracker.h"
QUICHE teamded03512019-03-07 14:45:11 -080021#include "net/third_party/quiche/src/spdy/platform/api/spdy_logging.h"
QUICHE team82dee2f2019-01-18 12:35:12 -050022#include "net/third_party/quiche/src/spdy/platform/api/spdy_macros.h"
QUICHE team82dee2f2019-01-18 12:35:12 -050023#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h"
24
25namespace spdy {
26
27namespace test {
28template <typename StreamIdType>
29class PriorityWriteSchedulerPeer;
30}
31
32// WriteScheduler implementation that manages the order in which streams are
33// written using the SPDY priority scheme described at:
34// https://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1#TOC-2.3.3-Stream-priority
35//
36// Internally, PriorityWriteScheduler consists of 8 PriorityInfo objects, one
37// for each priority value. Each PriorityInfo contains a list of streams of
38// that priority that are ready to write, as well as a timestamp of the last
39// I/O event that occurred for a stream of that priority.
40//
41// DO NOT USE. Deprecated.
42template <typename StreamIdType>
43class PriorityWriteScheduler : public WriteScheduler<StreamIdType> {
44 public:
45 using typename WriteScheduler<StreamIdType>::StreamPrecedenceType;
46
47 // Creates scheduler with no streams.
nharpercd820e02019-05-16 15:12:07 -070048 PriorityWriteScheduler() : PriorityWriteScheduler(kHttp2RootStreamId) {}
49 explicit PriorityWriteScheduler(StreamIdType root_stream_id)
50 : root_stream_id_(root_stream_id) {}
QUICHE team82dee2f2019-01-18 12:35:12 -050051
52 void RegisterStream(StreamIdType stream_id,
53 const StreamPrecedenceType& precedence) override {
54 // TODO(mpw): verify |precedence.is_spdy3_priority() == true| once
55 // Http2PriorityWriteScheduler enabled for HTTP/2.
56
57 // parent_id not used here, but may as well validate it. However,
58 // parent_id may legitimately not be registered yet--see b/15676312.
59 StreamIdType parent_id = precedence.parent_id();
nharpercd820e02019-05-16 15:12:07 -070060 SPDY_DVLOG_IF(1,
61 parent_id != root_stream_id_ && !StreamRegistered(parent_id))
QUICHE team82dee2f2019-01-18 12:35:12 -050062 << "Parent stream " << parent_id << " not registered";
63
nharpercd820e02019-05-16 15:12:07 -070064 if (stream_id == root_stream_id_) {
65 SPDY_BUG << "Stream " << root_stream_id_ << " already registered";
QUICHE team82dee2f2019-01-18 12:35:12 -050066 return;
67 }
68 StreamInfo stream_info = {precedence.spdy3_priority(), stream_id, false};
69 bool inserted =
70 stream_infos_.insert(std::make_pair(stream_id, stream_info)).second;
71 SPDY_BUG_IF(!inserted) << "Stream " << stream_id << " already registered";
72 }
73
74 void UnregisterStream(StreamIdType stream_id) override {
75 auto it = stream_infos_.find(stream_id);
76 if (it == stream_infos_.end()) {
77 SPDY_BUG << "Stream " << stream_id << " not registered";
78 return;
79 }
80 StreamInfo& stream_info = it->second;
81 if (stream_info.ready) {
82 bool erased =
83 Erase(&priority_infos_[stream_info.priority].ready_list, stream_info);
84 DCHECK(erased);
85 }
86 stream_infos_.erase(it);
87 }
88
89 bool StreamRegistered(StreamIdType stream_id) const override {
90 return stream_infos_.find(stream_id) != stream_infos_.end();
91 }
92
93 StreamPrecedenceType GetStreamPrecedence(
94 StreamIdType stream_id) const override {
95 auto it = stream_infos_.find(stream_id);
96 if (it == stream_infos_.end()) {
QUICHE teamded03512019-03-07 14:45:11 -080097 SPDY_DVLOG(1) << "Stream " << stream_id << " not registered";
QUICHE team82dee2f2019-01-18 12:35:12 -050098 return StreamPrecedenceType(kV3LowestPriority);
99 }
100 return StreamPrecedenceType(it->second.priority);
101 }
102
103 void UpdateStreamPrecedence(StreamIdType stream_id,
104 const StreamPrecedenceType& precedence) override {
105 // TODO(mpw): verify |precedence.is_spdy3_priority() == true| once
106 // Http2PriorityWriteScheduler enabled for HTTP/2.
107
108 // parent_id not used here, but may as well validate it. However,
109 // parent_id may legitimately not be registered yet--see b/15676312.
110 StreamIdType parent_id = precedence.parent_id();
nharpercd820e02019-05-16 15:12:07 -0700111 SPDY_DVLOG_IF(1,
112 parent_id != root_stream_id_ && !StreamRegistered(parent_id))
QUICHE team82dee2f2019-01-18 12:35:12 -0500113 << "Parent stream " << parent_id << " not registered";
114
115 auto it = stream_infos_.find(stream_id);
116 if (it == stream_infos_.end()) {
117 // TODO(mpw): add to stream_infos_ on demand--see b/15676312.
QUICHE teamded03512019-03-07 14:45:11 -0800118 SPDY_DVLOG(1) << "Stream " << stream_id << " not registered";
QUICHE team82dee2f2019-01-18 12:35:12 -0500119 return;
120 }
121 StreamInfo& stream_info = it->second;
122 SpdyPriority new_priority = precedence.spdy3_priority();
123 if (stream_info.priority == new_priority) {
124 return;
125 }
126 if (stream_info.ready) {
127 bool erased =
128 Erase(&priority_infos_[stream_info.priority].ready_list, stream_info);
129 DCHECK(erased);
130 priority_infos_[new_priority].ready_list.push_back(&stream_info);
131 ++num_ready_streams_;
132 }
133 stream_info.priority = new_priority;
134 }
135
136 std::vector<StreamIdType> GetStreamChildren(
dschinazi40d1e682019-06-18 15:45:32 -0700137 StreamIdType /*stream_id*/) const override {
QUICHE team82dee2f2019-01-18 12:35:12 -0500138 return std::vector<StreamIdType>();
139 }
140
141 void RecordStreamEventTime(StreamIdType stream_id,
142 int64_t now_in_usec) override {
143 auto it = stream_infos_.find(stream_id);
144 if (it == stream_infos_.end()) {
145 SPDY_BUG << "Stream " << stream_id << " not registered";
146 return;
147 }
148 PriorityInfo& priority_info = priority_infos_[it->second.priority];
149 priority_info.last_event_time_usec =
150 std::max(priority_info.last_event_time_usec, now_in_usec);
151 }
152
153 int64_t GetLatestEventWithPrecedence(StreamIdType stream_id) const override {
154 auto it = stream_infos_.find(stream_id);
155 if (it == stream_infos_.end()) {
156 SPDY_BUG << "Stream " << stream_id << " not registered";
157 return 0;
158 }
159 int64_t last_event_time_usec = 0;
160 const StreamInfo& stream_info = it->second;
161 for (SpdyPriority p = kV3HighestPriority; p < stream_info.priority; ++p) {
162 last_event_time_usec = std::max(last_event_time_usec,
163 priority_infos_[p].last_event_time_usec);
164 }
165 return last_event_time_usec;
166 }
167
168 StreamIdType PopNextReadyStream() override {
169 return std::get<0>(PopNextReadyStreamAndPrecedence());
170 }
171
172 // Returns the next ready stream and its precedence.
173 std::tuple<StreamIdType, StreamPrecedenceType>
174 PopNextReadyStreamAndPrecedence() override {
175 for (SpdyPriority p = kV3HighestPriority; p <= kV3LowestPriority; ++p) {
176 ReadyList& ready_list = priority_infos_[p].ready_list;
177 if (!ready_list.empty()) {
178 StreamInfo* info = ready_list.front();
179 ready_list.pop_front();
180 --num_ready_streams_;
181
182 DCHECK(stream_infos_.find(info->stream_id) != stream_infos_.end());
183 info->ready = false;
184 return std::make_tuple(info->stream_id,
185 StreamPrecedenceType(info->priority));
186 }
187 }
188 SPDY_BUG << "No ready streams available";
189 return std::make_tuple(0, StreamPrecedenceType(kV3LowestPriority));
190 }
191
192 bool ShouldYield(StreamIdType stream_id) const override {
193 auto it = stream_infos_.find(stream_id);
194 if (it == stream_infos_.end()) {
195 SPDY_BUG << "Stream " << stream_id << " not registered";
196 return false;
197 }
198
199 // If there's a higher priority stream, this stream should yield.
200 const StreamInfo& stream_info = it->second;
201 for (SpdyPriority p = kV3HighestPriority; p < stream_info.priority; ++p) {
202 if (!priority_infos_[p].ready_list.empty()) {
203 return true;
204 }
205 }
206
207 // If this priority level is empty, or this stream is the next up, there's
208 // no need to yield.
209 const auto& ready_list = priority_infos_[it->second.priority].ready_list;
210 if (ready_list.empty() || ready_list.front()->stream_id == stream_id) {
211 return false;
212 }
213
214 // There are other streams in this priority level which take precedence.
215 // Yield.
216 return true;
217 }
218
219 void MarkStreamReady(StreamIdType stream_id, bool add_to_front) override {
220 auto it = stream_infos_.find(stream_id);
221 if (it == stream_infos_.end()) {
222 SPDY_BUG << "Stream " << stream_id << " not registered";
223 return;
224 }
225 StreamInfo& stream_info = it->second;
226 if (stream_info.ready) {
227 return;
228 }
229 ReadyList& ready_list = priority_infos_[stream_info.priority].ready_list;
230 if (add_to_front) {
231 ready_list.push_front(&stream_info);
232 } else {
233 ready_list.push_back(&stream_info);
234 }
235 ++num_ready_streams_;
236 stream_info.ready = true;
237 }
238
239 void MarkStreamNotReady(StreamIdType stream_id) override {
240 auto it = stream_infos_.find(stream_id);
241 if (it == stream_infos_.end()) {
242 SPDY_BUG << "Stream " << stream_id << " not registered";
243 return;
244 }
245 StreamInfo& stream_info = it->second;
246 if (!stream_info.ready) {
247 return;
248 }
249 bool erased =
250 Erase(&priority_infos_[stream_info.priority].ready_list, stream_info);
251 DCHECK(erased);
252 stream_info.ready = false;
253 }
254
255 // Returns true iff the number of ready streams is non-zero.
256 bool HasReadyStreams() const override { return num_ready_streams_ > 0; }
257
258 // Returns the number of ready streams.
259 size_t NumReadyStreams() const override { return num_ready_streams_; }
260
fayang753002d2019-07-24 11:54:43 -0700261 size_t NumRegisteredStreams() const override { return stream_infos_.size(); }
262
bnc44712912019-08-15 18:58:14 -0700263 std::string DebugString() const override {
QUICHE team82dee2f2019-01-18 12:35:12 -0500264 return SpdyStrCat(
265 "PriorityWriteScheduler {num_streams=", stream_infos_.size(),
266 " num_ready_streams=", NumReadyStreams(), "}");
267 }
268
269 // Returns true if a stream is ready.
fayangb7edbb82019-07-23 06:08:00 -0700270 bool IsStreamReady(StreamIdType stream_id) const override {
QUICHE team82dee2f2019-01-18 12:35:12 -0500271 auto it = stream_infos_.find(stream_id);
272 if (it == stream_infos_.end()) {
QUICHE teamded03512019-03-07 14:45:11 -0800273 SPDY_DLOG(INFO) << "Stream " << stream_id << " not registered";
QUICHE team82dee2f2019-01-18 12:35:12 -0500274 return false;
275 }
276 return it->second.ready;
277 }
278
279 private:
280 friend class test::PriorityWriteSchedulerPeer<StreamIdType>;
281
282 // State kept for all registered streams. All ready streams have ready = true
283 // and should be present in priority_infos_[priority].ready_list.
284 struct StreamInfo {
285 SpdyPriority priority;
286 StreamIdType stream_id;
287 bool ready;
288 };
289
290 // O(1) size lookup, O(1) insert at front or back (amortized).
291 using ReadyList = http2::Http2Deque<StreamInfo*>;
292
293 // State kept for each priority level.
294 struct PriorityInfo {
295 // IDs of streams that are ready to write.
296 ReadyList ready_list;
297 // Time of latest write event for stream of this priority, in microseconds.
298 int64_t last_event_time_usec = 0;
299 };
300
301 typedef std::unordered_map<StreamIdType, StreamInfo> StreamInfoMap;
302
303 // Erases first occurrence (which should be the only one) of |info| in
304 // |ready_list|, returning true if found (and erased), or false otherwise.
305 // Decrements |num_ready_streams_| if an entry is erased.
306 bool Erase(ReadyList* ready_list, const StreamInfo& info) {
307 auto it = std::find(ready_list->begin(), ready_list->end(), &info);
308 if (it == ready_list->end()) {
309 return false;
310 }
311 ready_list->erase(it);
312 --num_ready_streams_;
313 return true;
314 }
315
316 // Number of ready streams.
317 size_t num_ready_streams_ = 0;
318 // Per-priority state, including ready lists.
319 PriorityInfo priority_infos_[kV3LowestPriority + 1];
320 // StreamInfos for all registered streams.
321 StreamInfoMap stream_infos_;
nharpercd820e02019-05-16 15:12:07 -0700322 StreamIdType root_stream_id_;
QUICHE team82dee2f2019-01-18 12:35:12 -0500323};
324
325} // namespace spdy
326
327#endif // QUICHE_SPDY_CORE_PRIORITY_WRITE_SCHEDULER_H_