Templatize PriorityWriteScheduler.

This allows it to store a QuicStreamPriority object for each registered stream,
so that the incremental bit can be accessed by QuicWriteBlockedList.  Otherwise
no behavioral change is introduced.

Also change QuicStreamPriority::urgency type from uint8_t to int to match the
type of QuicStreamPriority::kMinimumUrgency,
QuicStreamPriority::kMaximumUrgency, QuicStreamPriority::kDefaultUrgency,
spdy::SpdyPriority, PriorityWriteScheduler::kHighestPriority and
PriorityWriteScheduler::kLowestPriority.
PiperOrigin-RevId: 502978569
diff --git a/quiche/http2/core/priority_write_scheduler.h b/quiche/http2/core/priority_write_scheduler.h
index 54181f3..4e32b90 100644
--- a/quiche/http2/core/priority_write_scheduler.h
+++ b/quiche/http2/core/priority_write_scheduler.h
@@ -29,11 +29,20 @@
 class PriorityWriteSchedulerPeer;
 }
 
+// SpdyPriority is an integer type, so this functor can be used both as
+// PriorityTypeToInt and as IntToPriorityType.
+struct QUICHE_EXPORT SpdyPriorityToSpdyPriority {
+  spdy::SpdyPriority operator()(spdy::SpdyPriority priority) {
+    return priority;
+  }
+};
+
 // PriorityWriteScheduler manages the order in which HTTP/2 or HTTP/3 streams
-// are written. Each stream has a priority, which is an integer between 0 and 7.
-// Higher priority (lower integer value) streams are always given precedence
-// over lower priority (higher value) streams, as long as the higher priority
-// stream is not blocked.
+// are written. Each stream has a priority of type PriorityType. This includes
+// an integer between 0 and 7, and optionally other information that is stored
+// but otherwise ignored by this class.  Higher priority (lower integer value)
+// streams are always given precedence over lower priority (higher value)
+// streams, as long as the higher priority stream is not blocked.
 //
 // Each stream can be in one of two states: ready or not ready (for writing).
 // Ready state is changed by calling the MarkStreamReady() and
@@ -41,11 +50,11 @@
 // by PopNextReadyStream(). When returned by that method, the stream's state
 // changes to not ready.
 //
-template <typename StreamIdType>
+template <typename StreamIdType, typename PriorityType = spdy::SpdyPriority,
+          typename PriorityTypeToInt = SpdyPriorityToSpdyPriority,
+          typename IntToPriorityType = SpdyPriorityToSpdyPriority>
 class QUICHE_EXPORT PriorityWriteScheduler {
  public:
-  using PriorityType = spdy::SpdyPriority;
-
   static constexpr int kHighestPriority = 0;
   static constexpr int kLowestPriority = 7;
 
@@ -57,8 +66,8 @@
   //
   // Preconditions: `stream_id` should be unregistered.
   void RegisterStream(StreamIdType stream_id, PriorityType priority) {
-    auto stream_info =
-        std::make_unique<StreamInfo>(StreamInfo{priority, stream_id, false});
+    auto stream_info = std::make_unique<StreamInfo>(
+        StreamInfo{std::move(priority), stream_id, false});
     bool inserted =
         stream_infos_.insert(std::make_pair(stream_id, std::move(stream_info)))
             .second;
@@ -78,8 +87,10 @@
     }
     const StreamInfo* const stream_info = it->second.get();
     if (stream_info->ready) {
-      bool erased = Erase(&priority_infos_[stream_info->priority].ready_list,
-                          stream_info);
+      bool erased =
+          Erase(&priority_infos_[PriorityTypeToInt()(stream_info->priority)]
+                     .ready_list,
+                stream_info);
       QUICHE_DCHECK(erased);
     }
     stream_infos_.erase(it);
@@ -97,7 +108,7 @@
     auto it = stream_infos_.find(stream_id);
     if (it == stream_infos_.end()) {
       QUICHE_DVLOG(1) << "Stream " << stream_id << " not registered";
-      return kLowestPriority;
+      return IntToPriorityType()(kLowestPriority);
     }
     return it->second->priority;
   }
@@ -112,18 +123,30 @@
       QUICHE_DVLOG(1) << "Stream " << stream_id << " not registered";
       return;
     }
+
     StreamInfo* const stream_info = it->second.get();
     if (stream_info->priority == priority) {
       return;
     }
-    if (stream_info->ready) {
-      bool erased = Erase(&priority_infos_[stream_info->priority].ready_list,
-                          stream_info);
+
+    // Only move `stream_info` to a different bucket if the integral priority
+    // value changes.
+    if (PriorityTypeToInt()(stream_info->priority) !=
+            PriorityTypeToInt()(priority) &&
+        stream_info->ready) {
+      bool erased =
+          Erase(&priority_infos_[PriorityTypeToInt()(stream_info->priority)]
+                     .ready_list,
+                stream_info);
       QUICHE_DCHECK(erased);
-      priority_infos_[priority].ready_list.push_back(stream_info);
+      priority_infos_[PriorityTypeToInt()(priority)].ready_list.push_back(
+          stream_info);
       ++num_ready_streams_;
     }
-    stream_info->priority = priority;
+
+    // But override `priority` for the stream regardless of the integral value,
+    // because it might contain additional information.
+    stream_info->priority = std::move(priority);
   }
 
   // Records time (in microseconds) of a read/write event for the given
@@ -136,7 +159,8 @@
       QUICHE_BUG(spdy_bug_19_4) << "Stream " << stream_id << " not registered";
       return;
     }
-    PriorityInfo& priority_info = priority_infos_[it->second->priority];
+    PriorityInfo& priority_info =
+        priority_infos_[PriorityTypeToInt()(it->second->priority)];
     priority_info.last_event_time_usec =
         std::max(priority_info.last_event_time_usec, now_in_usec);
   }
@@ -154,7 +178,8 @@
     }
     int64_t last_event_time_usec = 0;
     const StreamInfo* const stream_info = it->second.get();
-    for (PriorityType p = kHighestPriority; p < stream_info->priority; ++p) {
+    for (int p = kHighestPriority;
+         p < PriorityTypeToInt()(stream_info->priority); ++p) {
       last_event_time_usec = std::max(last_event_time_usec,
                                       priority_infos_[p].last_event_time_usec);
     }
@@ -176,7 +201,7 @@
   //
   // Preconditions: `HasReadyStreams() == true`
   std::tuple<StreamIdType, PriorityType> PopNextReadyStreamAndPriority() {
-    for (PriorityType p = kHighestPriority; p <= kLowestPriority; ++p) {
+    for (int p = kHighestPriority; p <= kLowestPriority; ++p) {
       ReadyList& ready_list = priority_infos_[p].ready_list;
       if (!ready_list.empty()) {
         StreamInfo* const info = ready_list.front();
@@ -190,7 +215,7 @@
       }
     }
     QUICHE_BUG(spdy_bug_19_6) << "No ready streams available";
-    return std::make_tuple(0, kLowestPriority);
+    return std::make_tuple(0, IntToPriorityType()(kLowestPriority));
   }
 
   // Returns true if there's another stream ahead of the given stream in the
@@ -207,7 +232,8 @@
 
     // If there's a higher priority stream, this stream should yield.
     const StreamInfo* const stream_info = it->second.get();
-    for (PriorityType p = kHighestPriority; p < stream_info->priority; ++p) {
+    for (int p = kHighestPriority;
+         p < PriorityTypeToInt()(stream_info->priority); ++p) {
       if (!priority_infos_[p].ready_list.empty()) {
         return true;
       }
@@ -215,7 +241,8 @@
 
     // If this priority level is empty, or this stream is the next up, there's
     // no need to yield.
-    const auto& ready_list = priority_infos_[it->second->priority].ready_list;
+    const auto& ready_list =
+        priority_infos_[PriorityTypeToInt()(it->second->priority)].ready_list;
     if (ready_list.empty() || ready_list.front()->stream_id == stream_id) {
       return false;
     }
@@ -240,7 +267,8 @@
     if (stream_info->ready) {
       return;
     }
-    ReadyList& ready_list = priority_infos_[stream_info->priority].ready_list;
+    ReadyList& ready_list =
+        priority_infos_[PriorityTypeToInt()(stream_info->priority)].ready_list;
     if (add_to_front) {
       ready_list.push_front(stream_info);
     } else {
@@ -264,8 +292,9 @@
     if (!stream_info->ready) {
       return;
     }
-    bool erased =
-        Erase(&priority_infos_[stream_info->priority].ready_list, stream_info);
+    bool erased = Erase(
+        &priority_infos_[PriorityTypeToInt()(stream_info->priority)].ready_list,
+        stream_info);
     QUICHE_DCHECK(erased);
     stream_info->ready = false;
   }
diff --git a/quiche/quic/core/quic_session.h b/quiche/quic/core/quic_session.h
index 4ef5579..5c97f1a 100644
--- a/quiche/quic/core/quic_session.h
+++ b/quiche/quic/core/quic_session.h
@@ -708,7 +708,7 @@
   virtual bool ShouldProcessPendingStreamImmediately() const { return true; }
 
   spdy::SpdyPriority GetSpdyPriorityofStream(QuicStreamId stream_id) const {
-    return write_blocked_streams_.GetSpdyPriorityofStream(stream_id);
+    return write_blocked_streams_.GetPriorityofStream(stream_id).urgency;
   }
 
   size_t pending_streams_size() const { return pending_stream_map_.size(); }
diff --git a/quiche/quic/core/quic_stream_priority.h b/quiche/quic/core/quic_stream_priority.h
index 04db767..de2cce2 100644
--- a/quiche/quic/core/quic_stream_priority.h
+++ b/quiche/quic/core/quic_stream_priority.h
@@ -27,7 +27,7 @@
   static constexpr absl::string_view kUrgencyKey = "u";
   static constexpr absl::string_view kIncrementalKey = "i";
 
-  uint8_t urgency = kDefaultUrgency;
+  int urgency = kDefaultUrgency;
   bool incremental = kDefaultIncremental;
 
   bool operator==(const QuicStreamPriority& other) const {
@@ -40,6 +40,19 @@
   }
 };
 
+// Functors to be used as template parameters for PriorityWriteScheduler.
+struct QUICHE_EXPORT QuicStreamPriorityToInt {
+  int operator()(const QuicStreamPriority& priority) {
+    return priority.urgency;
+  }
+};
+
+struct QUICHE_EXPORT IntToQuicStreamPriority {
+  QuicStreamPriority operator()(int urgency) {
+    return QuicStreamPriority{urgency};
+  }
+};
+
 // Serializes the Priority Field Value for a PRIORITY_UPDATE frame.
 QUICHE_EXPORT std::string SerializePriorityFieldValue(
     QuicStreamPriority priority);
diff --git a/quiche/quic/core/quic_write_blocked_list.cc b/quiche/quic/core/quic_write_blocked_list.cc
index 473b154..2497907 100644
--- a/quiche/quic/core/quic_write_blocked_list.cc
+++ b/quiche/quic/core/quic_write_blocked_list.cc
@@ -38,7 +38,7 @@
   const auto id_and_priority =
       priority_write_scheduler_.PopNextReadyStreamAndPriority();
   const QuicStreamId id = std::get<0>(id_and_priority);
-  const spdy::SpdyPriority priority = std::get<1>(id_and_priority);
+  const spdy::SpdyPriority priority = std::get<1>(id_and_priority).urgency;
 
   if (!priority_write_scheduler_.HasReadyStreams()) {
     // If no streams are blocked, don't bother latching.  This stream will be
@@ -65,7 +65,7 @@
     return;
   }
 
-  priority_write_scheduler_.RegisterStream(stream_id, priority.urgency);
+  priority_write_scheduler_.RegisterStream(stream_id, priority);
 }
 
 void QuicWriteBlockedList::UnregisterStream(QuicStreamId stream_id,
@@ -80,8 +80,7 @@
 void QuicWriteBlockedList::UpdateStreamPriority(
     QuicStreamId stream_id, const QuicStreamPriority& new_priority) {
   QUICHE_DCHECK(!static_stream_collection_.IsRegistered(stream_id));
-  priority_write_scheduler_.UpdateStreamPriority(stream_id,
-                                                 new_priority.urgency);
+  priority_write_scheduler_.UpdateStreamPriority(stream_id, new_priority);
 }
 
 void QuicWriteBlockedList::UpdateBytesForStream(QuicStreamId stream_id,
diff --git a/quiche/quic/core/quic_write_blocked_list.h b/quiche/quic/core/quic_write_blocked_list.h
index 1bbff6e..0ad2c2d 100644
--- a/quiche/quic/core/quic_write_blocked_list.h
+++ b/quiche/quic/core/quic_write_blocked_list.h
@@ -50,7 +50,7 @@
 
   bool ShouldYield(QuicStreamId id) const;
 
-  spdy::SpdyPriority GetSpdyPriorityofStream(QuicStreamId id) const {
+  QuicStreamPriority GetPriorityofStream(QuicStreamId id) const {
     return priority_write_scheduler_.GetStreamPriority(id);
   }
 
@@ -83,7 +83,10 @@
   bool IsStreamBlocked(QuicStreamId stream_id) const;
 
  private:
-  http2::PriorityWriteScheduler<QuicStreamId> priority_write_scheduler_;
+  http2::PriorityWriteScheduler<QuicStreamId, QuicStreamPriority,
+                                QuicStreamPriorityToInt,
+                                IntToQuicStreamPriority>
+      priority_write_scheduler_;
 
   // If performing batch writes, this will be the stream ID of the stream doing
   // batch writes for this priority level.  We will allow this stream to write
diff --git a/quiche/quic/core/quic_write_blocked_list_test.cc b/quiche/quic/core/quic_write_blocked_list_test.cc
index 2acaa35..1847993 100644
--- a/quiche/quic/core/quic_write_blocked_list_test.cc
+++ b/quiche/quic/core/quic_write_blocked_list_test.cc
@@ -17,8 +17,7 @@
 constexpr bool kStatic = true;
 constexpr bool kNotStatic = false;
 
-// Default value for `incremental`, see RFC9218.
-// TODO(b/147306124): Add support and tests for `incremental` being true.
+constexpr bool kIncremental = true;
 constexpr bool kNotIncremental = false;
 
 class QuicWriteBlockedListTest : public QuicTest {
@@ -43,8 +42,8 @@
     return write_blocked_list_.ShouldYield(id);
   }
 
-  spdy::SpdyPriority GetSpdyPriorityofStream(QuicStreamId id) const {
-    return write_blocked_list_.GetSpdyPriorityofStream(id);
+  QuicStreamPriority GetPriorityofStream(QuicStreamId id) const {
+    return write_blocked_list_.GetPriorityofStream(id);
   }
 
   QuicStreamId PopFront() { return write_blocked_list_.PopFront(); }
@@ -83,14 +82,19 @@
   // Mark streams blocked in roughly reverse priority order, and
   // verify that streams are sorted.
   RegisterStream(40, kNotStatic, {kV3LowestPriority, kNotIncremental});
-  RegisterStream(23, kNotStatic, {kV3HighestPriority, kNotIncremental});
+  RegisterStream(23, kNotStatic, {kV3HighestPriority, kIncremental});
   RegisterStream(17, kNotStatic, {kV3HighestPriority, kNotIncremental});
   RegisterStream(1, kStatic, {kV3HighestPriority, kNotIncremental});
   RegisterStream(3, kStatic, {kV3HighestPriority, kNotIncremental});
 
-  EXPECT_EQ(kV3LowestPriority, GetSpdyPriorityofStream(40));
-  EXPECT_EQ(kV3HighestPriority, GetSpdyPriorityofStream(23));
-  EXPECT_EQ(kV3HighestPriority, GetSpdyPriorityofStream(17));
+  EXPECT_EQ(kV3LowestPriority, GetPriorityofStream(40).urgency);
+  EXPECT_EQ(kNotIncremental, GetPriorityofStream(40).incremental);
+
+  EXPECT_EQ(kV3HighestPriority, GetPriorityofStream(23).urgency);
+  EXPECT_EQ(kIncremental, GetPriorityofStream(23).incremental);
+
+  EXPECT_EQ(kV3HighestPriority, GetPriorityofStream(17).urgency);
+  EXPECT_EQ(kNotIncremental, GetPriorityofStream(17).incremental);
 
   AddStream(40);
   EXPECT_TRUE(IsStreamBlocked(40));
@@ -302,22 +306,32 @@
 
 TEST_F(QuicWriteBlockedListTest, UpdateStreamPriority) {
   RegisterStream(40, kNotStatic, {kV3LowestPriority, kNotIncremental});
-  RegisterStream(23, kNotStatic, {6, kNotIncremental});
+  RegisterStream(23, kNotStatic, {6, kIncremental});
   RegisterStream(17, kNotStatic, {kV3HighestPriority, kNotIncremental});
   RegisterStream(1, kStatic, {2, kNotIncremental});
   RegisterStream(3, kStatic, {kV3HighestPriority, kNotIncremental});
 
-  EXPECT_EQ(kV3LowestPriority, GetSpdyPriorityofStream(40));
-  EXPECT_EQ(6, GetSpdyPriorityofStream(23));
-  EXPECT_EQ(kV3HighestPriority, GetSpdyPriorityofStream(17));
+  EXPECT_EQ(kV3LowestPriority, GetPriorityofStream(40).urgency);
+  EXPECT_EQ(kNotIncremental, GetPriorityofStream(40).incremental);
 
-  UpdateStreamPriority(40, {3, kNotIncremental});
+  EXPECT_EQ(6, GetPriorityofStream(23).urgency);
+  EXPECT_EQ(kIncremental, GetPriorityofStream(23).incremental);
+
+  EXPECT_EQ(kV3HighestPriority, GetPriorityofStream(17).urgency);
+  EXPECT_EQ(kNotIncremental, GetPriorityofStream(17).incremental);
+
+  UpdateStreamPriority(40, {3, kIncremental});
   UpdateStreamPriority(23, {kV3HighestPriority, kNotIncremental});
   UpdateStreamPriority(17, {5, kNotIncremental});
 
-  EXPECT_EQ(3, GetSpdyPriorityofStream(40));
-  EXPECT_EQ(kV3HighestPriority, GetSpdyPriorityofStream(23));
-  EXPECT_EQ(5, GetSpdyPriorityofStream(17));
+  EXPECT_EQ(3, GetPriorityofStream(40).urgency);
+  EXPECT_EQ(kIncremental, GetPriorityofStream(40).incremental);
+
+  EXPECT_EQ(kV3HighestPriority, GetPriorityofStream(23).urgency);
+  EXPECT_EQ(kNotIncremental, GetPriorityofStream(23).incremental);
+
+  EXPECT_EQ(5, GetPriorityofStream(17).urgency);
+  EXPECT_EQ(kNotIncremental, GetPriorityofStream(17).incremental);
 
   AddStream(40);
   AddStream(23);
@@ -340,8 +354,9 @@
       "IsRegistered");
 }
 
-// TODO(bnc): Test that incremental bit can be changed if urgency is the same.
-TEST_F(QuicWriteBlockedListTest, UpdateStreamPrioritySame) {
+TEST_F(QuicWriteBlockedListTest, UpdateStreamPrioritySameUrgency) {
+  // Streams with same urgency are returned by PopFront() in the order they were
+  // added by AddStream().
   RegisterStream(1, kNotStatic, {6, kNotIncremental});
   RegisterStream(2, kNotStatic, {6, kNotIncremental});
 
@@ -351,12 +366,18 @@
   EXPECT_EQ(1u, PopFront());
   EXPECT_EQ(2u, PopFront());
 
+  // Calling UpdateStreamPriority() on the first stream does not change the
+  // order.
   RegisterStream(3, kNotStatic, {6, kNotIncremental});
   RegisterStream(4, kNotStatic, {6, kNotIncremental});
 
-  EXPECT_EQ(6, GetSpdyPriorityofStream(3));
-  UpdateStreamPriority(3, {6, kNotIncremental});
-  EXPECT_EQ(6, GetSpdyPriorityofStream(3));
+  EXPECT_EQ(6, GetPriorityofStream(3).urgency);
+  EXPECT_EQ(kNotIncremental, GetPriorityofStream(3).incremental);
+
+  UpdateStreamPriority(3, {6, kIncremental});
+
+  EXPECT_EQ(6, GetPriorityofStream(3).urgency);
+  EXPECT_EQ(kIncremental, GetPriorityofStream(3).incremental);
 
   AddStream(3);
   AddStream(4);
@@ -364,12 +385,18 @@
   EXPECT_EQ(3u, PopFront());
   EXPECT_EQ(4u, PopFront());
 
-  RegisterStream(5, kNotStatic, {6, kNotIncremental});
-  RegisterStream(6, kNotStatic, {6, kNotIncremental});
+  // Calling UpdateStreamPriority() on the second stream does not change the
+  // order.
+  RegisterStream(5, kNotStatic, {6, kIncremental});
+  RegisterStream(6, kNotStatic, {6, kIncremental});
 
-  EXPECT_EQ(6, GetSpdyPriorityofStream(6));
+  EXPECT_EQ(6, GetPriorityofStream(6).urgency);
+  EXPECT_EQ(kIncremental, GetPriorityofStream(6).incremental);
+
   UpdateStreamPriority(6, {6, kNotIncremental});
-  EXPECT_EQ(6, GetSpdyPriorityofStream(6));
+
+  EXPECT_EQ(6, GetPriorityofStream(6).urgency);
+  EXPECT_EQ(kNotIncremental, GetPriorityofStream(6).incremental);
 
   AddStream(5);
   AddStream(6);