Skip to content

proposal: x/net/http2: add support for RFC 9218 priorities #75500

@nicholashusin

Description

@nicholashusin

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 between 0 and 7, inclusive, where lower number denotes a higher urgency. Defaults to 3.
  • incremental: a boolean, where false indicates that a stream can benefit from getting partial chunks, rather than needing to wait for the complete response. Defaults to false.

Our new scheduler can then make the following prioritization:

  • It will prioritize streams with lower urgency value (i.e. highest urgency), before streams with higher urgency value.
  • Given a group of incremental streams, it will round-robin writing between them, as they can all benefit immediately from partial responses. Conversely, for non-incremental streams, 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 priority to urgency=3 and incremental=true to 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 priority has incremental as false. Under the assumption that many HTTP/2 requests do not provide priority, 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 priority header or PRIORITY_UPDATE frame, we will use urgency=3 and incremental=true as the default value for all of its streams.
  • Once the client has sent a priority header or PRIORITY_UPDATE frame at least once, we will use urgency=3 and incremental=false as 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

No one assigned

    Labels

    LibraryProposalIssues describing a requested change to the Go standard library or x/ libraries, but not to a toolProposal

    Type

    No type

    Projects

    Status

    Active

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions