summaryrefslogtreecommitdiffstats
path: root/net/spdy/priority_write_scheduler.h
blob: e1e144dd96c5436d3b08640a9a5a30f066f9a6d5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
// Copyright (c) 2015 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 NET_SPDY_PRIORITY_WRITE_SCHEDULER_H_
#define NET_SPDY_PRIORITY_WRITE_SCHEDULER_H_

#include <stddef.h>

#include <algorithm>
#include <deque>
#include <unordered_map>
#include <utility>

#include "base/logging.h"
#include "net/spdy/spdy_protocol.h"

namespace net {

// Class that manages the order in which streams are written using the SPDY
// priority scheme described at:
// https://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1#TOC-2.3.3-Stream-priority
//
// Callers must first register a stream with the PriorityWriteScheduler (by
// calling RegisterStream(), which informs the PriorityWriteScheduler of the
// stream's priority) before calling other methods referencing that stream,
// which may implicitly use the stream's priority. When the stream is
// eventually closed, the caller should unregister it from the
// PriorityWriteScheduler (by calling UnregisterStream()), to free data
// structures associated with it.
//
// Each stream can be in one of two states: ready or not ready (for writing).
// Ready state is changed by calling the MarkStreamReady() and
// MarkStreamNotReady() methods. Only streams in the ready state can be
// returned by PopNextReadyStream(); when returned by that method, the stream's
// state changes to not ready.
//
// Internally, PriorityWriteScheduler consists of 8 per-priority sublists, one
// for each priority value.  The elements (if any) of each sublist are streams
// that are ready to write and have that priority.
template <typename StreamIdType>
class PriorityWriteScheduler {
 public:
  // Creates scheduler with no streams.
  PriorityWriteScheduler() = default;

  // Registers the given stream with the scheduler, which will now track its
  // priority and ready state. If the stream was already registered, logs
  // DFATAL and does nothing.
  void RegisterStream(StreamIdType stream_id, SpdyPriority priority) {
    priority = ClampPriority(priority);
    StreamInfo stream_info = {priority, false};
    bool inserted =
        stream_infos_.insert(std::make_pair(stream_id, stream_info)).second;
    if (!inserted) {
      LOG(DFATAL) << "Stream " << stream_id << " already registered";
    }
  }

  // Unregisters the given stream from the scheduler, which will no
  // longer keep track of its priority and ready state. If the stream
  // was not previously registered, logs DFATAL and does nothing.
  void UnregisterStream(StreamIdType stream_id) {
    auto it = stream_infos_.find(stream_id);
    if (it == stream_infos_.end()) {
      LOG(DFATAL) << "Stream " << stream_id << " not registered";
      return;
    }
    StreamInfo& stream_info = it->second;
    if (stream_info.ready) {
      bool erased = Erase(&ready_lists_[stream_info.priority], stream_id);
      DCHECK(erased);
    }
    stream_infos_.erase(it);
  }

  // Returns the priority value for the specified stream. If the stream is not
  // registered, logs DFATAL and returns the lowest priority.
  SpdyPriority GetStreamPriority(StreamIdType stream_id) const {
    auto it = stream_infos_.find(stream_id);
    if (it == stream_infos_.end()) {
      LOG(DFATAL) << "Stream " << stream_id << " not registered";
      return kV3LowestPriority;
    }
    return it->second.priority;
  }

  // Updates the priority of the given stream. If the stream is not registered,
  // logs DFATAL and does nothing.
  void UpdateStreamPriority(StreamIdType stream_id, SpdyPriority priority) {
    auto it = stream_infos_.find(stream_id);
    if (it == stream_infos_.end()) {
      LOG(DFATAL) << "Stream " << stream_id << " not registered";
      return;
    }
    StreamInfo& stream_info = it->second;
    if (stream_info.priority == priority) {
      return;
    }
    if (stream_info.ready) {
      bool erased = Erase(&ready_lists_[stream_info.priority], stream_id);
      DCHECK(erased);
      ready_lists_[priority].push_back(stream_id);
    }
    stream_info.priority = priority;
  }

  // If the scheduler has any ready streams, pops the next stream ID from the
  // highest priority non-empty ready list and returns it, transitioning the
  // stream from ready to not ready. If the scheduler doesn't have any ready
  // streams, logs DFATAL and returns 0.
  StreamIdType PopNextReadyStream() {
    StreamIdType stream_id = 0;
    for (SpdyPriority p = kV3HighestPriority; p <= kV3LowestPriority; ++p) {
      StreamIdList& ready_list = ready_lists_[p];
      if (!ready_list.empty()) {
        stream_id = ready_list.front();
        ready_list.pop_front();

        auto it = stream_infos_.find(stream_id);
        if (it == stream_infos_.end()) {
          LOG(DFATAL) << "Missing StreamInfo for stream " << stream_id;
        } else {
          it->second.ready = false;
        }
        return stream_id;
      }
    }
    LOG(DFATAL) << "No ready streams available";
    return stream_id;
  }

  // Returns true if there's another stream of greater or equal priority ahead
  // of |stream_id| in the queue.  This function can be called to see if
  // |stream_id| should yield work to another stream.
  bool ShouldYield(StreamIdType stream_id) const {
    // If there's a higher priority stream, this stream should yield.
    if (HasHigherPriorityReadyStream(stream_id)) {
      return true;
    }

    auto it = stream_infos_.find(stream_id);
    if (it == stream_infos_.end()) {
      LOG(DFATAL) << "Stream " << stream_id << " not registered";
      return false;
    }

    // If this priority level is empty, or this stream is the next up, there's
    // no need to yield.
    auto ready_list = ready_lists_[it->second.priority];
    if (ready_list.empty() || ready_list.front() == stream_id) {
      return false;
    }

    // There are other streams in this priority level which take precedence.
    // Yield.
    return true;
  }

  // Returns true if the scheduler has any ready streams with a higher priority
  // than that of the specified stream. If the stream is not registered, logs
  // DFATAL and returns false.
  bool HasHigherPriorityReadyStream(StreamIdType stream_id) const {
    auto it = stream_infos_.find(stream_id);
    if (it == stream_infos_.end()) {
      LOG(DFATAL) << "Stream " << stream_id << " not registered";
      return false;
    }
    const StreamInfo& stream_info = it->second;
    for (SpdyPriority p = kV3HighestPriority; p < stream_info.priority; ++p) {
      if (!ready_lists_[p].empty()) {
        return true;
      }
    }
    return false;
  }

  // Marks the given stream as ready to write. If stream was already ready,
  // does nothing. If stream was not registered, logs DFATAL and does
  // nothing. If |add_to_front| is true, adds stream to the front of its
  // per-priority ready list, otherwise adds it to the back.
  void MarkStreamReady(StreamIdType stream_id, bool add_to_front) {
    auto it = stream_infos_.find(stream_id);
    if (it == stream_infos_.end()) {
      LOG(DFATAL) << "Stream " << stream_id << " not registered";
      return;
    }
    StreamInfo& stream_info = it->second;
    if (stream_info.ready) {
      return;
    }
    StreamIdList& ready_list = ready_lists_[stream_info.priority];
    if (add_to_front) {
      ready_list.push_front(stream_id);
    } else {
      ready_list.push_back(stream_id);
    }
    stream_info.ready = true;
  }

  // Marks the given stream as not ready to write, removing it from the ready
  // list for its priority. If stream was already not ready, does nothing. If
  // stream was not registered, logs DFATAL and does nothing.
  void MarkStreamNotReady(StreamIdType stream_id) {
    auto it = stream_infos_.find(stream_id);
    if (it == stream_infos_.end()) {
      LOG(DFATAL) << "Stream " << stream_id << " not registered";
      return;
    }
    StreamInfo& stream_info = it->second;
    if (!stream_info.ready) {
      return;
    }
    bool erased = Erase(&ready_lists_[stream_info.priority], stream_id);
    DCHECK(erased);
    stream_info.ready = false;
  }

  // Returns true iff the number of ready streams is non-zero.
  bool HasReadyStreams() const {
    for (SpdyPriority i = kV3HighestPriority; i <= kV3LowestPriority; ++i) {
      if (!ready_lists_[i].empty()) {
        return true;
      }
    }
    return false;
  }

  // Returns the number of ready streams.
  size_t NumReadyStreams() const {
    size_t n = 0;
    for (SpdyPriority i = kV3HighestPriority; i <= kV3LowestPriority; ++i) {
      n += ready_lists_[i].size();
    }
    return n;
  }

  // Returns the number of ready streams with the given priority.
  size_t NumReadyStreams(SpdyPriority priority) const {
    priority = ClampPriority(priority);
    return ready_lists_[priority].size();
  }

 private:
  // 0(1) size lookup, 0(1) insert at front or back.
  typedef std::deque<StreamIdType> StreamIdList;

  // State kept for all registered streams. All ready streams have ready = true
  // and should be present in ready_lists_[priority].
  struct StreamInfo {
    SpdyPriority priority;
    bool ready;
  };

  typedef std::unordered_map<StreamIdType, StreamInfo> StreamInfoMap;

  static SpdyPriority ClampPriority(SpdyPriority priority) {
    if (priority < kV3HighestPriority) {
      LOG(DFATAL) << "Invalid priority: " << static_cast<int>(priority);
      return kV3HighestPriority;
    }
    if (priority > kV3LowestPriority) {
      LOG(DFATAL) << "Invalid priority: " << static_cast<int>(priority);
      return kV3LowestPriority;
    }
    return priority;
  }

  // Erases first occurrence (which should be the only one) of |stream_id| in
  // |ready_list|, returning true if found (and erased), or false otherwise.
  bool Erase(StreamIdList* ready_list, StreamIdType stream_id) {
    auto it = std::find(ready_list->begin(), ready_list->end(), stream_id);
    if (it == ready_list->end()) {
      return false;
    }
    ready_list->erase(it);
    return true;
  }

  // IDs of streams that are ready to write, grouped by priority.
  StreamIdList ready_lists_[kV3LowestPriority + 1];
  // StreamInfos for all registered streams.
  StreamInfoMap stream_infos_;
};

}  // namespace net

#endif  // NET_SPDY_PRIORITY_WRITE_SCHEDULER_H_