blob: 20c9c1359c8eab2f36ccb7b957df34513386b100 [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>
11#include <tuple>
12#include <unordered_map>
13#include <utility>
14#include <vector>
15
QUICHE team82dee2f2019-01-18 12:35:12 -050016#include "net/third_party/quiche/src/http2/platform/api/http2_containers.h"
17#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
18#include "net/third_party/quiche/src/spdy/core/write_scheduler.h"
19#include "net/third_party/quiche/src/spdy/platform/api/spdy_bug_tracker.h"
QUICHE teamded03512019-03-07 14:45:11 -080020#include "net/third_party/quiche/src/spdy/platform/api/spdy_logging.h"
QUICHE team82dee2f2019-01-18 12:35:12 -050021#include "net/third_party/quiche/src/spdy/platform/api/spdy_macros.h"
22#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h"
23#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
261 SpdyString DebugString() const override {
262 return SpdyStrCat(
263 "PriorityWriteScheduler {num_streams=", stream_infos_.size(),
264 " num_ready_streams=", NumReadyStreams(), "}");
265 }
266
267 // Returns true if a stream is ready.
268 bool IsStreamReady(StreamIdType stream_id) const {
269 auto it = stream_infos_.find(stream_id);
270 if (it == stream_infos_.end()) {
QUICHE teamded03512019-03-07 14:45:11 -0800271 SPDY_DLOG(INFO) << "Stream " << stream_id << " not registered";
QUICHE team82dee2f2019-01-18 12:35:12 -0500272 return false;
273 }
274 return it->second.ready;
275 }
276
277 private:
278 friend class test::PriorityWriteSchedulerPeer<StreamIdType>;
279
280 // State kept for all registered streams. All ready streams have ready = true
281 // and should be present in priority_infos_[priority].ready_list.
282 struct StreamInfo {
283 SpdyPriority priority;
284 StreamIdType stream_id;
285 bool ready;
286 };
287
288 // O(1) size lookup, O(1) insert at front or back (amortized).
289 using ReadyList = http2::Http2Deque<StreamInfo*>;
290
291 // State kept for each priority level.
292 struct PriorityInfo {
293 // IDs of streams that are ready to write.
294 ReadyList ready_list;
295 // Time of latest write event for stream of this priority, in microseconds.
296 int64_t last_event_time_usec = 0;
297 };
298
299 typedef std::unordered_map<StreamIdType, StreamInfo> StreamInfoMap;
300
301 // Erases first occurrence (which should be the only one) of |info| in
302 // |ready_list|, returning true if found (and erased), or false otherwise.
303 // Decrements |num_ready_streams_| if an entry is erased.
304 bool Erase(ReadyList* ready_list, const StreamInfo& info) {
305 auto it = std::find(ready_list->begin(), ready_list->end(), &info);
306 if (it == ready_list->end()) {
307 return false;
308 }
309 ready_list->erase(it);
310 --num_ready_streams_;
311 return true;
312 }
313
314 // Number of ready streams.
315 size_t num_ready_streams_ = 0;
316 // Per-priority state, including ready lists.
317 PriorityInfo priority_infos_[kV3LowestPriority + 1];
318 // StreamInfos for all registered streams.
319 StreamInfoMap stream_infos_;
nharpercd820e02019-05-16 15:12:07 -0700320 StreamIdType root_stream_id_;
QUICHE team82dee2f2019-01-18 12:35:12 -0500321};
322
323} // namespace spdy
324
325#endif // QUICHE_SPDY_CORE_PRIORITY_WRITE_SCHEDULER_H_