OHTTP test client: run requests sequentially

PiperOrigin-RevId: 931332585
diff --git a/quiche/quic/masque/masque_ohttp_client.cc b/quiche/quic/masque/masque_ohttp_client.cc
index 85ddfde..1db2d45 100644
--- a/quiche/quic/masque/masque_ohttp_client.cc
+++ b/quiche/quic/masque/masque_ohttp_client.cc
@@ -476,7 +476,7 @@
     // Key fetch request is still pending.
     return false;
   }
-  return pending_ohttp_requests_.empty();
+  return unstarted_requests_.empty() && pending_ohttp_requests_.empty();
 }
 
 void MasqueOhttpClient::Abort(absl::Status status) {
@@ -664,12 +664,31 @@
   }
   ohttp_client_.emplace(std::move(*ohttp_client));
 
-  for (const auto& per_request_config : config_.per_request_configs()) {
-    QUICHE_RETURN_IF_ERROR(SendOhttpRequest(per_request_config));
+  bool first_request = true;
+  for (size_t i = 0; i < config_.per_request_configs().size(); ++i) {
+    if (first_request || config_.send_requests_in_parallel()) {
+      QUICHE_RETURN_IF_ERROR(
+          SendOhttpRequest(config_.per_request_configs()[i]));
+      first_request = false;
+    } else {
+      unstarted_requests_.push_back(i);
+    }
   }
   return absl::OkStatus();
 }
 
+void MasqueOhttpClient::MaybeStartNextRequest() {
+  if (unstarted_requests_.empty()) {
+    return;
+  }
+  absl::Status status = SendOhttpRequest(
+      config_.per_request_configs()[unstarted_requests_.front()]);
+  unstarted_requests_.pop_front();
+  if (!status.ok()) {
+    Abort(status);
+  }
+}
+
 absl::Status MasqueOhttpClient::SendOhttpRequest(
     const Config::PerRequestConfig& per_request_config) {
   QuicUrl url(per_request_config.url(), "https");
@@ -925,6 +944,7 @@
   auto cleanup = absl::MakeCleanup([this, it, end_stream]() {
     if (end_stream) {
       pending_ohttp_requests_.erase(it);
+      MaybeStartNextRequest();
     }
   });
   if (!response.ok()) {
@@ -1103,6 +1123,7 @@
   auto cleanup = absl::MakeCleanup([this, it, end_stream]() {
     if (end_stream) {
       pending_ohttp_requests_.erase(it);
+      MaybeStartNextRequest();
     }
   });
 
diff --git a/quiche/quic/masque/masque_ohttp_client.h b/quiche/quic/masque/masque_ohttp_client.h
index 25b3bea..a33707c 100644
--- a/quiche/quic/masque/masque_ohttp_client.h
+++ b/quiche/quic/masque/masque_ohttp_client.h
@@ -6,6 +6,7 @@
 #define QUICHE_QUIC_MASQUE_MASQUE_OHTTP_CLIENT_H_
 
 #include <functional>
+#include <list>
 #include <memory>
 #include <optional>
 #include <string>
@@ -191,6 +192,9 @@
     void SetHandleGzipResponse(bool handle_gzip_response) {
       handle_gzip_response_ = handle_gzip_response;
     }
+    void SetSendRequestsInParallel(bool send_requests_in_parallel) {
+      send_requests_in_parallel_ = send_requests_in_parallel;
+    }
     absl::Status AddKeyFetchHeaders(
         const std::vector<std::string>& key_fetch_headers);
     void AddPerRequestConfig(const PerRequestConfig& per_request_config) {
@@ -215,6 +219,9 @@
       return key_fetch_headers_;
     }
     bool handle_gzip_response() const { return handle_gzip_response_; }
+    bool send_requests_in_parallel() const {
+      return send_requests_in_parallel_;
+    }
     bool skip_ohttp() const;
 
    private:
@@ -227,6 +234,7 @@
     std::vector<std::pair<std::string, std::string>> key_fetch_headers_;
     std::vector<PerRequestConfig> per_request_configs_;
     bool handle_gzip_response_ = false;
+    bool send_requests_in_parallel_ = false;
   };
 
   struct RunDetails {
@@ -296,6 +304,8 @@
   static absl::StatusOr<RunDetails> RunInner(Config config,
                                              absl::string_view info_string);
 
+  void MaybeStartNextRequest();
+
   class QUICHE_NO_EXPORT ChunkHandler
       : public quiche::ObliviousHttpChunkHandler,
         public quiche::BinaryHttpResponse::IndeterminateLengthDecoder::
@@ -418,6 +428,7 @@
   absl::flat_hash_map<RequestId, PendingRequest> pending_ohttp_requests_;
   ResponseVisitor* response_visitor_ = nullptr;
   RunDetails run_details_;
+  std::list<size_t> unstarted_requests_;
 };
 }  // namespace quic
 
diff --git a/quiche/quic/masque/masque_ohttp_client_bin.cc b/quiche/quic/masque/masque_ohttp_client_bin.cc
index 3d60774..d0a0a5f 100644
--- a/quiche/quic/masque/masque_ohttp_client_bin.cc
+++ b/quiche/quic/masque/masque_ohttp_client_bin.cc
@@ -30,6 +30,10 @@
                                 "inner request and decompresses "
                                 "gzip-encoded inner responses.");
 
+DEFINE_QUICHE_COMMAND_LINE_FLAG(bool, send_requests_in_parallel, false,
+                                "If true, sends all OHTTP requests in "
+                                "parallel, otherwise send them sequentially.");
+
 DEFINE_QUICHE_COMMAND_LINE_FLAG(
     bool, use_mtls_for_key_fetch, false,
     "If true, use mTLS when fetching the OHTTP/HPKE keys.");
@@ -133,6 +137,8 @@
       quiche::GetQuicheCommandLineFlag(FLAGS_disable_certificate_verification);
   const bool handle_gzip_response =
       quiche::GetQuicheCommandLineFlag(FLAGS_handle_gzip_response);
+  const bool send_requests_in_parallel =
+      quiche::GetQuicheCommandLineFlag(FLAGS_send_requests_in_parallel);
   const bool use_mtls_for_key_fetch =
       quiche::GetQuicheCommandLineFlag(FLAGS_use_mtls_for_key_fetch);
   const std::string client_cert_file =
@@ -214,6 +220,7 @@
   MasqueOhttpClient::Config config(/*key_fetch_url=*/urls[0],
                                    /*relay_url=*/urls[1]);
   config.SetHandleGzipResponse(handle_gzip_response);
+  config.SetSendRequestsInParallel(send_requests_in_parallel);
   if (use_mtls_for_key_fetch) {
     QUICHE_RETURN_IF_ERROR(config.ConfigureKeyFetchClientCert(
         client_cert_file, client_cert_key_file));