Skip to content

lore-transport: Happy eyeballs strategy for QUIC fallback connections across resolved addresses - #28

Closed
ahaczewski wants to merge 1 commit into
EpicGames:mainfrom
ahaczewski:fix/quic-happy-eyeballs
Closed

lore-transport: Happy eyeballs strategy for QUIC fallback connections across resolved addresses#28
ahaczewski wants to merge 1 commit into
EpicGames:mainfrom
ahaczewski:fix/quic-happy-eyeballs

Conversation

@ahaczewski

@ahaczewski ahaczewski commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Use a Happy Eyeballs strategy (see RFC8305) when the QUIC client connects
to a hostname that resolves to multiple socket addresses.

The client now:

  • preserves the resolver's preferred first address family;
  • interleaves IPv6 and IPv4 candidates while preserving relative order within each family;
  • starts the first address immediately;
  • starts subsequent attempts with a 250-millisecond stagger;
  • advances immediately when all active attempts fail;
  • limits concurrent attempts to 10;
  • returns the first successful connection and cancels the remaining attempts.

The existing per-address endpoint setup and final error behavior remain unchanged.

Why

The previous implementation awaited QUIC addresses sequentially. If
localhost resolved to ::1 before 127.0.0.1 while loreserver listened on
its default IPv4 address, the IPv6 attempt consumed the full 30-second QUIC idle
timeout before IPv4 was attempted.

This caused commands such as lore history to take about 30
seconds because repository initialization had started a background QUIC
pre-warm, even if not required.

Before:

remote_url = "lore://localhost:41337"

real 30.05

Using 127.0.0.1 directly completed in 0.08s, confirming that address
fallback was responsible for the delay.

Closes #27

Testing

Added unit coverage for:

  • IPv6-first and IPv4-first address-family interleaving;
  • starting a fallback while the first attempt remains stalled;
  • advancing immediately after an attempt fails;
  • avoiding fallback when the first address succeeds;
  • returning failure when every address fails;
  • bounding the number of concurrent attempts.

Verification performed:

cargo +nightly fmt --all -- --check
cargo test -p lore-transport
cargo clippy -p lore-transport --all-targets -- -D warnings --no-deps

All 28 lore-transport tests pass.

The original reproduction using lore://localhost:41337 now completes in:

real 0.38

A remote immutable-store query also demonstrated the complete fallback:

QUIC connecting to localhost at [::1]:41337
QUIC connecting to localhost at 127.0.0.1:41337
Success QUIC connecting to 127.0.0.1:41337
QUIC connection ... complete in 254ms

real 0.27

Note on tests

The lore-transport/src/quic/client.rs file did not contain any test previously,
and if they are wrongly placed, let me know and I'll make amendments.

Alternative approach

The alternative would be to prevent QUIC from connecting for commands
that do not require QUIC connection. I rejected that solution due to sweeping
changes required.

AI disclosure

OpenAI GPT-5.5 was used to improve upon the first version of this PR.

CoPilot-generated code review snippets were used to amend the PR with
suggested fixes.

Copilot AI review requested due to automatic review settings June 18, 2026 18:04

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the QUIC client connection logic to use a Happy Eyeballs-style strategy when a hostname resolves to multiple socket addresses, avoiding long stalls on an unreachable first address while preserving existing per-address configuration and error behavior.

Changes:

  • Collect resolved socket addresses and attempt connections concurrently with a fixed stagger (250ms).
  • Introduce connect_happy_eyeballs to schedule attempts and return the first successful connection.
  • Add unit tests covering stalled-first-attempt fallback, immediate advance on failure, no fallback after first success, and all-fail behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lore-transport/src/quic/client.rs
Comment thread lore-transport/src/quic/client.rs Outdated
Comment thread lore-transport/src/quic/client.rs
Comment thread lore-transport/src/quic/client.rs

@ragnarula ragnarula left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is neat! I wasn't aware of the happy eyeballs technique before. Left some minor comments to address.

Comment thread lore-transport/src/quic/client.rs
Comment thread lore-transport/src/quic/client.rs
Comment thread lore-transport/src/quic/client.rs Outdated
@ahaczewski
ahaczewski force-pushed the fix/quic-happy-eyeballs branch 2 times, most recently from e8bbffe to fe91bcf Compare June 19, 2026 16:18
@ahaczewski
ahaczewski requested a review from Copilot June 19, 2026 16:44

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

@mjansson

Copy link
Copy Markdown
Collaborator

This looks like a pretty good addition now!

However, please remove the additional // SPDX-FileCopyrightText: 2026 Andrzej Haczewski <ahaczewski@gmail.com> in the file header(s) - the PR intake process will correctly attribute the revision and the changes to your user/email

@ahaczewski

Copy link
Copy Markdown
Contributor Author

@mjansson Ok, I removed the unnecessary attribution, should I now squash the commits, or you'll do it on your end?

@mjansson

Copy link
Copy Markdown
Collaborator

@mjansson Ok, I removed the unnecessary attribution, should I now squash the commits, or you'll do it on your end?

Doesn't matter, but all the commits need to be with the DCO sign-off - the git commit -s part - so probably easier if you squash and commit or amend that with -s on that single commit.

@ahaczewski
ahaczewski force-pushed the fix/quic-happy-eyeballs branch from ee2c100 to f457a07 Compare June 22, 2026 13:09
@ahaczewski

Copy link
Copy Markdown
Contributor Author

@mjansson Done, one commit, signed-off as per DCO.

@mjansson mjansson left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM now

@mjansson mjansson changed the title Fix QUIC fallback across resolved addresses lore-transport: Happy eyeballs strategy for QUIC fallback connections across resolved addresses Jun 22, 2026
@ahaczewski
ahaczewski force-pushed the fix/quic-happy-eyeballs branch from f457a07 to c85f9c1 Compare June 23, 2026 08:17
@ahaczewski
ahaczewski force-pushed the fix/quic-happy-eyeballs branch from c85f9c1 to c552b7b Compare June 26, 2026 14:45
@mjansson

mjansson commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

We'll get this merged once the intake and attribution process is in place, ideally early next week.

@mjansson mjansson added the ready-to-import Approved by Epic staff for import into Lore label Jul 28, 2026
@epic-lore-bot

epic-lore-bot Bot commented Jul 28, 2026

Copy link
Copy Markdown

Import failed: GH reports conflict: PR not mergeable

@epic-lore-bot epic-lore-bot Bot added the import-failed Import into Lore failed due to a conflict label Jul 28, 2026
@epic-lore-bot

epic-lore-bot Bot commented Jul 28, 2026

Copy link
Copy Markdown

Import failed: GH reports conflict: PR not mergeable

@mjansson mjansson removed ready-to-import Approved by Epic staff for import into Lore import-failed Import into Lore failed due to a conflict labels Jul 28, 2026
@mjansson

Copy link
Copy Markdown
Collaborator

@ahaczewski can you update to main to unblock the intake process? We cannot handle conflicts yet

Signed-off-by: Andrzej Haczewski <ahaczewski@gmail.com>
@ahaczewski
ahaczewski force-pushed the fix/quic-happy-eyeballs branch from c552b7b to 5afffc2 Compare August 5, 2026 16:13
@mjansson mjansson added the ready-to-import Approved by Epic staff for import into Lore label Aug 5, 2026
@epic-lore-bot

epic-lore-bot Bot commented Aug 5, 2026

Copy link
Copy Markdown

Imported as Lore CR-294.

@epic-lore-bot epic-lore-bot Bot added imported Imported into Lore for internal review and removed ready-to-import Approved by Epic staff for import into Lore labels Aug 5, 2026
@epic-lore-bot

epic-lore-bot Bot commented Aug 5, 2026

Copy link
Copy Markdown

Closed by mirrored commit ac86246.

epic-lore-bot Bot pushed a commit that referenced this pull request Aug 5, 2026
… across resolved addresses

Use a Happy Eyeballs strategy (see [RFC8305](https://www.rfc-editor.org/info/rfc8305/)) when the QUIC client connects
to a hostname that resolves to multiple socket addresses.

The client now:

- preserves the resolver's preferred first address family;
- interleaves IPv6 and IPv4 candidates while preserving relative order within each family;
- starts the first address immediately;
- starts subsequent attempts with a 250-millisecond stagger;
- advances immediately when all active attempts fail;
- limits concurrent attempts to 10;
- returns the first successful connection and cancels the remaining attempts.

The existing per-address endpoint setup and final error behavior remain unchanged.

## Why

The previous implementation awaited QUIC addresses sequentially. If
`localhost` resolved to `::1` before `127.0.0.1` while `loreserver` listened on
its default IPv4 address, the IPv6 attempt consumed the full 30-second QUIC idle
timeout before IPv4 was attempted.

This caused commands such as `lore history` to take about 30
seconds because repository initialization had started a background QUIC
pre-warm, even if not required.

Before:

```text
remote_url = "lore://localhost:41337"

real 30.05
```

Using `127.0.0.1` directly completed in `0.08s`, confirming that address
fallback was responsible for the delay.

Closes #27

## Testing

Added unit coverage for:

- IPv6-first and IPv4-first address-family interleaving;
- starting a fallback while the first attempt remains stalled;
- advancing immediately after an attempt fails;
- avoiding fallback when the first address succeeds;
- returning failure when every address fails;
- bounding the number of concurrent attempts.


Verification performed:

```console
cargo +nightly fmt --all -- --check
cargo test -p lore-transport
cargo clippy -p lore-transport --all-targets -- -D warnings --no-deps
```

All 28 `lore-transport` tests pass.

The original reproduction using `lore://localhost:41337` now completes in:

```text
real 0.38
```

A remote immutable-store query also demonstrated the complete fallback:

```text
QUIC connecting to localhost at [::1]:41337
QUIC connecting to localhost at 127.0.0.1:41337
Success QUIC connecting to 127.0.0.1:41337
QUIC connection ... complete in 254ms

real 0.27
```

### Note on tests

The `lore-transport/src/quic/client.rs` file did not contain any test previously,
and if they are wrongly placed, let me know and I'll make amendments.

## Alternative approach

The alternative would be to prevent QUIC from connecting for commands
that do not require QUIC connection. I rejected that solution due to sweeping
changes required.

## AI disclosure

OpenAI GPT-5.5 was used to improve upon the first version of this PR.

CoPilot-generated code review snippets were used to amend the PR with
suggested fixes.

```
Imported-PR: #28
Imported-From: 5afffc2
Imported-Base: 6f14447
Imported-Merge: 8583075
Imported-Author: Andrzej Haczewski (ahaczewski)
Signed-off-by: Andrzej Haczewski <ahaczewski@gmail.com>
GH-URL: #28
```

Lore-RevId: 482
Lore-Signature: a625a714b2e4ada93734f6a98bc5c071191943f6c107e051960c38ea364bf5c7
@epic-lore-bot epic-lore-bot Bot closed this Aug 5, 2026
@epic-lore-bot epic-lore-bot Bot added the merged Merged into Lore codebase label Aug 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

imported Imported into Lore for internal review merged Merged into Lore codebase

Development

Successfully merging this pull request may close these issues.

QUIC connection waits 30 seconds before trying the next resolved address

5 participants