@fails-components/webtransport 1.1.4 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/dom.d.ts +27 -10
- package/dist/lib/dom.d.ts.map +1 -1
- package/dist/lib/http2/browser/browser.d.ts +5 -2
- package/dist/lib/http2/browser/browser.d.ts.map +1 -1
- package/dist/lib/http2/browser/browserparser.d.ts.map +1 -1
- package/dist/lib/http2/node/capsuleparser.d.ts.map +1 -1
- package/dist/lib/http2/node/client.d.ts +3 -2
- package/dist/lib/http2/node/client.d.ts.map +1 -1
- package/dist/lib/http2/node/server.d.ts +10 -5
- package/dist/lib/http2/node/server.d.ts.map +1 -1
- package/dist/lib/http2/node/websocketparser.d.ts.map +1 -1
- package/dist/lib/http2/parserbase.d.ts +33 -1
- package/dist/lib/http2/parserbase.d.ts.map +1 -1
- package/dist/lib/http2/priorityscheduler.d.ts +167 -0
- package/dist/lib/http2/priorityscheduler.d.ts.map +1 -0
- package/dist/lib/http2/session.d.ts +25 -16
- package/dist/lib/http2/session.d.ts.map +1 -1
- package/dist/lib/http2/stream.d.ts +10 -0
- package/dist/lib/http2/stream.d.ts.map +1 -1
- package/dist/lib/http2/websocketcommon.d.ts.map +1 -1
- package/dist/lib/server.d.ts.map +1 -1
- package/dist/lib/session.d.ts +31 -17
- package/dist/lib/session.d.ts.map +1 -1
- package/dist/lib/stream.d.ts +13 -0
- package/dist/lib/stream.d.ts.map +1 -1
- package/dist/lib/types.d.ts +18 -15
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/webtransport.browser.d.ts +3 -2
- package/dist/lib/webtransport.browser.d.ts.map +1 -1
- package/dist/lib/webtransportbase.d.ts +1 -0
- package/dist/lib/webtransportbase.d.ts.map +1 -1
- package/eslint.config.js +20 -22
- package/lib/dom.ts +31 -13
- package/lib/http2/browser/browser.js +28 -13
- package/lib/http2/browser/browserparser.js +4 -1
- package/lib/http2/node/capsuleparser.js +4 -1
- package/lib/http2/node/client.js +38 -27
- package/lib/http2/node/server.js +56 -19
- package/lib/http2/node/websocketparser.js +5 -2
- package/lib/http2/parserbase.js +76 -7
- package/lib/http2/priorityscheduler.js +654 -0
- package/lib/http2/session.js +69 -20
- package/lib/http2/stream.js +60 -10
- package/lib/http2/websocketcommon.js +1 -1
- package/lib/server.js +0 -1
- package/lib/session.js +128 -64
- package/lib/stream.js +50 -8
- package/lib/types.ts +15 -8
- package/lib/webtransport.browser.js +77 -18
- package/lib/webtransportbase.js +7 -0
- package/old_test/testsuite.js +6 -2
- package/package.json +6 -6
- package/readme.md +3 -1
|
@@ -0,0 +1,654 @@
|
|
|
1
|
+
// Ported from libquiche, PriorityScheduler and BTreeScheduler so their license applies to the original in C++ and this javascript translation
|
|
2
|
+
// Copyright (c) 2018 The Chromium Authors. All rights reserved.
|
|
3
|
+
// Use of this source code is governed by a BSD-style license that can be
|
|
4
|
+
// found in the LICENSE file.
|
|
5
|
+
|
|
6
|
+
// A record for a registered stream.
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {object} Priority
|
|
10
|
+
* @property {SendGroupId} sendGroupId
|
|
11
|
+
* @property {bigint} sendOrder
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {bigint} SendGroupId
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {bigint} SendOrder
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
*
|
|
24
|
+
* @param {StreamId} sendGroupId
|
|
25
|
+
* @returns {string}
|
|
26
|
+
*/
|
|
27
|
+
function sendGroupIdToKey(sendGroupId) {
|
|
28
|
+
return sendGroupId.toString()
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* @typedef {bigint} StreamId
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
*
|
|
36
|
+
* @param {StreamId} streamId
|
|
37
|
+
* @returns {string}
|
|
38
|
+
*/
|
|
39
|
+
function streamIdToKey(streamId) {
|
|
40
|
+
return streamId.toString()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
class StreamEntry {
|
|
44
|
+
/**
|
|
45
|
+
* @param {SendOrder} priority
|
|
46
|
+
*/
|
|
47
|
+
constructor(priority) {
|
|
48
|
+
this.priority = priority
|
|
49
|
+
/**
|
|
50
|
+
* @type {number|undefined}
|
|
51
|
+
*/
|
|
52
|
+
this.currentSequenceNumber = undefined
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
scheduled() {
|
|
56
|
+
return typeof this.currentSequenceNumber !== 'undefined'
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @typedef {object} FullStreamEntry
|
|
62
|
+
* @property {SendGroupId} iD
|
|
63
|
+
* @property {StreamEntry} streamEntry
|
|
64
|
+
*/
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @typedef {object} ScheduleKey
|
|
68
|
+
* @property {SendOrder} priority
|
|
69
|
+
* @property {number} sequenceNumber
|
|
70
|
+
*/
|
|
71
|
+
|
|
72
|
+
/*struct StreamEntry {
|
|
73
|
+
// The current priority of the stream.
|
|
74
|
+
ABSL_ATTRIBUTE_NO_UNIQUE_ADDRESS Priority priority;
|
|
75
|
+
// If present, the sequence number with which the stream is currently
|
|
76
|
+
// scheduled. If absent, indicates that the stream is not scheduled.
|
|
77
|
+
std::optional<int> currentSequenceNumber = std::nullopt;
|
|
78
|
+
|
|
79
|
+
bool scheduled() const { return currentSequenceNumber.has_value(); }
|
|
80
|
+
};
|
|
81
|
+
// The full entry for the stream (includes the ID that's used as a hashmap
|
|
82
|
+
// key).
|
|
83
|
+
using FullStreamEntry = std::pair<const Id, StreamEntry>;
|
|
84
|
+
|
|
85
|
+
// A key that is used to order entities within the schedule.
|
|
86
|
+
struct ScheduleKey {
|
|
87
|
+
// The main order key: the priority of the stream.
|
|
88
|
+
ABSL_ATTRIBUTE_NO_UNIQUE_ADDRESS Priority priority;
|
|
89
|
+
// The secondary order key: the sequence number.
|
|
90
|
+
int sequenceNumber;
|
|
91
|
+
|
|
92
|
+
// Orders schedule keys in order of decreasing priority.
|
|
93
|
+
bool operator<(const ScheduleKey& other) const {
|
|
94
|
+
return std::make_tuple(priority, sequenceNumber) >
|
|
95
|
+
std::make_tuple(other.priority, other.sequenceNumber);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// In order to find all entities with priority `p`, one can iterate between
|
|
99
|
+
// `lower_bound(MinForPriority(p))` and `upper_bound(MaxForPriority(p))`.
|
|
100
|
+
static ScheduleKey MinForPriority(Priority priority) {
|
|
101
|
+
return ScheduleKey{priority, std::numeric_limits<int>::max()};
|
|
102
|
+
}
|
|
103
|
+
static ScheduleKey MaxForPriority(Priority priority) {
|
|
104
|
+
return ScheduleKey{priority, std::numeric_limits<int>::min()};
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
*/
|
|
108
|
+
// BTreeScheduler is a data structure that allows streams (and potentially other
|
|
109
|
+
// entities) to be scheduled according to the arbitrary priorities. The API for
|
|
110
|
+
// using the scheduler can be used as follows:
|
|
111
|
+
// - A stream has to be registered with a priority before being scheduled.
|
|
112
|
+
// - A stream can be unregistered, or can be re-prioritized.
|
|
113
|
+
// - A stream can be scheduled; that adds it into the queue.
|
|
114
|
+
// - PopFront() will return the stream with highest priority.
|
|
115
|
+
// - ShouldYield() will return if there is a stream with higher priority than
|
|
116
|
+
// the specified one.
|
|
117
|
+
//
|
|
118
|
+
// The prioritization works as following:
|
|
119
|
+
// - If two streams have different priorities, the higher priority stream goes
|
|
120
|
+
// first.
|
|
121
|
+
// - If two streams have the same priority, the one that got scheduled earlier
|
|
122
|
+
// goes first. Internally, this is implemented by assigning a monotonically
|
|
123
|
+
// decreasing sequence number to every newly scheduled stream.
|
|
124
|
+
//
|
|
125
|
+
// The Id type has to define operator==, be hashable via absl::Hash, and
|
|
126
|
+
// printable via operator<<; the Priority type has to define operator<.
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
*
|
|
130
|
+
* @param {ScheduleKey} a
|
|
131
|
+
* @param {ScheduleKey} b
|
|
132
|
+
* @returns
|
|
133
|
+
*/
|
|
134
|
+
function compareStreamEntries(a, b) {
|
|
135
|
+
// for sort
|
|
136
|
+
if (a.priority > b.priority) {
|
|
137
|
+
return -1
|
|
138
|
+
} else if (a.priority === b.priority) {
|
|
139
|
+
if (a.sequenceNumber > b.sequenceNumber) {
|
|
140
|
+
return -1
|
|
141
|
+
} else if (a.sequenceNumber === b.sequenceNumber) {
|
|
142
|
+
return 0
|
|
143
|
+
} else {
|
|
144
|
+
return 1
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
return 1
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
*
|
|
152
|
+
* @param {{scheduleKey: ScheduleKey}} a
|
|
153
|
+
* @param {{scheduleKey: ScheduleKey}} b
|
|
154
|
+
* @returns
|
|
155
|
+
*/
|
|
156
|
+
|
|
157
|
+
function compareFullStreamEntries(a, b) {
|
|
158
|
+
return compareStreamEntries(a.scheduleKey, b.scheduleKey)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
class BTreeScheduler {
|
|
162
|
+
constructor() {
|
|
163
|
+
// The map of currently registered streams.
|
|
164
|
+
/**
|
|
165
|
+
* @type {Object<string, StreamEntry>}
|
|
166
|
+
*/
|
|
167
|
+
this.streams_ = {} // Id, StreamEntry
|
|
168
|
+
// The stream schedule, ordered starting from the highest priority stream.
|
|
169
|
+
/**
|
|
170
|
+
* @type {{scheduleKey: ScheduleKey, fullStreamEntry: FullStreamEntry}[]}
|
|
171
|
+
*/
|
|
172
|
+
this.schedule_ = [] // ScheduleKey, FullStreamEntry
|
|
173
|
+
|
|
174
|
+
// The counter that is used to ensure that streams with the same priority are
|
|
175
|
+
// handled in the FIFO order. Decreases with every write.
|
|
176
|
+
this.currentWriteSequenceNumber_ = 0
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Returns true if there are any streams registered.
|
|
180
|
+
HasRegistered() {
|
|
181
|
+
return Object.keys(this.streams_).length !== 0
|
|
182
|
+
}
|
|
183
|
+
// Returns true if there are any streams scheduled.
|
|
184
|
+
HasScheduled() {
|
|
185
|
+
return Object.keys(this.schedule_).length !== 0
|
|
186
|
+
}
|
|
187
|
+
// Returns the number of currently scheduled streams.
|
|
188
|
+
NumScheduled() {
|
|
189
|
+
return Object.keys(this.schedule_).length
|
|
190
|
+
}
|
|
191
|
+
// Returns the total number of currently registered streams.
|
|
192
|
+
NumRegistered() {
|
|
193
|
+
return Object.keys(this.streams_).length
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Counts the number of scheduled entries in the range [min, max]. If either
|
|
197
|
+
// min or max is omitted, negative or positive infinity is assumed.
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* @param {number} min
|
|
201
|
+
* @param {number} max
|
|
202
|
+
*/
|
|
203
|
+
NumScheduledInPriorityRange(min, max) {
|
|
204
|
+
/*if (typeof min !== 'undefined' && typeof max !== 'undefined') {
|
|
205
|
+
//QUICHE_DCHECK(*min <= *max);
|
|
206
|
+
}*/
|
|
207
|
+
// This is reversed, since the schedule is ordered in the descending priority
|
|
208
|
+
// order.
|
|
209
|
+
const begin =
|
|
210
|
+
typeof max !== 'undefined'
|
|
211
|
+
? this.schedule_.findIndex((el) => el.scheduleKey.priority <= max)
|
|
212
|
+
: 0
|
|
213
|
+
const end =
|
|
214
|
+
typeof min !== 'undefined'
|
|
215
|
+
? this.schedule_.findIndex((el) => el.scheduleKey.priority >= min)
|
|
216
|
+
: this.schedule_.length
|
|
217
|
+
return end - begin
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Returns true if there is a stream that would go before `id` in the
|
|
221
|
+
// schedule.
|
|
222
|
+
/**
|
|
223
|
+
* @param {StreamId} streamId
|
|
224
|
+
*/
|
|
225
|
+
ShouldYield(streamId) {
|
|
226
|
+
const stream = this.streams_[streamIdToKey(streamId)]
|
|
227
|
+
if (!stream) {
|
|
228
|
+
throw new Error('ID not registered')
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (this.schedule_.length === 0) {
|
|
232
|
+
return false
|
|
233
|
+
}
|
|
234
|
+
const next = this.schedule_[0]
|
|
235
|
+
if (BTreeScheduler.StreamId(next) == streamId) {
|
|
236
|
+
return false
|
|
237
|
+
}
|
|
238
|
+
return next.scheduleKey.priority >= stream.priority
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Returns the priority for `id`, or nullopt if stream is not registered.
|
|
242
|
+
/**
|
|
243
|
+
* @param {StreamId} id
|
|
244
|
+
*/
|
|
245
|
+
GetPriorityFor(id) {
|
|
246
|
+
const it = this.streams_[streamIdToKey(id)]
|
|
247
|
+
if (!it) {
|
|
248
|
+
return undefined
|
|
249
|
+
}
|
|
250
|
+
return it.priority
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Pops the highest priority stream. Will fail if the schedule is empty.
|
|
254
|
+
PopFront() {
|
|
255
|
+
if (this.schedule_.length === 0) {
|
|
256
|
+
return undefined
|
|
257
|
+
}
|
|
258
|
+
const scheduleIt = this.schedule_[0]
|
|
259
|
+
//QUICHE_DCHECK(scheduleIt->second->second.scheduled());
|
|
260
|
+
scheduleIt.fullStreamEntry.streamEntry.currentSequenceNumber = undefined
|
|
261
|
+
|
|
262
|
+
const result = BTreeScheduler.StreamId(scheduleIt)
|
|
263
|
+
this.schedule_.shift()
|
|
264
|
+
return result
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Registers the specified stream with the supplied priority. The stream must
|
|
268
|
+
// not be already registered.
|
|
269
|
+
/**
|
|
270
|
+
* @param {StreamId} streamId
|
|
271
|
+
* @param {Priority} priority
|
|
272
|
+
*/
|
|
273
|
+
Register(streamId, priority) {
|
|
274
|
+
if (this.streams_[streamIdToKey(streamId)]) {
|
|
275
|
+
throw new Error('ID already registered')
|
|
276
|
+
}
|
|
277
|
+
this.streams_[streamIdToKey(streamId)] = new StreamEntry(priority.sendOrder)
|
|
278
|
+
}
|
|
279
|
+
// Unregisters a previously registered stream.
|
|
280
|
+
/**
|
|
281
|
+
* @param {StreamId} streamId
|
|
282
|
+
*/
|
|
283
|
+
Unregister(streamId) {
|
|
284
|
+
const it = /** @type {StreamEntry|undefined} */ (
|
|
285
|
+
this.streams_[streamIdToKey(streamId)]
|
|
286
|
+
)
|
|
287
|
+
if (!it) {
|
|
288
|
+
throw new Error('Stream not registered')
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (it.scheduled()) {
|
|
292
|
+
this.DescheduleStream(it)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
delete this.streams_[streamIdToKey(streamId)]
|
|
296
|
+
}
|
|
297
|
+
// Alters the priority of an already registered stream.
|
|
298
|
+
/**
|
|
299
|
+
* @param {StreamId} streamId
|
|
300
|
+
* @param {SendOrder} newPriority
|
|
301
|
+
*/
|
|
302
|
+
UpdatePriority(streamId, newPriority) {
|
|
303
|
+
const stream = this.streams_[streamIdToKey(streamId)]
|
|
304
|
+
if (!stream) {
|
|
305
|
+
return new Error('ID not registered')
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
let sequenceNumber
|
|
309
|
+
if (stream.scheduled()) {
|
|
310
|
+
const oldEntry = this.DescheduleStream(stream)
|
|
311
|
+
sequenceNumber = oldEntry.scheduleKey.sequenceNumber
|
|
312
|
+
//QUICHE_DCHECK_EQ(oldEntry->second, &*it);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
stream.priority = newPriority
|
|
316
|
+
if (sequenceNumber) {
|
|
317
|
+
this.schedule_.push({
|
|
318
|
+
scheduleKey: { priority: stream.priority, sequenceNumber },
|
|
319
|
+
fullStreamEntry: {
|
|
320
|
+
iD: streamId,
|
|
321
|
+
streamEntry: stream
|
|
322
|
+
}
|
|
323
|
+
})
|
|
324
|
+
this.schedule_.sort(compareFullStreamEntries)
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Adds the `stream` into the schedule if it's not already there.
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* @param {StreamId} streamId
|
|
332
|
+
*/
|
|
333
|
+
Schedule(streamId) {
|
|
334
|
+
const streamIt = this.streams_[streamIdToKey(streamId)]
|
|
335
|
+
if (!streamIt) {
|
|
336
|
+
return new Error('ID not registered')
|
|
337
|
+
}
|
|
338
|
+
if (streamIt.scheduled()) {
|
|
339
|
+
return
|
|
340
|
+
}
|
|
341
|
+
const newElement = {
|
|
342
|
+
scheduleKey: {
|
|
343
|
+
priority: streamIt.priority,
|
|
344
|
+
sequenceNumber: --this.currentWriteSequenceNumber_
|
|
345
|
+
},
|
|
346
|
+
fullStreamEntry: {
|
|
347
|
+
iD: streamId,
|
|
348
|
+
streamEntry: streamIt
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
this.schedule_.push(newElement)
|
|
352
|
+
this.schedule_.sort(compareFullStreamEntries)
|
|
353
|
+
|
|
354
|
+
streamIt.currentSequenceNumber = newElement.scheduleKey.sequenceNumber
|
|
355
|
+
}
|
|
356
|
+
// Deschedules a stream that is known to be currently scheduled.
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* @param {StreamId} streamId
|
|
360
|
+
*/
|
|
361
|
+
Deschedule(streamId) {
|
|
362
|
+
const stream = this.streams_[streamIdToKey(streamId)]
|
|
363
|
+
if (!stream) {
|
|
364
|
+
throw new Error('Stream not registered')
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (!stream.scheduled()) {
|
|
368
|
+
throw new Error('Stream not scheduled')
|
|
369
|
+
}
|
|
370
|
+
this.DescheduleStream(stream)
|
|
371
|
+
stream.currentSequenceNumber = undefined
|
|
372
|
+
}
|
|
373
|
+
// Returns true if `stream` is in the schedule.
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* @param {StreamId} streamId
|
|
377
|
+
*/
|
|
378
|
+
IsScheduled(streamId) {
|
|
379
|
+
const streamIt = this.streams_[streamIdToKey(streamId)]
|
|
380
|
+
if (!streamIt) {
|
|
381
|
+
return false
|
|
382
|
+
}
|
|
383
|
+
return streamIt.scheduled()
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/*
|
|
387
|
+
using FullScheduleEntry = std::pair<const ScheduleKey, FullStreamEntry*>;
|
|
388
|
+
using ScheduleIterator =
|
|
389
|
+
typename absl::btree_map<ScheduleKey, FullStreamEntry*>::const_iterator;*/
|
|
390
|
+
|
|
391
|
+
// Convenience method to get the stream ID for a schedule entry.
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* @param {{scheduleKey: ScheduleKey, fullStreamEntry: FullStreamEntry}} entry
|
|
395
|
+
*/
|
|
396
|
+
static StreamId(entry) {
|
|
397
|
+
return entry.fullStreamEntry.iD
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Removes a stream from the schedule, and returns the old entry if it were
|
|
401
|
+
// present.
|
|
402
|
+
/**
|
|
403
|
+
* @param {StreamEntry} entry
|
|
404
|
+
*/
|
|
405
|
+
DescheduleStream(entry) {
|
|
406
|
+
//QUICHE_DCHECK(entry.scheduled());
|
|
407
|
+
const it = this.schedule_.findIndex(
|
|
408
|
+
(el) =>
|
|
409
|
+
entry.priority === el.scheduleKey.priority &&
|
|
410
|
+
entry.currentSequenceNumber ===
|
|
411
|
+
el.fullStreamEntry.streamEntry.currentSequenceNumber
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
if (it === -1) {
|
|
415
|
+
throw new Error(
|
|
416
|
+
'Calling DescheduleStream() on an entry that is not in the schedule at ' +
|
|
417
|
+
'the expected key.'
|
|
418
|
+
)
|
|
419
|
+
}
|
|
420
|
+
const result = this.schedule_[it]
|
|
421
|
+
this.schedule_.splice(it, 1)
|
|
422
|
+
return result
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
export class PriorityScheduler {
|
|
427
|
+
constructor() {
|
|
428
|
+
// using PerGroupScheduler = quiche:: BTreeScheduler<StreamId, SendOrder>;
|
|
429
|
+
// using GroupSchedulerPair = std:: pair<const SendGroupId, PerGroupScheduler>;
|
|
430
|
+
|
|
431
|
+
// Round-robin schedule for the groups.
|
|
432
|
+
this.activeGroups_ = new BTreeScheduler() // SendGroupId, SinglePriority
|
|
433
|
+
// Map group ID to the scheduler for the group in question.
|
|
434
|
+
/**
|
|
435
|
+
* @type {Object<string, BTreeScheduler>}
|
|
436
|
+
*/
|
|
437
|
+
this.perGroupSchedulers_ = {} // absl:: node_hash_map < SendGroupId, PerGroupScheduler >
|
|
438
|
+
// Map stream ID to a pointer to the entry in `perGroupSchedulers_`.
|
|
439
|
+
/**
|
|
440
|
+
* @type {Object<string, {sendGroupId: SendGroupId, perGroupScheduler: BTreeScheduler }>}
|
|
441
|
+
*/
|
|
442
|
+
this.streamToGroupMap_ = {} // absl:: flat_hash_map < StreamId, GroupSchedulerPair *>
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Returns true if there are any streams registered.
|
|
446
|
+
HasRegistered() {
|
|
447
|
+
return this.activeGroups_.HasRegistered()
|
|
448
|
+
}
|
|
449
|
+
// Returns true if there are any streams scheduled.
|
|
450
|
+
HasScheduled() {
|
|
451
|
+
return this.activeGroups_.HasScheduled()
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Returns the number of currently scheduled streams.
|
|
455
|
+
NumScheduled() {
|
|
456
|
+
let total = 0
|
|
457
|
+
for (const [, groupScheduler] of Object.entries(this.perGroupSchedulers_)) {
|
|
458
|
+
total += groupScheduler.NumScheduled()
|
|
459
|
+
}
|
|
460
|
+
return total
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Registers the specified stream with the supplied priority. The stream must
|
|
464
|
+
// not be already registered.
|
|
465
|
+
/**
|
|
466
|
+
* @param {StreamId} streamId
|
|
467
|
+
* @param {Priority} priority
|
|
468
|
+
*/
|
|
469
|
+
Register(streamId, priority) {
|
|
470
|
+
if (this.streamToGroupMap_[streamIdToKey(streamId)]) {
|
|
471
|
+
throw new Error('Provided stream ID already registered')
|
|
472
|
+
}
|
|
473
|
+
if (!this.perGroupSchedulers_[sendGroupIdToKey(priority.sendGroupId)]) {
|
|
474
|
+
this.perGroupSchedulers_[sendGroupIdToKey(priority.sendGroupId)] =
|
|
475
|
+
new BTreeScheduler()
|
|
476
|
+
this.activeGroups_.Register(priority.sendGroupId, priority) // TODO, may be we have to add a scheduler
|
|
477
|
+
}
|
|
478
|
+
const scheduler =
|
|
479
|
+
this.perGroupSchedulers_[sendGroupIdToKey(priority.sendGroupId)]
|
|
480
|
+
this.streamToGroupMap_[streamIdToKey(streamId)] = {
|
|
481
|
+
sendGroupId: priority.sendGroupId,
|
|
482
|
+
perGroupScheduler: scheduler
|
|
483
|
+
}
|
|
484
|
+
scheduler.Register(streamId, priority)
|
|
485
|
+
}
|
|
486
|
+
// Unregisters a previously registered stream.
|
|
487
|
+
/**
|
|
488
|
+
* @param {StreamId} streamId
|
|
489
|
+
*/
|
|
490
|
+
Unregister(streamId) {
|
|
491
|
+
const stream = this.streamToGroupMap_[streamIdToKey(streamId)]
|
|
492
|
+
if (!stream) {
|
|
493
|
+
throw new Error('Stream ID not registered')
|
|
494
|
+
}
|
|
495
|
+
const groupId = stream.sendGroupId
|
|
496
|
+
const groupScheduler = stream.perGroupScheduler
|
|
497
|
+
delete this.streamToGroupMap_[streamIdToKey(streamId)]
|
|
498
|
+
groupScheduler.Unregister(streamId)
|
|
499
|
+
|
|
500
|
+
// Clean up the group if there are no more streams associated with it.
|
|
501
|
+
if (!groupScheduler.HasRegistered()) {
|
|
502
|
+
delete this.perGroupSchedulers_[sendGroupIdToKey(groupId)]
|
|
503
|
+
this.activeGroups_.Unregister(groupId)
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
// Alters the priority of an already registered stream.
|
|
507
|
+
/**
|
|
508
|
+
* @param {StreamId} streamId
|
|
509
|
+
* @param {bigint} newSendOrder
|
|
510
|
+
*/
|
|
511
|
+
UpdateSendOrder(streamId, newSendOrder) {
|
|
512
|
+
const scheduler = this.SchedulerForStream(streamId)
|
|
513
|
+
if (!scheduler) {
|
|
514
|
+
throw new Error('Stream ID not registered')
|
|
515
|
+
}
|
|
516
|
+
return scheduler.UpdatePriority(streamId, newSendOrder)
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* @param {StreamId} streamId
|
|
521
|
+
* @param {SendGroupId} newSendGroup
|
|
522
|
+
*/
|
|
523
|
+
UpdateSendGroup(streamId, newSendGroup) {
|
|
524
|
+
const scheduler = this.SchedulerForStream(streamId)
|
|
525
|
+
if (!scheduler) {
|
|
526
|
+
throw new Error('Stream ID not registered')
|
|
527
|
+
}
|
|
528
|
+
const isScheduled = scheduler.IsScheduled(streamId)
|
|
529
|
+
const sendOrder = scheduler.GetPriorityFor(streamId)
|
|
530
|
+
if (!sendOrder) {
|
|
531
|
+
throw new Error(
|
|
532
|
+
'Stream registered at the top level scheduler, but not at the per-group one'
|
|
533
|
+
)
|
|
534
|
+
}
|
|
535
|
+
this.Unregister(streamId)
|
|
536
|
+
|
|
537
|
+
this.Register(streamId, { sendGroupId: newSendGroup, sendOrder })
|
|
538
|
+
if (isScheduled) {
|
|
539
|
+
this.Schedule(streamId)
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Returns true if there is a stream that would go before `id` in the
|
|
544
|
+
// schedule.
|
|
545
|
+
/**
|
|
546
|
+
* @param {StreamId} streamId
|
|
547
|
+
*/
|
|
548
|
+
ShouldYield(streamId) {
|
|
549
|
+
const stream = this.streamToGroupMap_[streamIdToKey(streamId)]
|
|
550
|
+
if (!stream) {
|
|
551
|
+
throw new Error('Stream ID not registered')
|
|
552
|
+
}
|
|
553
|
+
const { sendGroupId, perGroupScheduler } = stream
|
|
554
|
+
|
|
555
|
+
const perGroupResult = this.activeGroups_.ShouldYield(sendGroupId)
|
|
556
|
+
if (perGroupResult) {
|
|
557
|
+
return true
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
return perGroupScheduler.ShouldYield(streamId)
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// Returns the priority for `id`, or nullopt if stream is not registered.
|
|
564
|
+
/**
|
|
565
|
+
* @param {StreamId} streamId
|
|
566
|
+
*/
|
|
567
|
+
GetPriorityFor(streamId) {
|
|
568
|
+
const stream = this.streamToGroupMap_[streamIdToKey(streamId)]
|
|
569
|
+
if (!stream) {
|
|
570
|
+
return null
|
|
571
|
+
}
|
|
572
|
+
const { sendGroupId, perGroupScheduler } = stream
|
|
573
|
+
const sendOrder = perGroupScheduler.GetPriorityFor(streamId)
|
|
574
|
+
if (!sendOrder) {
|
|
575
|
+
return null
|
|
576
|
+
}
|
|
577
|
+
return { sendGroupId, sendOrder }
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// Pops the highest priority stream. Will fail if the schedule is empty.
|
|
581
|
+
PopFront() {
|
|
582
|
+
const groupId = this.activeGroups_.PopFront()
|
|
583
|
+
if (typeof groupId === 'undefined') {
|
|
584
|
+
return undefined
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
const scheduler = this.perGroupSchedulers_[sendGroupIdToKey(groupId)]
|
|
588
|
+
if (!scheduler) {
|
|
589
|
+
throw new Error('Scheduled a group with no per-group scheduler attached')
|
|
590
|
+
}
|
|
591
|
+
const result = scheduler.PopFront()
|
|
592
|
+
if (typeof result === 'undefined') {
|
|
593
|
+
return undefined
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// Reschedule the group if it has more active streams in it.
|
|
597
|
+
if (scheduler.HasScheduled()) {
|
|
598
|
+
this.activeGroups_.Schedule(groupId)
|
|
599
|
+
}
|
|
600
|
+
return result
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// Adds `stream` to the schedule if it's not already there.
|
|
604
|
+
/**
|
|
605
|
+
* @param {StreamId} streamId
|
|
606
|
+
*/
|
|
607
|
+
Schedule(streamId) {
|
|
608
|
+
const it = this.streamToGroupMap_[streamIdToKey(streamId)]
|
|
609
|
+
if (!it) {
|
|
610
|
+
return new Error('Stream ID not registered')
|
|
611
|
+
}
|
|
612
|
+
const { sendGroupId, perGroupScheduler } = it
|
|
613
|
+
this.activeGroups_.Schedule(sendGroupId)
|
|
614
|
+
return perGroupScheduler.Schedule(streamId)
|
|
615
|
+
}
|
|
616
|
+
// Returns true if `stream` is in the schedule.
|
|
617
|
+
/**
|
|
618
|
+
* @param {StreamId} streamId
|
|
619
|
+
*/
|
|
620
|
+
IsScheduled(streamId) {
|
|
621
|
+
const scheduler = this.SchedulerForStream(streamId)
|
|
622
|
+
if (!scheduler) {
|
|
623
|
+
return false
|
|
624
|
+
}
|
|
625
|
+
return scheduler.IsScheduled(streamId)
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/*
|
|
629
|
+
// All groups currently have the equal priority; this type represents the said
|
|
630
|
+
// single priority.
|
|
631
|
+
class SinglePriority {
|
|
632
|
+
public:
|
|
633
|
+
bool operator== (const SinglePriority&) const { return true; }
|
|
634
|
+
bool operator != (const SinglePriority&) const { return false; }
|
|
635
|
+
|
|
636
|
+
bool operator < (const SinglePriority&) const { return false; }
|
|
637
|
+
bool operator > (const SinglePriority&) const { return false; }
|
|
638
|
+
bool operator <= (const SinglePriority&) const { return true; }
|
|
639
|
+
bool operator >= (const SinglePriority&) const { return true; }
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
*/
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* @param {StreamId} streamId
|
|
646
|
+
*/
|
|
647
|
+
SchedulerForStream(streamId) {
|
|
648
|
+
const it = this.streamToGroupMap_[streamIdToKey(streamId)]
|
|
649
|
+
if (!it) {
|
|
650
|
+
return undefined
|
|
651
|
+
}
|
|
652
|
+
return it.perGroupScheduler
|
|
653
|
+
}
|
|
654
|
+
}
|