Skip to content

fix(port): preserve tie order for dual-stack port publishers - #14213

Open
glours wants to merge 1 commit into
docker:mainfrom
glours:13577-port-followup
Open

fix(port): preserve tie order for dual-stack port publishers#14213
glours wants to merge 1 commit into
docker:mainfrom
glours:13577-port-followup

Conversation

@glours

@glours glours commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

What I did
Follow-up to #13577, which made docker compose port SERVICE [PRIVATE_PORT] list all port mappings when PRIVATE_PORT is omitted, by routing through containerPublishers() (shared with docker compose ps).

containerPublishers() sorts by PrivatePort using sort.Slice, which does not guarantee tie order. That didn't matter for ps, but the single-port lookup path added by #13577 now takes the first matching entry as the answer, so a dual-stack publish (same target port exposed on both an IPv4 and an IPv6 host address) could non-deterministically return either one.

  • Switch sort.Slice to sort.SliceStable in containerPublishers() so ties preserve the daemon's original order.
  • Add a regression test for that (large enough to force Go's sort past its 12-element insertion-sort fallback, since a smaller fixture can't tell a stable sort from an unstable one).
  • Add table-driven coverage for Ports()'s protocol/port filtering and its not-found error path (previously only covered by two e2e assertions).
  • Drop a dead length check and a duplicated host:port formatter left over from PRIVATE_PORT optional for docker compose port #13577's review.

Related issue
N/A

(not mandatory) A picture of a cute animal, if possible in relation to what you did
image

`docker compose port SERVICE PORT` picks the first matching publisher
as its answer, but containerPublishers()'s sort.Slice does not
guarantee tie order, so a dual-stack publish (same target port on
both an IPv4 and an IPv6 address) could non-deterministically return
either one. Switch to sort.SliceStable to preserve the daemon's
original order for ties.

Also drops a dead length check and a duplicated host:port formatter.

Signed-off-by: Guillaume Lours <glours@users.noreply.github.com>
@glours
glours requested review from a team as code owners September 11, 2026 10:23
@glours
glours requested a review from ndeloof September 11, 2026 10:23

@docker-agent docker-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Assessment: 🟢 APPROVE

The changes are correct and well-scoped. The switch from sort.Slice to sort.SliceStable in containerPublishers() correctly fixes the non-deterministic tie-breaking for dual-stack port publishers. The removed redundant length guard in cmd/compose/port.go is safe because Ports() guarantees a non-empty slice when port != 0 and no error is returned. The regression test correctly validates stable sort behaviour using 16 elements (>12 to force the quicksort path), and the new table-driven tests for Ports() provide solid coverage.

@codecov

codecov Bot commented Sep 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@ndeloof ndeloof left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed in depth: the sort.SliceStable switch is exactly right — sort.Slice's pdqsort scrambles same-PrivatePort ties once past the small-slice insertion-sort path, and the single-port lookup added by #13577 takes publishers[0] as THE answer, so a dual-stack publish could non-deterministically print the ::-side. Verified the dropped len(publishers)>0 guard in cmd/port.go is safe: Ports() already errors on an unmatched port, so publishers[0] is unreachable empty. The tie-order test forcing the quicksort partitioning path (>12 tied elements) is a nice touch — a small fixture genuinely cannot catch this. Tests green locally with -race, CI green. LGTM.

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.

3 participants