Copilot commented on code in PR #14001:
URL: https://github.com/apache/trafficserver/pull/14001#discussion_r4476689496


##########
include/proxy/http3/QPACK.h:
##########
@@ -65,13 +65,25 @@ class QPACK : public QUICApplication
   int encode(uint64_t stream_id, HTTPHdr &header_set, MIOBuffer *header_block, 
uint64_t &header_block_len);
 
   /*
-   * This will emit either of two events below:
-   * - QPACK_EVENT_DECODE_COMPLETE (Data: *HTTPHdr)
-   * - QPACK_EVENT_DECODE_FAILED (Data: nullptr)
+   * This will emit either of two events below, on @a thread:
+   * - QPACK_EVENT_DECODE_COMPLETE (the event's cookie is &hdr)
+   * - QPACK_EVENT_DECODE_FAILED (the event's cookie is &hdr, or nullptr if the
+   *   decoder had already failed)
+   *
+   * @a thread has to be the thread decode() is called on: QPACK keeps the
+   * results it has not delivered yet on a list that only that thread touches.
    */
   int decode(uint64_t stream_id, const uint8_t *header_block, size_t 
header_block_len, HTTPHdr &hdr, Continuation *cont,
              EThread *thread = this_ethread());

Review Comment:
   The updated contract states `thread` must be the same thread `decode()` is 
called on, and `QPACK::decode()` now asserts `thread == this_ethread()`. 
Keeping `thread` as a public parameter is now misleading and invites incorrect 
usage (passing a different thread will assert at runtime). Consider removing 
the parameter, making it internal-only, or at least updating the signature/API 
to prevent callers from providing a mismatched thread (e.g., no parameter, or a 
stronger type/alternative entry point).



##########
src/proxy/http3/Http3HeaderVIOAdaptor.cc:
##########
@@ -45,6 +45,11 @@ Http3HeaderVIOAdaptor::Http3HeaderVIOAdaptor(VIO *sink, 
HTTPType http_type, QPAC
 
 Http3HeaderVIOAdaptor::~Http3HeaderVIOAdaptor()
 {
+  // QPACK may still hold this adaptor and its header, for a decode that is
+  // blocked or whose result is still in the event queue.
+  if (this->_qpack != nullptr) {

Review Comment:
   With the new thread-affinity requirements (decode/cancel must run on the 
delivery thread), calling `cancel()` directly from this destructor is only safe 
if the adaptor is guaranteed to be destroyed on the same event thread that ran 
`decode()`. If that guarantee exists, consider asserting it here for safety; 
otherwise, marshal the cancel onto the correct event thread to avoid races with 
QPACK’s pending report/block lists.



##########
src/proxy/http3/QPACK.cc:
##########
@@ -310,6 +319,68 @@ QPACK::decode(uint64_t stream_id, const uint8_t 
*header_block, size_t header_blo
   return 0;
 }
 
+int
+QPACK::cancel(uint64_t stream_id)
+{
+  int dropped = 0;
+
+  for (DecodeReport *r = this->_pending_reports.head(); r != nullptr;) {
+    DecodeReport *next = DecodeReport::Linkage::next_ptr(r);
+    if (r->stream_id() == stream_id) {
+      // The report is still in the event queue and frees itself when it runs.
+      this->_pending_reports.erase(r);
+      r->withdraw();
+      ++dropped;
+    }
+    r = next;
+  }
+
+  for (DecodeRequest *r = this->_blocked_list.head(); r != nullptr;) {
+    DecodeRequest *next = DecodeRequest::Linkage::next_ptr(r);
+    if (r->stream_id() == stream_id) {
+      this->_blocked_list.erase(r);
+      delete r;
+      ++dropped;
+    }
+    r = next;
+  }
+
+  return dropped;
+}
+
+QPACK::DecodeReport::DecodeReport(QPACK *qpack, Continuation *continuation, 
uint64_t stream_id, int event, HTTPHdr *hdr)
+  : Continuation(continuation->mutex), _qpack(qpack), 
_continuation(continuation), _stream_id(stream_id), _event(event), _hdr(hdr)
+{
+  SET_HANDLER(&QPACK::DecodeReport::deliver);
+}
+
+int
+QPACK::DecodeReport::deliver(int /* event ATS_UNUSED */, Event *data)
+{
+  // Off the list before the handler runs: the handler may destroy its owner,
+  // which cancels this stream, or the session that owns this QPACK.
+  if (this->_qpack != nullptr) {
+    this->_qpack->_pending_reports.erase(this);
+  }
+  if (this->_continuation != nullptr) {
+    data->cookie = this->_hdr;
+    this->_continuation->handleEvent(this->_event, data);
+  }
+  delete this;
+  return EVENT_DONE;
+}

Review Comment:
   This forwards the `Event*` that was scheduled for `DecodeReport` into the 
consumer continuation. That means fields on `data` (notably the event’s 
associated continuation / cancellation semantics) no longer correspond to the 
consumer continuation, which can break handlers that assume the `Event*` they 
receive represents *their* scheduled event. A safer pattern is to schedule the 
consumer continuation with the desired event/cookie (creating an `Event` owned 
by the consumer) rather than calling `handleEvent()` directly with the 
wrapper’s `Event*`.



##########
src/proxy/http3/QPACK.cc:
##########
@@ -310,6 +319,68 @@ QPACK::decode(uint64_t stream_id, const uint8_t 
*header_block, size_t header_blo
   return 0;
 }
 
+int
+QPACK::cancel(uint64_t stream_id)
+{
+  int dropped = 0;
+
+  for (DecodeReport *r = this->_pending_reports.head(); r != nullptr;) {
+    DecodeReport *next = DecodeReport::Linkage::next_ptr(r);
+    if (r->stream_id() == stream_id) {
+      // The report is still in the event queue and frees itself when it runs.
+      this->_pending_reports.erase(r);
+      r->withdraw();
+      ++dropped;
+    }
+    r = next;
+  }
+
+  for (DecodeRequest *r = this->_blocked_list.head(); r != nullptr;) {
+    DecodeRequest *next = DecodeRequest::Linkage::next_ptr(r);
+    if (r->stream_id() == stream_id) {
+      this->_blocked_list.erase(r);
+      delete r;
+      ++dropped;
+    }
+    r = next;
+  }
+
+  return dropped;
+}

Review Comment:
   `cancel()` mutates `_pending_reports` and `_blocked_list`, which (per the 
new docs) are intended to be touched only by the decode/delivery thread. Unlike 
`decode()` / `_report()`, `cancel()` currently has no thread-affinity 
assertion/guard, so an off-thread destructor call could race with delivery and 
list iteration. Add the same kind of thread-affinity enforcement used by 
`decode()`/`_report()` (e.g., record the owning thread in `QPACK` and 
`ink_assert(this_ethread() == owning_thread)` in `cancel()`), or change 
`cancel()` to marshal the work onto the owning thread.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to