Skip to content

feat: Define a path migration event#3598

Merged
larseggert merged 7 commits into
mozilla:mainfrom
martinthomson:migration-event
May 13, 2026
Merged

feat: Define a path migration event#3598
larseggert merged 7 commits into
mozilla:mainfrom
martinthomson:migration-event

Conversation

@martinthomson
Copy link
Copy Markdown
Member

No description provided.

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean, well-scoped addition. The new PathMigrated event correctly covers all three migration paths: client-initiated forced migration (Connection::migrate with force=true), server-side peer migration (handle_migration), and client deferred migration upon PATH_RESPONSE validation. The was_primary guard in handle_migration correctly avoids spurious events when the path was already primary (since Paths::handle_migration is a no-op in that case aside from updating the timestamp). Deduplication via insert/contains with the derived PartialEq works as expected.

One minor import ordering nit noted inline.

Comment thread neqo-transport/src/events.rs Outdated
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Signed-off-by: Martin Thomson <mt@lowentropy.net>
@codecov
Copy link
Copy Markdown

codecov Bot commented May 10, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.99%. Comparing base (6d76b52) to head (d837223).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3598      +/-   ##
==========================================
- Coverage   95.11%   94.99%   -0.13%     
==========================================
  Files         111      116       +5     
  Lines       37931    38293     +362     
  Branches    37931    38293     +362     
==========================================
+ Hits        36078    36375     +297     
- Misses       1158     1211      +53     
- Partials      695      707      +12     
Flag Coverage Δ
freebsd 94.16% <100.00%> (-0.11%) ⬇️
linux 95.11% <100.00%> (+<0.01%) ⬆️
macos 95.06% <100.00%> (+0.01%) ⬆️
windows 95.12% <100.00%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
neqo-common 98.61% <ø> (ø)
neqo-http3 93.92% <100.00%> (ø)
neqo-qpack 95.14% <ø> (ø)
neqo-transport 95.84% <100.00%> (+0.04%) ⬆️
neqo-udp 84.90% <ø> (ø)
mtu 86.61% <ø> (ø)

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean, well-scoped addition. The three emission sites correctly cover the distinct migration triggers:

  1. Client forced migration (Connection::migrate with force=true / path already valid) — immediate event.
  2. Server peer migration (handle_migration) — event gated by was_primary to avoid spurious fires when the path is already primary.
  3. Client deferred migration (PATH_RESPONSE handler) — event after path validation completes.

Preferred address migration is covered transitively through Connection::migrate(force=false) → deferred → site 3. Deduplication via insert()/PartialEq on the derived ConnectionEvent is sound. The HTTP/3 layers correctly acknowledge and ignore the new variant.

The previous review's import ordering nit has been addressed in the second commit.

Two optional suggestions left inline: a small DRY helper for the repeated borrow-extract-emit pattern, and an event assertion in the preferred_address test to close the last coverage gap.

Comment thread neqo-transport/src/connection/mod.rs Outdated
Which should save one borrow each time.
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean, well-scoped addition. The path_migrated helper on Connection consolidates the borrow-extract-emit pattern across all three emission sites, addressing the DRY suggestion from the prior review. The import ordering nit is also fixed.

Correctness: The three call sites cover the distinct migration triggers correctly:

  1. Forced/immediate migration in Connection::migrate (client-initiated, force=true).
  2. Peer migration in handle_migration (server-side), gated by !was_primary to avoid spurious events.
  3. Deferred migration upon PATH_RESPONSE validation.

Preferred-address migration flows through site 3 transitively (via migrate(force=false) → deferred → PATH_RESPONSE), so it's covered. The preferred_address() test helper doesn't assert the event though — adding an assertion after client.process_input(resp, now()) (line 810) would close the last coverage gap.

Public API: ConnectionEvent::PathMigrated is a new variant on a public enum. Downstream consumers with exhaustive matches will need updating — the three in-tree consumers (http09, connection_client, connection_server) are handled.

Comment thread neqo-transport/src/connection/tests/migration.rs
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean, well-scoped addition. The new ConnectionEvent::PathMigrated variant and the three emission sites correctly cover all migration triggers:

  1. Forced/immediate migration (Connection::migrate with force=true or already-valid path) — event emitted synchronously before returning to caller.
  2. Server-side peer migration (handle_migration) — gated by !was_primary, which is correct: Paths::handle_migration early-returns when the path is already primary (only updating the timestamp via update(now)), so no migration actually occurs in that case.
  3. Deferred migration (PATH_RESPONSE validation) — emitted after Paths::path_response returns true, which internally calls select_primary to promote the migration target. The if let Some(primary) guard is sound since primary() is guaranteed to return Some after select_primary completes.

Preferred-address migration flows through site 1 (if path is already valid) or transitively through site 3 (via migrate(force=false) → deferred → PATH_RESPONSE), so it is covered without a dedicated call site.

The path_migrated helper on Connection consolidates the borrow-extract-emit pattern, keeping the three call sites DRY. Event deduplication via insert()/derived PartialEq works as expected for PathMigrated.

Public API: ConnectionEvent is pub and not #[non_exhaustive], so this new variant is a semver-breaking change for downstream consumers with exhaustive matches. The three in-tree consumers (http09, connection_client, connection_server) are handled. The semver CI check should surface any concern here.

Prior review suggestions (import ordering, DRY helper extraction) have been addressed in subsequent commits.

Copy link
Copy Markdown
Collaborator

@larseggert larseggert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM; minor clippy nit.

@github-actions
Copy link
Copy Markdown
Contributor

Benchmark results

Significant performance differences relative to 6d76b52.

transfer/1-conn/1-100mb-resp (aka. Download): 💔 Performance has regressed by +3.9086%.
       time:   [197.15 ms 197.54 ms 198.01 ms]
       thrpt:  [505.02 MiB/s 506.23 MiB/s 507.23 MiB/s]
change:
       time:   [+3.5454% +3.9086% +4.2733] (p = 0.00 < 0.05)
       thrpt:  [-4.0982% -3.7616% -3.4240]
       Performance has regressed.
Found 1 outliers among 100 measurements (1.00%)
1 (1.00%) high severe
transfer/1-conn/1-100mb-req (aka. Upload): 💔 Performance has regressed by +3.3303%.
       time:   [198.55 ms 199.09 ms 199.83 ms]
       thrpt:  [500.42 MiB/s 502.29 MiB/s 503.64 MiB/s]
change:
       time:   [+2.9276% +3.3303% +3.7739] (p = 0.00 < 0.05)
       thrpt:  [-3.6366% -3.2230% -2.8443]
       Performance has regressed.
Found 3 outliers among 100 measurements (3.00%)
2 (2.00%) high mild
1 (1.00%) high severe
All results
transfer/1-conn/1-100mb-resp (aka. Download): 💔 Performance has regressed by +3.9086%.
       time:   [197.15 ms 197.54 ms 198.01 ms]
       thrpt:  [505.02 MiB/s 506.23 MiB/s 507.23 MiB/s]
change:
       time:   [+3.5454% +3.9086% +4.2733] (p = 0.00 < 0.05)
       thrpt:  [-4.0982% -3.7616% -3.4240]
       Performance has regressed.
Found 1 outliers among 100 measurements (1.00%)
1 (1.00%) high severe
transfer/1-conn/10_000-parallel-1b-resp (aka. RPS): No change in performance detected.
       time:   [282.23 ms 284.12 ms 286.04 ms]
       thrpt:  [34.960 Kelem/s 35.197 Kelem/s 35.433 Kelem/s]
change:
       time:   [-1.5122% -0.5883% +0.3225] (p = 0.22 > 0.05)
       thrpt:  [-0.3215% +0.5918% +1.5354]
       No change in performance detected.
Found 1 outliers among 100 measurements (1.00%)
1 (1.00%) high mild
transfer/1-conn/1-1b-resp (aka. HPS): No change in performance detected.
       time:   [38.537 ms 38.714 ms 38.919 ms]
       thrpt:  [25.694   B/s 25.830   B/s 25.949   B/s]
change:
       time:   [-0.3424% +0.2832% +0.9193] (p = 0.40 > 0.05)
       thrpt:  [-0.9109% -0.2824% +0.3436]
       No change in performance detected.
Found 6 outliers among 100 measurements (6.00%)
6 (6.00%) high severe
transfer/1-conn/1-100mb-req (aka. Upload): 💔 Performance has regressed by +3.3303%.
       time:   [198.55 ms 199.09 ms 199.83 ms]
       thrpt:  [500.42 MiB/s 502.29 MiB/s 503.64 MiB/s]
change:
       time:   [+2.9276% +3.3303% +3.7739] (p = 0.00 < 0.05)
       thrpt:  [-3.6366% -3.2230% -2.8443]
       Performance has regressed.
Found 3 outliers among 100 measurements (3.00%)
2 (2.00%) high mild
1 (1.00%) high severe
streams/walltime/1-streams/each-1000-bytes: No change in performance detected.
       time:   [587.63 µs 589.36 µs 591.44 µs]
       change: [-0.4199% -0.0218% +0.4140] (p = 0.92 > 0.05)
       No change in performance detected.
Found 9 outliers among 100 measurements (9.00%)
2 (2.00%) high mild
7 (7.00%) high severe
streams/walltime/1000-streams/each-1-bytes: Change within noise threshold.
       time:   [12.090 ms 12.108 ms 12.127 ms]
       change: [-0.6241% -0.4036% -0.1832] (p = 0.00 < 0.05)
       Change within noise threshold.
streams/walltime/1000-streams/each-1000-bytes: No change in performance detected.
       time:   [43.276 ms 43.328 ms 43.381 ms]
       change: [-0.3716% -0.1375% +0.0793] (p = 0.24 > 0.05)
       No change in performance detected.
Found 1 outliers among 100 measurements (1.00%)
1 (1.00%) high mild
streams-flow-controlled/walltime/1-streams/each-4194304-bytes: Change within noise threshold.
       time:   [33.742 ms 33.792 ms 33.842 ms]
       change: [+0.2775% +0.4732% +0.6940] (p = 0.00 < 0.05)
       Change within noise threshold.
streams-flow-controlled/walltime/10-streams/each-1048576-bytes: No change in performance detected.
       time:   [95.687 ms 96.979 ms 98.321 ms]
       change: [-1.8088% +0.1042% +2.1068] (p = 0.91 > 0.05)
       No change in performance detected.
transfer/walltime/pacing-false/varying-seeds: Change within noise threshold.
       time:   [22.840 ms 22.871 ms 22.919 ms]
       change: [-2.4338% -2.2011% -1.9653] (p = 0.00 < 0.05)
       Change within noise threshold.
Found 3 outliers among 100 measurements (3.00%)
1 (1.00%) high mild
2 (2.00%) high severe
transfer/walltime/pacing-true/varying-seeds: Change within noise threshold.
       time:   [23.335 ms 23.362 ms 23.396 ms]
       change: [-1.0514% -0.8775% -0.7112] (p = 0.00 < 0.05)
       Change within noise threshold.
Found 1 outliers among 100 measurements (1.00%)
1 (1.00%) high severe
transfer/walltime/pacing-false/same-seed: Change within noise threshold.
       time:   [23.000 ms 23.025 ms 23.058 ms]
       change: [-2.0394% -1.8012% -1.6015] (p = 0.00 < 0.05)
       Change within noise threshold.
Found 1 outliers among 100 measurements (1.00%)
1 (1.00%) high severe
transfer/walltime/pacing-true/same-seed: Change within noise threshold.
       time:   [23.542 ms 23.571 ms 23.615 ms]
       change: [-0.8301% -0.6798% -0.4924] (p = 0.00 < 0.05)
       Change within noise threshold.
Found 3 outliers among 100 measurements (3.00%)
2 (2.00%) high mild
1 (1.00%) high severe

Download data for profiler.firefox.com or download performance comparison data.

@github-actions
Copy link
Copy Markdown
Contributor

Failed Interop Tests

QUIC Interop Runner, client vs. server, differences relative to main at 6d76b52.

neqo-pr as clientneqo-pr as server
neqo-pr vs. go-x-net: BP BA
neqo-pr vs. haproxy: 🚀L1 BP BA
neqo-pr vs. kwik: 🚀L1 BP BA
neqo-pr vs. lsquic: baseline result missing
neqo-pr vs. msquic: A L1 C1 🚀C2
neqo-pr vs. mvfst: A BP BA
neqo-pr vs. neqo: A
neqo-pr vs. nginx: BP BA
neqo-pr vs. ngtcp2: CM
neqo-pr vs. picoquic: baseline result missing
neqo-pr vs. quic-go: A
neqo-pr vs. quiche: BP BA
neqo-pr vs. s2n-quic: CM
neqo-pr vs. tquic: S BP BA
neqo-pr vs. xquic: A L1 🚀C1
aioquic vs. neqo-pr: CM
go-x-net vs. neqo-pr: CM
kwik vs. neqo-pr: BP BA CM
msquic vs. neqo-pr: ⚠️L1 CM
mvfst vs. neqo-pr: Z A L1 C1 CM
neqo vs. neqo-pr: A
openssl vs. neqo-pr: LR M A CM
quic-go vs. neqo-pr: CM
quiche vs. neqo-pr: CM
quinn vs. neqo-pr: V2 CM
s2n-quic vs. neqo-pr: 🚀B CM
tquic vs. neqo-pr: CM
xquic vs. neqo-pr: M CM
All results

Succeeded Interop Tests

QUIC Interop Runner, client vs. server

neqo-pr as client

neqo-pr as server

Unsupported Interop Tests

QUIC Interop Runner, client vs. server

neqo-pr as client

neqo-pr as server

@github-actions
Copy link
Copy Markdown
Contributor

Client/server transfer results

Performance differences relative to 6d76b52.

Transfer of 33554432 bytes over loopback, min. 100 runs. All unit-less numbers are in milliseconds.

Client vs. server (params) Mean ± σ Min Max MiB/s ± σ Δ baseline Δ baseline
neqo-neqo-newreno-nopacing 86.4 ± 2.4 81.8 91.9 370.2 ± 13.3 💚 -1.5 -1.7%
neqo-s2n-cubic 214.2 ± 1.8 208.6 217.9 149.4 ± 17.8 💚 -0.8 -0.4%

Table above only shows statistically significant changes. See all results below.

All results

Transfer of 33554432 bytes over loopback, min. 100 runs. All unit-less numbers are in milliseconds.

Client vs. server (params) Mean ± σ Min Max MiB/s ± σ Δ baseline Δ baseline
google-google-nopacing 458.5 ± 2.3 454.9 474.2 69.8 ± 13.9
google-neqo-cubic 266.7 ± 2.4 262.9 272.1 120.0 ± 13.3 -0.3 -0.1%
msquic-msquic-nopacing 136.5 ± 51.5 111.5 524.4 234.4 ± 0.6
msquic-neqo-cubic 146.8 ± 43.7 115.5 397.0 218.0 ± 0.7 1.8 1.2%
neqo-google-cubic 768.0 ± 2.6 763.8 779.0 41.7 ± 12.3 0.3 0.0%
neqo-msquic-cubic 145.3 ± 1.4 140.9 150.7 220.2 ± 22.9 -0.3 -0.2%
neqo-neqo-cubic 86.7 ± 2.3 83.3 94.1 369.0 ± 13.9 -0.6 -0.7%
neqo-neqo-cubic-nopacing 87.0 ± 2.8 82.7 101.0 367.9 ± 11.4 -0.4 -0.4%
neqo-neqo-newreno 86.9 ± 2.3 82.9 92.4 368.2 ± 13.9 -0.6 -0.7%
neqo-neqo-newreno-nopacing 86.4 ± 2.4 81.8 91.9 370.2 ± 13.3 💚 -1.5 -1.7%
neqo-quiche-cubic 189.6 ± 2.4 184.9 198.3 168.8 ± 13.3 0.6 0.3%
neqo-s2n-cubic 214.2 ± 1.8 208.6 217.9 149.4 ± 17.8 💚 -0.8 -0.4%
quiche-neqo-cubic 173.3 ± 4.1 165.6 184.6 184.7 ± 7.8 0.6 0.4%
quiche-quiche-nopacing 138.9 ± 2.3 132.9 147.0 230.4 ± 13.9
s2n-neqo-cubic 216.6 ± 5.2 206.1 249.9 147.7 ± 6.2 0.6 0.3%
s2n-s2n-nopacing 290.9 ± 23.7 277.8 385.9 110.0 ± 1.4

Download data for profiler.firefox.com or download performance comparison data.

@larseggert larseggert added this pull request to the merge queue May 13, 2026
Merged via the queue into mozilla:main with commit 0583770 May 13, 2026
184 of 185 checks passed
@martinthomson martinthomson deleted the migration-event branch May 13, 2026 06:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants