Add DelayedDatagramSocket: UDP socket with simulated network delay [chromium/src : main]

0 views
Skip to first unread message

Patrick Meenan (Gerrit)

unread,
Jul 15, 2026, 9:40:46 PM (10 days ago) Jul 15
to Yoav Weiss (@Shopify), Chromium LUCI CQ, chromium...@chromium.org, net-r...@chromium.org
Attention needed from Yoav Weiss (@Shopify)

Patrick Meenan added 11 comments

Patchset-level comments
File-level comment, Patchset 6 (Latest):
Patrick Meenan . resolved

Sorry about the delay. Some issues but nothing huge.

File net/socket/delayed_datagram_socket.h
Line 182, Patchset 6 (Latest): void RegisterQuicConnectionClosePayload(base::span<uint8_t> payload) override;
Patrick Meenan . unresolved

nit: The header should include `"base/containers/span.h"` directly since `base::span` is used in public method signatures (`delayed_datagram_socket.h:182`) and across .cc (`lines 231,347,439,710`).

Line 54, Patchset 6 (Latest):// ERR_IO_PENDING. Internally the packet is copied into a send queue and a
Patrick Meenan . unresolved

nit: Header comments state `Write()` never returns `ERR_IO_PENDING`, while both the unshaped passthrough path (`delayed_datagram_socket.cc:397`) and the direct overflow path (`:428`) delegate directly to `wrapped_socket_->Write()`, which can return `ERR_IO_PENDING`. Consider clarifying in the comment that `Write()` completes synchronously in shaping mode, but forwards `ERR_IO_PENDING` when unshaped (`!rtt.is_positive() && !upload_throttle_`).

File net/socket/delayed_datagram_socket.cc
Line 220, Patchset 6 (Latest): inner_read_closed_ = true;
Patrick Meenan . unresolved

Setting `inner_read_closed_ = true` on any `result < 0` permanently stops `EnsureInnerReadInFlight()` after recoverable datagram read errors like `ERR_MSG_TOO_BIG`. Future `Reads` can keep asynchronously surfacing the same latched error instead of restarting the read pump, even though QUIC’s single-read path ignores `ERR_MSG_TOO_BIG` and continues. Can we avoid permanently latching `inner_read_closed_ = true` on recoverable datagram errors (`ERR_MSG_TOO_BIG`), and add a unit test verifying `Read()` error propagation across two hops after an inner read error occurs?

Line 249, Patchset 6 (Latest): weak_factory_.GetWeakPtr(), std::move(data), packet_tos));
Patrick Meenan . unresolved

When `download_throttle_` is set and a valid zero-length UDP datagram arrives (`result == 0`), passing result to `download_throttle_->RequestBytes(0, ...)` triggers an immediate runtime `CHECK_GT(bytes, 0)` crash in `bandwidth_throttle.cc:107`. Can `ProcessInnerReadResult()` special-case zero-byte packets (`if (result == 0)`) and enqueue them with the normal propagation delay (`OnDownloadThrottleGranted(std::move(data), packet_tos)`) without calling `RequestBytes()`?

Line 344, Patchset 6 (Latest): int n = std::min(dest_len, static_cast<int>(front.data.size()));
Patrick Meenan . unresolved

When a queued datagram (`front.data.size()`) is larger than the caller's buffer (`dest_len`), `CopyFrontPacketInto()` truncates the datagram to `dest_len`, copies only the truncated bytes, pops `receive_queue_`, and returns `dest_len` as a successful read. POSIX returns `ERR_MSG_TOO_BIG` on truncation (`udp_socket_posix.cc:1141`), and Windows maps `WSAEMSGSIZE` to `ERR_MSG_TOO_BIG`; either way, callers should not receive a successful truncated datagram. If `static_cast<int>(front.data.size()) > dest_len`, can `CopyFrontPacketInto()` return `ERR_MSG_TOO_BIG` and consume/drop the queued datagram (`receive_queue_.pop_front();`) in the same way UDP consumes an oversized datagram from the kernel receive buffer?

Line 371, Patchset 6 (Latest): NOTREACHED();
Patrick Meenan . unresolved

optional before production wiring: While `UDPSocketWin::ReadMultiple` also executes `NOTREACHED()`, `QuicProxyDatagramClientSocket::ReadMultiple` migrated to `ReadMultipleEmulator` (`quic_proxy_datagram_client_socket.cc:304`) specifically to avoid process crashes when QUIC's `QuicUseReadMultiple` feature (`kQuicUseReadMultiple`) is active (`crbug.com/515333601`). Before wiring this wrapper into production QUIC client flows, can we delegate to `ReadMultipleEmulator` or implement batching through the delay queue?

Line 429, Patchset 6 (Latest): traffic_annotation);
Patrick Meenan . unresolved

When `send_queue_` saturates (`size() >= kMaxQueuedSendPackets`) and `!inner_write_pending_`, calling `wrapped_socket_->Write()` directly bypasses `send_queue_`. If this direct `wrapped_socket_->Write()` returns `ERR_IO_PENDING`, `Write()` returns `ERR_IO_PENDING` without setting `inner_write_pending_ = true`. When `send_timer_` later fires (`DrainOneWireSend()`), `!inner_write_pending_` passes and `DrainOneWireSend()` issues a second `wrapped_socket_->Write(...)` while the direct write is still pending, causing `UDPSocketPosix` / `UDPSocketWin` to crash on `CHECK(write_callback_.is_null())` (`udp_socket_posix.cc:594`, `udp_socket_win.cc:528`). Can we either set `inner_write_pending_ = true` when line 428 pends asynchronously (and wrap `callback` to clear it and resume draining without dropping caller completion), or consistently drop overflowing packets by returning `buffer_len` when `send_queue_` is full?

Line 484, Patchset 6 (Latest): return buffer_len;
Patrick Meenan . unresolved

When `Write()` is called in shaping mode (`config_.rtt.is_positive() || upload_throttle_`) before a successful `Connect*()` or when disconnected, `Write()` enqueues `PendingSend` and returns `buffer_len` synchronously without checking connection state. When `DrainOneWireSend()` later writes the packet to `wrapped_socket_`, `wrapped_socket_->Write()` returns `ERR_SOCKET_NOT_CONNECTED`. Because `OnInnerWireSendComplete()` (`:575`) silently drops post-sync errors, the disconnection failure is lost. Can we track connected state in `DelayedDatagramSocket` across successful `Connect*()` / async connect completion and `Close()`, then reject shaped writes with `ERR_SOCKET_NOT_CONNECTED` when not connected?

Line 556, Patchset 6 (Latest): if (send.dscp != DSCP_NO_CHANGE || send.ecn != ECN_NO_CHANGE) {
Patrick Meenan . unresolved

optional: `PendingSend` snapshots the raw `DSCP_NO_CHANGE` / `ECN_NO_CHANGE` sentinels, then `DrainOneWireSend()` resolves them later against whatever TOS state the inner socket happens to have after earlier queued sends. That can differ from the app-effective TOS at `Write()` time, especially for partial sentinels like `SetTos(DSCP_NO_CHANGE, explicit_ecn)`. Can `DelayedDatagramSocket` maintain an effective DSCP/ECN cache (`effective_dscp_` / `effective_ecn_`) and resolve `NO_CHANGE` sentinels against the cached effective state when constructing `PendingSend` in `Write()`, so `DrainOneWireSend()` applies the exact intended TOS per packet?

File net/socket/delayed_datagram_socket_unittest.cc
Line 1375, Patchset 6 (Latest): TRAFFIC_ANNOTATION_FOR_TESTS);
Patrick Meenan . unresolved

nit: In `OverflowRestoresPerPacketStateWhenInnerIdle` (`line 1374`), the return value of `socket->Write(...)` is discarded on the overflow path. Checking `EXPECT_EQ(socket->Write(...), 8);` ensures overflow return code assumptions are explicitly verified.

Open in Gerrit

Related details

Attention is currently required from:
  • Yoav Weiss (@Shopify)
Submit Requirements:
  • requirement satisfiedCode-Coverage
  • requirement is not satisfiedCode-Owners
  • requirement is not satisfiedCode-Review
  • requirement is not satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: chromium/src
Gerrit-Branch: main
Gerrit-Change-Id: I3d9f29c8900e7b6a6b6e99e5c039b0bdcaaa95d9
Gerrit-Change-Number: 8017223
Gerrit-PatchSet: 6
Gerrit-Owner: Yoav Weiss (@Shopify) <yoav...@chromium.org>
Gerrit-Reviewer: Patrick Meenan <pme...@chromium.org>
Gerrit-Reviewer: Yoav Weiss (@Shopify) <yoav...@chromium.org>
Gerrit-Attention: Yoav Weiss (@Shopify) <yoav...@chromium.org>
Gerrit-Comment-Date: Thu, 16 Jul 2026 01:40:35 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
satisfied_requirement
unsatisfied_requirement
open
diffy

Yoav Weiss (@Shopify) (Gerrit)

unread,
Jul 16, 2026, 2:46:54 AM (10 days ago) Jul 16
to Patrick Meenan, Chromium LUCI CQ, chromium...@chromium.org, net-r...@chromium.org
Attention needed from Patrick Meenan

Yoav Weiss (@Shopify) added 10 comments

File net/socket/delayed_datagram_socket.h
Line 182, Patchset 6: void RegisterQuicConnectionClosePayload(base::span<uint8_t> payload) override;
Patrick Meenan . resolved

nit: The header should include `"base/containers/span.h"` directly since `base::span` is used in public method signatures (`delayed_datagram_socket.h:182`) and across .cc (`lines 231,347,439,710`).

Yoav Weiss (@Shopify)

Done

Line 54, Patchset 6:// ERR_IO_PENDING. Internally the packet is copied into a send queue and a
Patrick Meenan . resolved

nit: Header comments state `Write()` never returns `ERR_IO_PENDING`, while both the unshaped passthrough path (`delayed_datagram_socket.cc:397`) and the direct overflow path (`:428`) delegate directly to `wrapped_socket_->Write()`, which can return `ERR_IO_PENDING`. Consider clarifying in the comment that `Write()` completes synchronously in shaping mode, but forwards `ERR_IO_PENDING` when unshaped (`!rtt.is_positive() && !upload_throttle_`).

Yoav Weiss (@Shopify)

Done

File net/socket/delayed_datagram_socket.cc
Line 220, Patchset 6: inner_read_closed_ = true;
Patrick Meenan . resolved

Setting `inner_read_closed_ = true` on any `result < 0` permanently stops `EnsureInnerReadInFlight()` after recoverable datagram read errors like `ERR_MSG_TOO_BIG`. Future `Reads` can keep asynchronously surfacing the same latched error instead of restarting the read pump, even though QUIC’s single-read path ignores `ERR_MSG_TOO_BIG` and continues. Can we avoid permanently latching `inner_read_closed_ = true` on recoverable datagram errors (`ERR_MSG_TOO_BIG`), and add a unit test verifying `Read()` error propagation across two hops after an inner read error occurs?

Yoav Weiss (@Shopify)

Done

Line 249, Patchset 6: weak_factory_.GetWeakPtr(), std::move(data), packet_tos));
Patrick Meenan . resolved

When `download_throttle_` is set and a valid zero-length UDP datagram arrives (`result == 0`), passing result to `download_throttle_->RequestBytes(0, ...)` triggers an immediate runtime `CHECK_GT(bytes, 0)` crash in `bandwidth_throttle.cc:107`. Can `ProcessInnerReadResult()` special-case zero-byte packets (`if (result == 0)`) and enqueue them with the normal propagation delay (`OnDownloadThrottleGranted(std::move(data), packet_tos)`) without calling `RequestBytes()`?

Yoav Weiss (@Shopify)

Done

Line 344, Patchset 6: int n = std::min(dest_len, static_cast<int>(front.data.size()));
Patrick Meenan . resolved

When a queued datagram (`front.data.size()`) is larger than the caller's buffer (`dest_len`), `CopyFrontPacketInto()` truncates the datagram to `dest_len`, copies only the truncated bytes, pops `receive_queue_`, and returns `dest_len` as a successful read. POSIX returns `ERR_MSG_TOO_BIG` on truncation (`udp_socket_posix.cc:1141`), and Windows maps `WSAEMSGSIZE` to `ERR_MSG_TOO_BIG`; either way, callers should not receive a successful truncated datagram. If `static_cast<int>(front.data.size()) > dest_len`, can `CopyFrontPacketInto()` return `ERR_MSG_TOO_BIG` and consume/drop the queued datagram (`receive_queue_.pop_front();`) in the same way UDP consumes an oversized datagram from the kernel receive buffer?

Yoav Weiss (@Shopify)

Done

Line 371, Patchset 6: NOTREACHED();
Patrick Meenan . resolved

optional before production wiring: While `UDPSocketWin::ReadMultiple` also executes `NOTREACHED()`, `QuicProxyDatagramClientSocket::ReadMultiple` migrated to `ReadMultipleEmulator` (`quic_proxy_datagram_client_socket.cc:304`) specifically to avoid process crashes when QUIC's `QuicUseReadMultiple` feature (`kQuicUseReadMultiple`) is active (`crbug.com/515333601`). Before wiring this wrapper into production QUIC client flows, can we delegate to `ReadMultipleEmulator` or implement batching through the delay queue?

Yoav Weiss (@Shopify)

Done

Line 429, Patchset 6: traffic_annotation);
Patrick Meenan . resolved

When `send_queue_` saturates (`size() >= kMaxQueuedSendPackets`) and `!inner_write_pending_`, calling `wrapped_socket_->Write()` directly bypasses `send_queue_`. If this direct `wrapped_socket_->Write()` returns `ERR_IO_PENDING`, `Write()` returns `ERR_IO_PENDING` without setting `inner_write_pending_ = true`. When `send_timer_` later fires (`DrainOneWireSend()`), `!inner_write_pending_` passes and `DrainOneWireSend()` issues a second `wrapped_socket_->Write(...)` while the direct write is still pending, causing `UDPSocketPosix` / `UDPSocketWin` to crash on `CHECK(write_callback_.is_null())` (`udp_socket_posix.cc:594`, `udp_socket_win.cc:528`). Can we either set `inner_write_pending_ = true` when line 428 pends asynchronously (and wrap `callback` to clear it and resume draining without dropping caller completion), or consistently drop overflowing packets by returning `buffer_len` when `send_queue_` is full?

Yoav Weiss (@Shopify)

Returning buffer_len

Line 484, Patchset 6: return buffer_len;
Patrick Meenan . resolved

When `Write()` is called in shaping mode (`config_.rtt.is_positive() || upload_throttle_`) before a successful `Connect*()` or when disconnected, `Write()` enqueues `PendingSend` and returns `buffer_len` synchronously without checking connection state. When `DrainOneWireSend()` later writes the packet to `wrapped_socket_`, `wrapped_socket_->Write()` returns `ERR_SOCKET_NOT_CONNECTED`. Because `OnInnerWireSendComplete()` (`:575`) silently drops post-sync errors, the disconnection failure is lost. Can we track connected state in `DelayedDatagramSocket` across successful `Connect*()` / async connect completion and `Close()`, then reject shaped writes with `ERR_SOCKET_NOT_CONNECTED` when not connected?

Yoav Weiss (@Shopify)

Done

Line 556, Patchset 6: if (send.dscp != DSCP_NO_CHANGE || send.ecn != ECN_NO_CHANGE) {
Patrick Meenan . resolved

optional: `PendingSend` snapshots the raw `DSCP_NO_CHANGE` / `ECN_NO_CHANGE` sentinels, then `DrainOneWireSend()` resolves them later against whatever TOS state the inner socket happens to have after earlier queued sends. That can differ from the app-effective TOS at `Write()` time, especially for partial sentinels like `SetTos(DSCP_NO_CHANGE, explicit_ecn)`. Can `DelayedDatagramSocket` maintain an effective DSCP/ECN cache (`effective_dscp_` / `effective_ecn_`) and resolve `NO_CHANGE` sentinels against the cached effective state when constructing `PendingSend` in `Write()`, so `DrainOneWireSend()` applies the exact intended TOS per packet?

Yoav Weiss (@Shopify)

Done

File net/socket/delayed_datagram_socket_unittest.cc
Line 1375, Patchset 6: TRAFFIC_ANNOTATION_FOR_TESTS);
Patrick Meenan . resolved

nit: In `OverflowRestoresPerPacketStateWhenInnerIdle` (`line 1374`), the return value of `socket->Write(...)` is discarded on the overflow path. Checking `EXPECT_EQ(socket->Write(...), 8);` ensures overflow return code assumptions are explicitly verified.

Yoav Weiss (@Shopify)

Done

Open in Gerrit

Related details

Attention is currently required from:
  • Patrick Meenan
Submit Requirements:
    • requirement satisfiedCode-Coverage
    • requirement is not satisfiedCode-Owners
    • requirement is not satisfiedCode-Review
    • requirement is not satisfiedReview-Enforcement
    Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
    Gerrit-MessageType: comment
    Gerrit-Project: chromium/src
    Gerrit-Branch: main
    Gerrit-Change-Id: I3d9f29c8900e7b6a6b6e99e5c039b0bdcaaa95d9
    Gerrit-Change-Number: 8017223
    Gerrit-PatchSet: 6
    Gerrit-Owner: Yoav Weiss (@Shopify) <yoav...@chromium.org>
    Gerrit-Reviewer: Patrick Meenan <pme...@chromium.org>
    Gerrit-Reviewer: Yoav Weiss (@Shopify) <yoav...@chromium.org>
    Gerrit-Attention: Patrick Meenan <pme...@chromium.org>
    Gerrit-Comment-Date: Thu, 16 Jul 2026 06:46:37 +0000
    Gerrit-HasComments: Yes
    Gerrit-Has-Labels: No
    Comment-In-Reply-To: Patrick Meenan <pme...@chromium.org>
    satisfied_requirement
    unsatisfied_requirement
    open
    diffy

    Patrick Meenan (Gerrit)

    unread,
    Jul 17, 2026, 8:52:18 AM (8 days ago) Jul 17
    to Yoav Weiss (@Shopify), Chromium LUCI CQ, chromium...@chromium.org, net-r...@chromium.org
    Attention needed from Yoav Weiss (@Shopify)

    Patrick Meenan added 6 comments

    Patchset-level comments
    File-level comment, Patchset 7 (Latest):
    Patrick Meenan . resolved

    Thanks. Just one correctness issue around TOS emulation, a couple of test coverage requests and a pair of documentation nits. Otherwise it's looking really good.

    File net/socket/delayed_datagram_socket.h
    Line 110, Patchset 7 (Latest):// pipeline. A native batched implementation can replace this later.
    Patrick Meenan . unresolved

    nit: Two scope claims are stale: shaped not-connected Writes are now rejected before success, and `GetPeerAddress()` cannot distinguish a valid zero-length datagram from EOF—it only reports the socket association. Can these bullets be corrected?

    File net/socket/delayed_datagram_socket.cc
    Line 35, Patchset 7 (Latest):// queue-overflow error visible to QUIC.
    Patrick Meenan . unresolved

    nit: This still describes the previous direct-write overflow behavior; The current CL drops the packet and reports the byte count. Please update this block and the unittest section header.

    Line 118, Patchset 7 (Latest): if (rv != ERR_IO_PENDING && rv == OK) {
    Patrick Meenan . unresolved

    The ordinary `Connect()` acceptance path is tested, but this synchronous `ConnectAsync()` branch is not. This is the path QUIC takes because `UDPClientSocket::ConnectAsync()` completes synchronously; removing this assignment makes every subsequent shaped Write fail with `ERR_SOCKET_NOT_CONNECTED` while the current tests still pass. Can the existing connect tests verify a shaped Write after both a sync-completing `ConnectAsync()` and the async completion path?

    Line 291, Patchset 7 (Latest): DscpAndEcn packet_tos = wrapped_socket_->GetLastTos();
    Patrick Meenan . unresolved

    This snapshot is required when read-ahead ingests packet B before packet A is delivered, but current GetLastTos coverage only checks the default before reads and after Close. Can we read two packets with distinct TOS values and assert GetLastTos after each delivery?

    Line 454, Patchset 7 (Latest): buffer, buffer_len, max_message_size, std::move(callback));
    Patrick Meenan . unresolved

    `ReadMultipleEmulator` currently emits `.tos = 0` after both synchronous and asynchronous `Read()` completion. QUIC consumes `DatagramMetadata::tos` for ECN, so `kQuicUseReadMultiple` makes every packet appear NotECT despite this wrapper’s per-packet TOS snapshot. Can the emulator preserve `socket_->GetLastTos()` here? All call sites pass `DatagramClientSocket` instances, although the `DscpAndEcn` result will need encoding back into the raw TOS byte. Please also cover two distinct TOS values through ReadMultiple.

    Open in Gerrit

    Related details

    Attention is currently required from:
    • Yoav Weiss (@Shopify)
    Submit Requirements:
      • requirement satisfiedCode-Coverage
      • requirement is not satisfiedCode-Owners
      • requirement is not satisfiedCode-Review
      • requirement is not satisfiedNo-Unresolved-Comments
      • requirement is not satisfiedReview-Enforcement
      Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
      Gerrit-MessageType: comment
      Gerrit-Project: chromium/src
      Gerrit-Branch: main
      Gerrit-Change-Id: I3d9f29c8900e7b6a6b6e99e5c039b0bdcaaa95d9
      Gerrit-Change-Number: 8017223
      Gerrit-PatchSet: 7
      Gerrit-Owner: Yoav Weiss (@Shopify) <yoav...@chromium.org>
      Gerrit-Reviewer: Patrick Meenan <pme...@chromium.org>
      Gerrit-Reviewer: Yoav Weiss (@Shopify) <yoav...@chromium.org>
      Gerrit-Attention: Yoav Weiss (@Shopify) <yoav...@chromium.org>
      Gerrit-Comment-Date: Fri, 17 Jul 2026 12:52:06 +0000
      Gerrit-HasComments: Yes
      Gerrit-Has-Labels: No
      satisfied_requirement
      unsatisfied_requirement
      open
      diffy

      Yoav Weiss (@Shopify) (Gerrit)

      unread,
      Jul 21, 2026, 10:27:44 PM (4 days ago) Jul 21
      to Patrick Meenan, Chromium LUCI CQ, chromium...@chromium.org, net-r...@chromium.org
      Attention needed from Patrick Meenan

      Yoav Weiss (@Shopify) added 5 comments

      File net/socket/delayed_datagram_socket.h
      Line 110, Patchset 7:// pipeline. A native batched implementation can replace this later.
      Patrick Meenan . resolved

      nit: Two scope claims are stale: shaped not-connected Writes are now rejected before success, and `GetPeerAddress()` cannot distinguish a valid zero-length datagram from EOF—it only reports the socket association. Can these bullets be corrected?

      Yoav Weiss (@Shopify)

      Done

      File net/socket/delayed_datagram_socket.cc
      Line 35, Patchset 7:// queue-overflow error visible to QUIC.
      Patrick Meenan . resolved

      nit: This still describes the previous direct-write overflow behavior; The current CL drops the packet and reports the byte count. Please update this block and the unittest section header.

      Yoav Weiss (@Shopify)

      Done

      Line 118, Patchset 7: if (rv != ERR_IO_PENDING && rv == OK) {
      Patrick Meenan . resolved

      The ordinary `Connect()` acceptance path is tested, but this synchronous `ConnectAsync()` branch is not. This is the path QUIC takes because `UDPClientSocket::ConnectAsync()` completes synchronously; removing this assignment makes every subsequent shaped Write fail with `ERR_SOCKET_NOT_CONNECTED` while the current tests still pass. Can the existing connect tests verify a shaped Write after both a sync-completing `ConnectAsync()` and the async completion path?

      Yoav Weiss (@Shopify)

      Done

      Line 291, Patchset 7: DscpAndEcn packet_tos = wrapped_socket_->GetLastTos();
      Patrick Meenan . resolved

      This snapshot is required when read-ahead ingests packet B before packet A is delivered, but current GetLastTos coverage only checks the default before reads and after Close. Can we read two packets with distinct TOS values and assert GetLastTos after each delivery?

      Yoav Weiss (@Shopify)

      Done

      Line 454, Patchset 7: buffer, buffer_len, max_message_size, std::move(callback));
      Patrick Meenan . resolved

      `ReadMultipleEmulator` currently emits `.tos = 0` after both synchronous and asynchronous `Read()` completion. QUIC consumes `DatagramMetadata::tos` for ECN, so `kQuicUseReadMultiple` makes every packet appear NotECT despite this wrapper’s per-packet TOS snapshot. Can the emulator preserve `socket_->GetLastTos()` here? All call sites pass `DatagramClientSocket` instances, although the `DscpAndEcn` result will need encoding back into the raw TOS byte. Please also cover two distinct TOS values through ReadMultiple.

      Yoav Weiss (@Shopify)

      Done

      Open in Gerrit

      Related details

      Attention is currently required from:
      • Patrick Meenan
      Submit Requirements:
        • requirement satisfiedCode-Coverage
        • requirement is not satisfiedCode-Owners
        • requirement is not satisfiedCode-Review
        • requirement is not satisfiedReview-Enforcement
        Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
        Gerrit-MessageType: comment
        Gerrit-Project: chromium/src
        Gerrit-Branch: main
        Gerrit-Change-Id: I3d9f29c8900e7b6a6b6e99e5c039b0bdcaaa95d9
        Gerrit-Change-Number: 8017223
        Gerrit-PatchSet: 8
        Gerrit-Owner: Yoav Weiss (@Shopify) <yoav...@chromium.org>
        Gerrit-Reviewer: Patrick Meenan <pme...@chromium.org>
        Gerrit-Reviewer: Yoav Weiss (@Shopify) <yoav...@chromium.org>
        Gerrit-Attention: Patrick Meenan <pme...@chromium.org>
        Gerrit-Comment-Date: Wed, 22 Jul 2026 02:27:29 +0000
        Gerrit-HasComments: Yes
        Gerrit-Has-Labels: No
        Comment-In-Reply-To: Patrick Meenan <pme...@chromium.org>
        satisfied_requirement
        unsatisfied_requirement
        open
        diffy

        Patrick Meenan (Gerrit)

        unread,
        Jul 23, 2026, 12:28:01 PM (2 days ago) Jul 23
        to Yoav Weiss (@Shopify), devtools...@chromium.org, Chromium LUCI CQ, chromium...@chromium.org, blink-re...@chromium.org, blink-...@chromium.org, devtools-re...@chromium.org, fenced-fra...@chromium.org, ipc-securi...@chromium.org, network-ser...@chromium.org, net-r...@chromium.org
        Attention needed from Yoav Weiss (@Shopify)

        Patrick Meenan added 11 comments

        Patchset-level comments
        File-level comment, Patchset 11 (Latest):
        Patrick Meenan . resolved

        Sorry for the delay, wanted to try to catch everything in one pass. Hopefully this is it.

        File net/socket/delayed_datagram_socket.h
        Line 113, Patchset 11 (Latest):class NET_EXPORT DelayedDatagramSocket : public DatagramClientSocket {
        Patrick Meenan . unresolved

        Consider class-level sequence checker annotation. Consider adding thread sequence assertions across public interface operations to enforce Chromium threading invariants.

        **Suggested Fix:** Add private member `SEQUENCE_CHECKER(sequence_checker_);` and check `DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);` across public methods (`Read`, `Write`, `Connect`, `Close`).

        Line 45, Patchset 11 (Latest):// task hop) — used as a surrogate for actual wire-arrival time. A caller Read()
        Patrick Meenan . unresolved

        ```suggestion
        // task hop) - used as a surrogate for actual wire-arrival time. A caller Read()
        ```

        File net/socket/delayed_datagram_socket.cc
        Line 7, Patchset 11 (Latest):#include <algorithm>
        Patrick Meenan . unresolved

        Think this is unused.

        ```suggestion
        ```

        Line 162, Patchset 8: CompletionOnceCallback callback) {
        Patrick Meenan . unresolved

        `Read()` initiates inner read-ahead without checking `connected_`, and at line :479 the zero-RTT / unlimited-upload `Write()` path forwards directly without evaluating its connection check. Both entry points must synchronously return `ERR_SOCKET_NOT_CONNECTED` before accessing the inner socket. Otherwise, an unopened Windows UDP read path can trigger a release `CHECK` in underlying OS socket plumbing.

        ```suggestion
        CompletionOnceCallback callback) {
        if (!connected_) {
        return ERR_SOCKET_NOT_CONNECTED;
        }
        ```

        Suggested Testing: In `delayed_datagram_socket_unittest.cc`, introduce a unit test fixture that instantiates a socket without calling `Connect()`, invokes `Read()`, `Write()`, and `ReadMultiple()`, and verifies `EXPECT_THAT(rv, IsError(ERR_SOCKET_NOT_CONNECTED))` without reaching underlying OS sockets.

        Line 447, Patchset 8: callback) {
        Patrick Meenan . unresolved
        ```suggestion
        callback) {
        if (!connected_) {
        return ERR_SOCKET_NOT_CONNECTED;
        }
        ```
        Line 463, Patchset 8: const NetworkTrafficAnnotationTag& traffic_annotation) {
        Patrick Meenan . unresolved
        ```suggestion
        const NetworkTrafficAnnotationTag& traffic_annotation) {
        if (!connected_) {
        return ERR_SOCKET_NOT_CONNECTED;
        }
        ```
        Line 645, Patchset 8: // Inner-write errors are silently dropped: the application already saw a
        // synchronous success from Write(), and UDP packet loss is a normal mode
        // for the protocol. Continue draining the queue.
        if (!send_queue_.empty()) {
        Patrick Meenan . unresolved

        shaped `Write()` calls report synchronous success before network transmission, while `OnInnerWireSendComplete()` discards subsequent asynchronous wire failures. This behavior suppresses critical QUIC error handling for events such as `ERR_MSG_TOO_BIG` (MTU discovery) and persistent route failures.

        ```suggestion
        if (result < 0) {
        if (result == ERR_MSG_TOO_BIG && !on_mtu_error_callback_.is_null()) {
        on_mtu_error_callback_.Run(result);
        } else if (IsFatalSocketError(result)) {
        connected_ = false;
        send_queue_.clear();
        latched_write_error_ = result;
        return;
        }
        }
        if (!send_queue_.empty()) {
        ```
        Line 750, Patchset 8: return wrapped_socket_->SetTos(dscp, ecn);
        Patrick Meenan . unresolved

        the wrapper snapshots metadata before the initial inner write, but discards the active `PendingSend` while that write is pending. A later QUIC packet can subsequently alter ECN state via `SetTos()`, causing POSIX or Windows nonblocking retries to transmit the earlier datagram using the later packet's metadata value.

        **Possible Implementation:** Rather than destroying the popped packet object when `wrapped_socket_->Write()` returns `ERR_IO_PENDING`, retain the active transfer in an explicit `std::optional<PendingSend> in_flight_send_` member. When `SetTos()` is invoked, check whether `in_flight_send_` is active; if a write is pending or undergoing nonblocking retry, defer committing option mutations to `wrapped_socket_` until `OnInnerWireSendComplete()` finishes.

        **Possible Testing:** Add a unit test where packet A is submitted under `SetTos(..., ECN_NO)` and returns `ERR_IO_PENDING`. While pending, configure `SetTos(..., ECN_CE)` for packet B, complete write A's callback, and verify via mock inspection that packet A hits the wire with `ECN_NO` and packet B with `ECN_CE`.

        Line 743, Patchset 8: if (dscp != DSCP_NO_CHANGE) {
        effective_dscp_ = dscp;
        }
        if (ecn != ECN_NO_CHANGE) {
        effective_ecn_ = ecn;
        }
        tos_configured_ = true;
        return wrapped_socket_->SetTos(dscp, ecn);
        Patrick Meenan . unresolved
        ```suggestion
        int rv = wrapped_socket_->SetTos(dscp, ecn);
        if (rv == OK) {
        if (dscp != DSCP_NO_CHANGE) {
        effective_dscp_ = dscp;
        }
        if (ecn != ECN_NO_CHANGE) {
        effective_ecn_ = ecn;
        }
        tos_configured_ = true;
        }
        return rv;
        ```
        File net/socket/diff_serv_code_point.h
        Line 71, Patchset 8: EcnCodePoint ecn) {
        Patrick Meenan . unresolved
        ```suggestion
        EcnCodePoint ecn) {
        CHECK_NE(dscp, DSCP_NO_CHANGE);
        CHECK_NE(ecn, ECN_NO_CHANGE);
        ```
        Open in Gerrit

        Related details

        Attention is currently required from:
        • Yoav Weiss (@Shopify)
        Submit Requirements:
          • requirement satisfiedCode-Coverage
          • requirement is not satisfiedCode-Owners
          • requirement is not satisfiedCode-Review
          • requirement is not satisfiedNo-Unresolved-Comments
          • requirement is not satisfiedReview-Enforcement
          Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
          Gerrit-MessageType: comment
          Gerrit-Project: chromium/src
          Gerrit-Branch: main
          Gerrit-Change-Id: I3d9f29c8900e7b6a6b6e99e5c039b0bdcaaa95d9
          Gerrit-Change-Number: 8017223
          Gerrit-PatchSet: 11
          Gerrit-Owner: Yoav Weiss (@Shopify) <yoav...@chromium.org>
          Gerrit-Reviewer: Patrick Meenan <pme...@chromium.org>
          Gerrit-Reviewer: Yoav Weiss (@Shopify) <yoav...@chromium.org>
          Gerrit-Attention: Yoav Weiss (@Shopify) <yoav...@chromium.org>
          Gerrit-Comment-Date: Thu, 23 Jul 2026 16:27:41 +0000
          Gerrit-HasComments: Yes
          Gerrit-Has-Labels: No
          satisfied_requirement
          unsatisfied_requirement
          open
          diffy

          Yoav Weiss (@Shopify) (Gerrit)

          unread,
          11:35 AM (4 hours ago) 11:35 AM
          to devtools...@chromium.org, Patrick Meenan, Chromium LUCI CQ, chromium...@chromium.org, blink-re...@chromium.org, blink-...@chromium.org, devtools-re...@chromium.org, fenced-fra...@chromium.org, ipc-securi...@chromium.org, network-ser...@chromium.org, net-r...@chromium.org
          Attention needed from Patrick Meenan

          Yoav Weiss (@Shopify) added 7 comments

          File net/socket/delayed_datagram_socket.h
          Line 113, Patchset 11:class NET_EXPORT DelayedDatagramSocket : public DatagramClientSocket {
          Patrick Meenan . resolved

          Consider class-level sequence checker annotation. Consider adding thread sequence assertions across public interface operations to enforce Chromium threading invariants.

          **Suggested Fix:** Add private member `SEQUENCE_CHECKER(sequence_checker_);` and check `DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);` across public methods (`Read`, `Write`, `Connect`, `Close`).

          Yoav Weiss (@Shopify)

          Done

          Line 45, Patchset 11:// task hop) — used as a surrogate for actual wire-arrival time. A caller Read()
          Patrick Meenan . resolved

          ```suggestion
          // task hop) - used as a surrogate for actual wire-arrival time. A caller Read()
          ```

          Yoav Weiss (@Shopify)

          Done

          File net/socket/delayed_datagram_socket.cc
          Line 7, Patchset 11:#include <algorithm>
          Patrick Meenan . resolved

          Think this is unused.

          ```suggestion
          ```

          Yoav Weiss (@Shopify)

          Done

          Line 162, Patchset 8: CompletionOnceCallback callback) {
          Patrick Meenan . resolved

          `Read()` initiates inner read-ahead without checking `connected_`, and at line :479 the zero-RTT / unlimited-upload `Write()` path forwards directly without evaluating its connection check. Both entry points must synchronously return `ERR_SOCKET_NOT_CONNECTED` before accessing the inner socket. Otherwise, an unopened Windows UDP read path can trigger a release `CHECK` in underlying OS socket plumbing.

          ```suggestion
          CompletionOnceCallback callback) {
          if (!connected_) {
          return ERR_SOCKET_NOT_CONNECTED;
          }
          ```

          Suggested Testing: In `delayed_datagram_socket_unittest.cc`, introduce a unit test fixture that instantiates a socket without calling `Connect()`, invokes `Read()`, `Write()`, and `ReadMultiple()`, and verifies `EXPECT_THAT(rv, IsError(ERR_SOCKET_NOT_CONNECTED))` without reaching underlying OS sockets.

          Yoav Weiss (@Shopify)

          Done

          Patrick Meenan . resolved
          ```suggestion
          callback) {
          if (!connected_) {
          return ERR_SOCKET_NOT_CONNECTED;
          }
          ```
          Yoav Weiss (@Shopify)

          Done

          Line 463, Patchset 8: const NetworkTrafficAnnotationTag& traffic_annotation) {
          Patrick Meenan . resolved
          ```suggestion
          const NetworkTrafficAnnotationTag& traffic_annotation) {
          if (!connected_) {
          return ERR_SOCKET_NOT_CONNECTED;
          }
          ```
          Yoav Weiss (@Shopify)

          Done

          File net/socket/diff_serv_code_point.h
          Line 71, Patchset 8: EcnCodePoint ecn) {
          Patrick Meenan . resolved
          ```suggestion
          EcnCodePoint ecn) {
          CHECK_NE(dscp, DSCP_NO_CHANGE);
          CHECK_NE(ecn, ECN_NO_CHANGE);
          ```
          Yoav Weiss (@Shopify)

          Done

          Open in Gerrit

          Related details

          Attention is currently required from:
          • Patrick Meenan
          Submit Requirements:
          • requirement satisfiedCode-Coverage
          • requirement is not satisfiedCode-Owners
          • requirement is not satisfiedCode-Review
          • requirement is not satisfiedNo-Unresolved-Comments
          • requirement is not satisfiedReview-Enforcement
          Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
          Gerrit-MessageType: comment
          Gerrit-Project: chromium/src
          Gerrit-Branch: main
          Gerrit-Change-Id: I3d9f29c8900e7b6a6b6e99e5c039b0bdcaaa95d9
          Gerrit-Change-Number: 8017223
          Gerrit-PatchSet: 12
          Gerrit-Owner: Yoav Weiss (@Shopify) <yoav...@chromium.org>
          Gerrit-Reviewer: Patrick Meenan <pme...@chromium.org>
          Gerrit-Reviewer: Yoav Weiss (@Shopify) <yoav...@chromium.org>
          Gerrit-Attention: Patrick Meenan <pme...@chromium.org>
          Gerrit-Comment-Date: Sat, 25 Jul 2026 15:35:11 +0000
          Gerrit-HasComments: Yes
          Gerrit-Has-Labels: No
          Comment-In-Reply-To: Patrick Meenan <pme...@chromium.org>
          satisfied_requirement
          unsatisfied_requirement
          open
          diffy

          Yoav Weiss (@Shopify) (Gerrit)

          unread,
          11:43 AM (4 hours ago) 11:43 AM
          to devtools...@chromium.org, Patrick Meenan, Chromium LUCI CQ, chromium...@chromium.org, blink-re...@chromium.org, blink-...@chromium.org, devtools-re...@chromium.org, fenced-fra...@chromium.org, ipc-securi...@chromium.org, network-ser...@chromium.org, net-r...@chromium.org
          Attention needed from Patrick Meenan

          Yoav Weiss (@Shopify) added 3 comments

          File net/socket/delayed_datagram_socket.cc
          Line 645, Patchset 8: // Inner-write errors are silently dropped: the application already saw a
          // synchronous success from Write(), and UDP packet loss is a normal mode
          // for the protocol. Continue draining the queue.
          if (!send_queue_.empty()) {
          Patrick Meenan . resolved

          shaped `Write()` calls report synchronous success before network transmission, while `OnInnerWireSendComplete()` discards subsequent asynchronous wire failures. This behavior suppresses critical QUIC error handling for events such as `ERR_MSG_TOO_BIG` (MTU discovery) and persistent route failures.

          ```suggestion
          if (result < 0) {
          if (result == ERR_MSG_TOO_BIG && !on_mtu_error_callback_.is_null()) {
          on_mtu_error_callback_.Run(result);
          } else if (IsFatalSocketError(result)) {
          connected_ = false;
          send_queue_.clear();
          latched_write_error_ = result;
          return;
          }
          }
          if (!send_queue_.empty()) {
          ```
          Yoav Weiss (@Shopify)

          Done

          Line 750, Patchset 8: return wrapped_socket_->SetTos(dscp, ecn);
          Patrick Meenan . resolved

          the wrapper snapshots metadata before the initial inner write, but discards the active `PendingSend` while that write is pending. A later QUIC packet can subsequently alter ECN state via `SetTos()`, causing POSIX or Windows nonblocking retries to transmit the earlier datagram using the later packet's metadata value.

          **Possible Implementation:** Rather than destroying the popped packet object when `wrapped_socket_->Write()` returns `ERR_IO_PENDING`, retain the active transfer in an explicit `std::optional<PendingSend> in_flight_send_` member. When `SetTos()` is invoked, check whether `in_flight_send_` is active; if a write is pending or undergoing nonblocking retry, defer committing option mutations to `wrapped_socket_` until `OnInnerWireSendComplete()` finishes.

          **Possible Testing:** Add a unit test where packet A is submitted under `SetTos(..., ECN_NO)` and returns `ERR_IO_PENDING`. While pending, configure `SetTos(..., ECN_CE)` for packet B, complete write A's callback, and verify via mock inspection that packet A hits the wire with `ECN_NO` and packet B with `ECN_CE`.

          Yoav Weiss (@Shopify)

          Dropped setting the TOS in the shaping path. See comment.

          Line 743, Patchset 8: if (dscp != DSCP_NO_CHANGE) {

          effective_dscp_ = dscp;
          }
          if (ecn != ECN_NO_CHANGE) {
          effective_ecn_ = ecn;
          }
          tos_configured_ = true;
          return wrapped_socket_->SetTos(dscp, ecn);
          Patrick Meenan . resolved
          ```suggestion

          int rv = wrapped_socket_->SetTos(dscp, ecn);
          if (rv == OK) {
          if (dscp != DSCP_NO_CHANGE) {
          effective_dscp_ = dscp;
          }
          if (ecn != ECN_NO_CHANGE) {
          effective_ecn_ = ecn;
          }
          tos_configured_ = true;
          }
          return rv;
          ```
          Yoav Weiss (@Shopify)

          Removed SetTOS entirely from the shaping path. See comment above

          Open in Gerrit

          Related details

          Attention is currently required from:
          • Patrick Meenan
          Submit Requirements:
            • requirement satisfiedCode-Coverage
            • requirement is not satisfiedCode-Owners
            • requirement is not satisfiedCode-Review
            • requirement is not satisfiedReview-Enforcement
            Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
            Gerrit-MessageType: comment
            Gerrit-Project: chromium/src
            Gerrit-Branch: main
            Gerrit-Change-Id: I3d9f29c8900e7b6a6b6e99e5c039b0bdcaaa95d9
            Gerrit-Change-Number: 8017223
            Gerrit-PatchSet: 12
            Gerrit-Owner: Yoav Weiss (@Shopify) <yoav...@chromium.org>
            Gerrit-Reviewer: Patrick Meenan <pme...@chromium.org>
            Gerrit-Reviewer: Yoav Weiss (@Shopify) <yoav...@chromium.org>
            Gerrit-Attention: Patrick Meenan <pme...@chromium.org>
            Gerrit-Comment-Date: Sat, 25 Jul 2026 15:43:15 +0000
            satisfied_requirement
            unsatisfied_requirement
            open
            diffy
            Reply all
            Reply to author
            Forward
            0 new messages