-
Notifications
You must be signed in to change notification settings - Fork 18.5k
Description
Proposal Details
In x/net/http2, http2.WriteScheduler is used to determine the order in which data is written to HTTP/2 streams.
x/net/http2 currently implements three write schedulers: round-robin (the default), random, and priority. The priority write scheduler is based off a prioritization scheme from RFC 7540 that is buggy, CPU hungry, and has been deprecated as of RFC 9113 (see #67817). As part of moving x/net/http2 into std (#67810), we would ideally like to deprecate this write scheduler.
While most users are likely to use the default round-robin schedulers, users of the priority write scheduler does exist, which prevents us from deprecating it.
Therefore, I propose that we take the following actions, in steps:
1. Implement RFC 9218 priority write scheduler
To overly-simplify https://www.rfc-editor.org/rfc/rfc9218.html, we will create a new priority write scheduler that prioritizes streams based on priority provided by clients. Like the round-robin write scheduler, this scheduler and its constructor will not be exported. The priority provided by client is composed of two aspects:
urgency: a number between0and7, inclusive, where lower number denotes a higher urgency. Defaults to3.incremental: a boolean, wherefalseindicates that a stream can benefit from getting partial chunks, rather than needing to wait for the complete response. Defaults tofalse.
Our new scheduler can then make the following prioritization:
- It will prioritize streams with lower
urgencyvalue (i.e. highest urgency), before streams with higherurgencyvalue. - Given a group of
incrementalstreams, it will round-robin writing between them, as they can all benefit immediately from partial responses. Conversely, for non-incrementalstreams, the scheduler will focus on writing to streams one-by-one till completion. - If streams are coming from a proxy / intermediary, we override all received
prioritytourgency=3andincremental=trueto ensure fairness among end-users of the proxy.
At this stage, there are no API changes, and the write scheduler is not really usable end-to-end yet.
2. Implement RFC 9218 end-to-end
To allow the RFC 9218 prioritization to work end-to-end, we need to propagate the priority that clients provide (via headers and/or PRIORITY_UPDATE frame) to our write scheduler. Doing so involves the following steps and corresponding API changes:
Framer support for PRIORITY_UPDATE frame
We will add support for reading and writing RFC 9218 PRIORITY_UPDATE frames to http2.Framer. Strictly speaking, this does not need to be an exported API, but doing so is consistent with handling for other frames. Exported API changes are as follows:
package http2
const FramePriorityUpdate FrameType = 0x10
type PriorityUpdateFrame struct {
FrameHeader
PriorityParam
PrioritizedStreamID uint32
}
func (f *Framer) WritePriorityUpdate(streamID uint32, p PriorityParam) error {}
New SETTINGS_NO_RFC7540_PRIORITIES setting
RFC 9218 defines a SETTINGS_NO_RFC7540_PRIORITIES setting to permit clients or servers to indicate that they are not using the deprecated RFC 7540 prioritization scheme.
We will add a const for this setting, matching other settings handled by the http2 package:
package http2
const SettingNoRFC7540Priorities SettingID = 0x9
RFC 9218 does not provide a prioritization scheme negotiation mechanism. There is no way for a server to indicate that it supports RFC 9218 priorities, but can fall back to RFC 7540 when available.
When the HTTP/2 server uses a write scheduler that we know does not use RFC 7540 priorities (round robin or the new RFC 9218 scheduler), we will send a SETTINGS_NO_RFC7540_PRIORITIES value of 1 in its initial SETTINGS.
3. Deprecate the existing RFC 7540 priority write scheduler
Once the RFC 9218 priority write scheduler is ready, we can then do a clean switch by changing http2.NewPriorityWriteScheduler to return the RFC 9218 priority write scheduler.
We will keep all existing APIs the same for backward-compatibility. For example, http2.PriorityWriteSchedulerConfig will be kept, but ignored by the new priority write scheduler.
4. Use the RFC 9218 priority write scheduler as the default
Assuming that the new priority write scheduler works well, we can then make this scheduler the default, even for those who use HTTP/2 transparently via std.
Worth noting here, is that there will be a significant behavior change if we follow the RFC 9218 recommendation strictly:
- The current default round-robin scheduler distributes writes evenly across all streams.
- With the new priority scheduler, it is likely that the majority of streams will be handled one-by-one until completion. This is because the default
priorityhasincrementalasfalse. Under the assumption that many HTTP/2 requests do not providepriority, most of the streams received will cause our scheduler to avoid round-robin.
To avoid a sudden significant behavior change for those who might be unaware of RFC 9218, we will only use false as the default incremental value for connections that has indicated that it is aware of RFC 9218 priorities. That is to say:
- When a client has never sent a
priorityheader orPRIORITY_UPDATEframe, we will useurgency=3andincremental=trueas the default value for all of its streams. - Once the client has sent a
priorityheader orPRIORITY_UPDATEframe at least once, we will useurgency=3andincremental=falseas the default value.
This ensures that clients who do not utilize RFC 9218 will retain its previous behavior (urgency=3 and incremental=true for all streams are effectively round-robin).
Additionally, we will also add a new field within http.Server to allow users to disable stream prioritization if they so choose:
type Server struct {
// Existing fields...
DisableClientPriority bool // When true, round-robin across all streams.
}
5. Deprecate WriteScheduler
Once we have the priority scheduler used as a default, we can then deprecate the http2.WriteScheduler API, as proposed in #67817.
Metadata
Metadata
Assignees
Labels
Type
Projects
Status